Last active
April 13, 2023 20:50
-
-
Save dglaude/2d21262b1c776e7279f39258876d32d6 to your computer and use it in GitHub Desktop.
CircuitPython: PyGamer + MLX90640 = Portable Thermal Camera
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
I am trying this with an external 320 x 240 dpi TFT display on the PyBadge.
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
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