Skip to content

Instantly share code, notes, and snippets.

@dglaude
Last active April 13, 2023 20:50
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dglaude/2d21262b1c776e7279f39258876d32d6 to your computer and use it in GitHub Desktop.
Save dglaude/2d21262b1c776e7279f39258876d32d6 to your computer and use it in GitHub Desktop.
CircuitPython: PyGamer + MLX90640 = Portable Thermal Camera
import time
import board
import busio
import adafruit_mlx90640
import displayio
number_of_colors = 13
palette = displayio.Palette(number_of_colors) # Palette with all our colors
palette[0] = 0x000000 # Black
palette[1] = 0x8e00fe
palette[2] = 0x6000fc
palette[3] = 0x2d51fd
palette[4] = 0x3dc4fa
palette[5] = 0x40c82d
palette[6] = 0xc5ff33
palette[7] = 0xfcff1d
palette[8] = 0xf6ba1c
palette[9] = 0xf69035
palette[10] = 0xf45b1b
palette[11] = 0xf3001b
palette[12] = 0xbe008e
bitmap = displayio.Bitmap(
board.DISPLAY.width,
board.DISPLAY.height,
number_of_colors,
)
print(board.DISPLAY.width)
print(board.DISPLAY.height)
# Now that we have a palette and a bitmap ready, we can create and use
# a TileGrid just like the previous example.
# The entire bitmap will be filled with palette[0] color on initialization
tile_grid = displayio.TileGrid(bitmap, pixel_shader=palette)
group = displayio.Group()
group.append(tile_grid)
board.DISPLAY.show(group)
PRINT_TEMPERATURES = False
PRINT_ASCIIART = False
PRINT_PIXEL = True
ascii32_string=" .\'^\",:;!>~+?]})|\\txcXUCOqk*#&%@"
classical_dark_to_white_70="$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`\'. "
classical_white_to_dark_10=" .:-=+*#%@"
def map32(s):
a1=20
a2=37
if s<a1:
i=0
elif s>a2:
i=31
else:
i=int(round( ( (s-a1)*31 / (a2-a1)) ))
return i
def temp2index(s):
a1=20
a2=37
b1=0
b2=12
if s<a1:
i=b1
elif s>a2:
i=b2
else:
i=int(round( b1 + ((s - a1) * (b2 - b1) / (a2 - a1)) ))
return i
i2c = busio.I2C(board.SCL, board.SDA, frequency=800000)
mlx = adafruit_mlx90640.MLX90640(i2c)
print("MLX addr detected on I2C")
print([hex(i) for i in mlx.serial_number])
mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_2_HZ
frame = [0] * 768
while True:
stamp = time.monotonic()
try:
mlx.getFrame(frame)
except ValueError:
# these happen, no biggie - retry
continue
print("Read 2 frames in %0.2f s" % (time.monotonic()-stamp))
for h in range(24): # range(24) from 0 to 23 // range(23, -1, -1) from 23 to 0
for w in range(32):
t = frame[h*32 + w]
if PRINT_TEMPERATURES:
print("%0.1f, " % t, end="")
if PRINT_ASCIIART:
c = ascii32_string[map32(t)]
print(c, end="")
if PRINT_PIXEL:
bitmap[5*w, 5*(24-h)] = temp2index(t) # Convert temperature to palette index
if PRINT_TEMPERATURES or PRINT_ASCIIART:
print()
if PRINT_TEMPERATURES or PRINT_ASCIIART:
print()
import time
import board
import busio
import adafruit_mlx90640
import displayio
number_of_colors = 13
palette = displayio.Palette(number_of_colors) # Palette with all our colors
palette[0] = 0x000000 # Black
palette[1] = 0x8e00fe
palette[2] = 0x6000fc
palette[3] = 0x2d51fd
palette[4] = 0x3dc4fa
palette[5] = 0x40c82d
palette[6] = 0xc5ff33
palette[7] = 0xfcff1d
palette[8] = 0xf6ba1c
palette[9] = 0xf69035
palette[10] = 0xf45b1b
palette[11] = 0xf3001b
palette[12] = 0xbe008e
bitmap = displayio.Bitmap(
board.DISPLAY.width,
board.DISPLAY.height,
number_of_colors,
)
print(board.DISPLAY.width)
print(board.DISPLAY.height)
# Now that we have a palette and a bitmap ready, we can create and use
# a TileGrid just like the previous example.
# The entire bitmap will be filled with palette[0] color on initialization
tile_grid = displayio.TileGrid(bitmap, pixel_shader=palette)
group = displayio.Group(scale=5)
group.append(tile_grid)
board.DISPLAY.show(group)
PRINT_TEMPERATURES = False
PRINT_ASCIIART = False
PRINT_PIXEL = True
ascii32_string=" .\'^\",:;!>~+?]})|\\txcXUCOqk*#&%@"
classical_dark_to_white_70="$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`\'. "
classical_white_to_dark_10=" .:-=+*#%@"
def map32(s):
a1=20
a2=37
if s<a1:
i=0
elif s>a2:
i=31
else:
i=int(round( ( (s-a1)*31 / (a2-a1)) ))
return i
def temp2index(s):
a1=20
a2=37
b1=0
b2=12
if s<a1:
i=b1
elif s>a2:
i=b2
else:
i=int(round( b1 + ((s - a1) * (b2 - b1) / (a2 - a1)) ))
return i
i2c = busio.I2C(board.SCL, board.SDA, frequency=800000)
mlx = adafruit_mlx90640.MLX90640(i2c)
print("MLX addr detected on I2C")
print([hex(i) for i in mlx.serial_number])
mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_2_HZ
frame = [0] * 768
while True:
stamp = time.monotonic()
try:
mlx.getFrame(frame)
except ValueError:
# these happen, no biggie - retry
continue
print("Read 2 frames in %0.2f s" % (time.monotonic()-stamp))
for h in range(24): # range(24) from 0 to 23 // range(23, -1, -1) from 23 to 0
for w in range(32):
t = frame[h*32 + w]
if PRINT_TEMPERATURES:
print("%0.1f, " % t, end="")
if PRINT_ASCIIART:
c = ascii32_string[map32(t)]
print(c, end="")
if PRINT_PIXEL:
bitmap[w, (24-h)] = temp2index(t) # Convert temperature to palette index
if PRINT_TEMPERATURES or PRINT_ASCIIART:
print()
if PRINT_TEMPERATURES or PRINT_ASCIIART:
print()
import time
import board
import busio
import adafruit_mlx90640
import displayio
import terminalio
from adafruit_display_text.label import Label
number_of_colors = 64
palette = displayio.Palette(number_of_colors) # Palette with all our colors
## Heatmap code inspired from: http://www.andrewnoske.com/wiki/Code_-_heatmaps_and_color_gradients
color_A = [ [0, 0, 0] , [0, 0, 255] ,[0, 255, 255] , [0, 255, 0] ,[255, 255, 0] , [255, 0, 0] , [255, 255, 255] ]
color_B = [ [0, 0, 255] ,[0, 255, 255] , [0, 255, 0] ,[255, 255, 0] , [255, 0, 0] ]
color_C = [ [0, 0, 0] , [255, 255, 255] ]
color_D = [ [0, 0, 255] , [255, 0, 0] ]
color = color_B
NUM_COLORS = len (color)
def MakeHeatMapColor():
for i in range(number_of_colors):
value = i * (NUM_COLORS-1) / (number_of_colors - 1);
idx1 = int(value); # Our desired color will be after this index.
if idx1 == value : # This is the corner case
red = color[idx1][0]
green = color[idx1][1]
blue = color[idx1][2]
else:
idx2 = idx1+1; # ... and before this index (inclusive).
fractBetween = value - idx1; # Distance between the two indexes (0-1).
red = int( round( (color[idx2][0] - color[idx1][0]) * fractBetween + color[idx1][0] ) )
green = int( round( (color[idx2][1] - color[idx1][1]) * fractBetween + color[idx1][1] ) )
blue = int( round( (color[idx2][2] - color[idx1][2]) * fractBetween + color[idx1][2] ) )
palette[i]= ( 0x010000 * red ) + ( 0x000100 * green ) + ( 0x000001 * blue )
MakeHeatMapColor()
image_bitmap = displayio.Bitmap( 32, 24, number_of_colors ) # Bitmap for colour coded thermal value
image_tile= displayio.TileGrid(image_bitmap, pixel_shader=palette) # Create a TileGrid using the Bitmap and Palette
image_group = displayio.Group(scale=4) # Create a Group that scale 32*24 to 128*96
image_group.append(image_tile)
scale_bitmap = displayio.Bitmap( number_of_colors, 1, number_of_colors ) #
scale_group = displayio.Group(scale=2) # Create a Group Scale must be 128 divided by number_of_colors
scale_tile = displayio.TileGrid(scale_bitmap, pixel_shader=palette, x = 0, y = 60)
scale_group.append(scale_tile)
for i in range(number_of_colors):
scale_bitmap[i, 0] = i # Fill the scale with the palette gradian
# Create the super Group
group = displayio.Group()
min_label = Label(terminalio.FONT, max_glyphs=10, color=palette[0], x = 0, y = 110)
max_label = Label(terminalio.FONT, max_glyphs=10, color=palette[number_of_colors-1], x = 80, y = 110)
# Add all the sub-group to the SuperGroup
group.append(image_group)
group.append(scale_group)
group.append(min_label)
group.append(max_label)
# Add the SuperGroup to the Display
board.DISPLAY.show(group)
mini = 0
maxi = 0
a1 = 20
a2 = 37
def temp2index(s):
global mini, maxi
global a1, a2
b1 = 1
b2 = number_of_colors - 1
if s > maxi:
maxi = s
if s < mini:
mini = s
if s < a1:
i = b1
elif s > a2:
i = b2
else:
i = int( round( b1 + ( (s - a1) * (b2 - b1) / (a2 - a1) ) ) )
return i
i2c = busio.I2C(board.SCL, board.SDA, frequency=800000)
mlx = adafruit_mlx90640.MLX90640(i2c)
print("MLX addr detected on I2C")
print([hex(i) for i in mlx.serial_number])
#mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_2_HZ
mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_4_HZ
frame = [0] * 768
while True:
stamp = time.monotonic()
try:
mlx.getFrame(frame)
except ValueError:
# these happen, no biggie - retry
continue
print("Read 2 frames in %0.2f s" % (time.monotonic()-stamp))
mini = frame[0] # Define a default min and max value
maxi = frame[0] # Will be updated by temp2index function
for h in range(24): # range(24) from 0 to 23 // range(23, -1, -1) from 23 to 0
for w in range(32):
t = frame[h*32 + w]
image_bitmap[w, (23-h)] = temp2index(t) # Convert temperature to palette index
min_label.text="%0.2f" % (mini)
max_string="%0.2f" % (maxi)
max_label.x=120-(5*len(max_string)) # Tricky calculation to left align
max_label.text=max_string
a1 = mini # Automatically change the color scale
a2 = maxi
import time
import board
import busio
import adafruit_mlx90640
import displayio
import terminalio
from adafruit_display_text.label import Label
number_of_colors = 64
palette_grey = displayio.Palette(number_of_colors) # Palette with all our colors
for i in range(number_of_colors):
palette_grey[i]= ( 0x030303 * i )
palette_red = displayio.Palette(number_of_colors) # Another Palette with all our colors
for i in range(number_of_colors):
palette_red[i]= ( ( 0x000300 * ( number_of_colors - i ) ) + 0xff0000 )
palette=palette_red
image_bitmap = displayio.Bitmap( 32, 24, number_of_colors )
# Create a TileGrid using the Bitmap and Palette
image_tile= displayio.TileGrid(image_bitmap, pixel_shader=palette)
# Create a Group
image_group = displayio.Group(scale=4) # Scale 32*24 to 128*96
scale_bitmap = displayio.Bitmap(
number_of_colors, # This is the number of pixels
1,
number_of_colors, # This is the number of colors
)
# Create a Group
scale_group = displayio.Group(scale=2) # scale=2 for number_of_colors=64
scale_tile = displayio.TileGrid(scale_bitmap, pixel_shader=palette, x = 0, y = 60)
for i in range(number_of_colors):
scale_bitmap[i, 0] = i
# Add the TileGrid to the Group
image_group.append(image_tile)
scale_group.append(scale_tile)
# Create the super Group
group = displayio.Group()
# Add the sub-group to the super group
group.append(image_group)
group.append(scale_group)
min_label = Label(terminalio.FONT, max_glyphs=32, color=palette[0], x = 0, y = 110)
group.append(min_label)
max_label = Label(terminalio.FONT, max_glyphs=32, color=palette[number_of_colors-1], x = 80, y = 110)
group.append(max_label)
# Add the Group to the Display
board.DISPLAY.show(group)
mini = 0
maxi = 0
a1 = 20
a2 = 37
def temp2index(s):
global mini, maxi
global a1, a2
b1 = 1
b2 = number_of_colors - 1
if s > maxi:
maxi = s
if s < mini:
mini = s
if s < a1:
i = b1
elif s > a2:
i = b2
else:
i = int( round( b1 + ( (s - a1) * (b2 - b1) / (a2 - a1) ) ) )
return i
i2c = busio.I2C(board.SCL, board.SDA, frequency=800000)
mlx = adafruit_mlx90640.MLX90640(i2c)
print("MLX addr detected on I2C")
print([hex(i) for i in mlx.serial_number])
mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_2_HZ
frame = [0] * 768
while True:
stamp = time.monotonic()
try:
mlx.getFrame(frame)
except ValueError:
# these happen, no biggie - retry
continue
print("Read 2 frames in %0.2f s" % (time.monotonic()-stamp))
mini = frame[0] # Define a default min and max value
maxi = frame[0] # Will be updated by temp2index function
for h in range(24): # range(24) from 0 to 23 // range(23, -1, -1) from 23 to 0
for w in range(32):
t = frame[h*32 + w]
image_bitmap[w, (23-h)] = temp2index(t) # Convert temperature to palette index
min_label.text="%0.2f" % (mini)
max_string="%0.2f" % (maxi)
max_label.x=120-(5*len(max_string)) # Tricky calculation to left align
max_label.text=max_string
a1 = mini # Automatically change the color scale
a2 = maxi
@dglaude
Copy link
Author

dglaude commented Dec 27, 2019

The first version is with a single pixel per sensor capture.
With displayio you can define a scaling factor for a group, so writing in a 32x24 bitmap, and scaling by 5 make a 160x120 area.

@dglaude
Copy link
Author

dglaude commented Dec 31, 2019

min_max.py version display the color scale and the minimum and maximum temperature from the image.
Colour scale goes from Yellow to Red.

@dglaude
Copy link
Author

dglaude commented Jan 2, 2020

heatmap_generated.py is the last version and has been proposed as an example for Adafruit: adafruit/Adafruit_CircuitPython_MLX90640#5

Some video and image available here: https://twitter.com/DavidGlaude/status/1210705136882003968?s=20

@christiankaye
Copy link

I am trying this with an external 320 x 240 dpi TFT display on the PyBadge.

@dglaude
Copy link
Author

dglaude commented Aug 27, 2022

You might be interested into this: https://gist.github.com/dglaude/cdd4ede9e43fe620637a2199e05ba8cb
Or some other of my gist at that time.
It is using ulab to do linear interpolation and invent pixel not present on the sensor, or "enhance" the image.

Not sure what a PyBadge with a 320x240 external TFT display could look like, but the code I linked has various scaling factor depending on board capabilities (memory and processing power).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment