Skip to content

Instantly share code, notes, and snippets.

@dglaude
Last active May 10, 2021 22:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dglaude/33d0460427c0c082ed6151f2d29b723b to your computer and use it in GitHub Desktop.
Save dglaude/33d0460427c0c082ed6151f2d29b723b to your computer and use it in GitHub Desktop.
mlx90640 thermal camera on CLUE with x2 bilinear scaler using ulab + x3 with displayio scale
### mlx90640_scalex2_clue.py + 64
### same as mlx90640_scalex2_clue.py but with 64 colours rather than 32 => need one optimisation on memory size.
import time
import board
import busio
import adafruit_mlx90640
import displayio
import terminalio
import bitmaptools
from adafruit_display_text.label import Label
from simpleio import map_range
import adafruit_fancyled.adafruit_fancyled as fancy
import gc
import ulab
gc.collect()
start_mem = gc.mem_free()
start_time = time.monotonic_ns()
# On PyPortal, the scale factor works from 3 to 9
# On Clue the biggest scale is 7
other_factor = 2
scale_factor = 3
total_factor = scale_factor * other_factor
text_x = (32 * scale_factor) - 30
text_y = (24 * scale_factor) + 8
gradian_y = 24 + int (20 / total_factor)
#gradian_y = int( (text_y + 8) / 2 )
#number_of_colors = 128 # Number of color in the gradian
number_of_colors = 64 # Number of color in the gradian
#number_of_colors = 32 # Number of color in the gradian
last_color = number_of_colors-1 # Last color in palette
palette = displayio.Palette(number_of_colors) # Palette with all our colors
# gradian for fancyled palette generation
grad = [(0.00, fancy.CRGB(0, 0, 255)), # Blue
(0.25, fancy.CRGB(0, 255, 255)),
(0.50, fancy.CRGB(0, 255, 0)), # Green
(0.75, fancy.CRGB(255, 255, 0)),
(1.00, fancy.CRGB(255, 0, 0))] # Red
# create our palette using fancyled expansion of our gradian
fancy_palette = fancy.expand_gradient(grad, number_of_colors)
for c in range(number_of_colors):
palette[c] = fancy_palette[c].pack()
# Bitmap for colour coded thermal value
image_bitmap = displayio.Bitmap( 2*32-1, 2*24-1, number_of_colors )
# Create a TileGrid using the Bitmap and Palette
image_tile= displayio.TileGrid(image_bitmap, pixel_shader=palette)
# Create a Group that scale 32*24 to 256*192
image_group = displayio.Group(scale=scale_factor)
image_group.append(image_tile)
#^# scale_bitmap = displayio.Bitmap( number_of_colors, 1, number_of_colors )
# Create a Group Scale
#^# scale_group = displayio.Group(scale=total_factor)
#^# scale_tile = displayio.TileGrid(scale_bitmap, pixel_shader=palette, x = 0, y = gradian_y)
#^# 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=text_y)
#_# center_label = Label(terminalio.FONT, max_glyphs=10, color=palette[int(number_of_colors/2)], x=int (text_x/2), y=text_y)
#°# max_label = Label(terminalio.FONT, max_glyphs=10, color=palette[last_color], x=text_x, y=text_y)
# Indicator for the minimum and maximum location
#°# o_label = Label(terminalio.FONT, max_glyphs = 1, text = "o", color = 0xFFFFFF, x = 0, y = 0)
#°# x_label = Label(terminalio.FONT, max_glyphs = 1, text = "x", color = 0xFFFFFF, x = 0, y = 0)
# Add all the sub-group to the SuperGroup
group.append(image_group)
#^# group.append(scale_group)
#°# group.append(min_label)
#_# group.append(center_label)
#°# group.append(max_label)
#°# group.append(o_label)
#°# group.append(x_label)
# Add the SuperGroup to the Display
board.DISPLAY.show(group)
min_t = 20 # Initial minimum temperature range, before auto scale
max_t = 37 # Initial maximum temperature range, before auto scale
i2c = busio.I2C(board.SCL, board.SDA, frequency=800000)
mlx = adafruit_mlx90640.MLX90640(i2c)
print("MLX addr detected on I2C")
#mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_2_HZ
mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_4_HZ
####frame = [0] * 768
npframe = ulab.zeros(768)
gc.collect()
mid_mem = gc.mem_free()
print("Memory now:", mid_mem - start_mem)
# Allocate once, not in the loop.
a = ulab.zeros((2*24-1, 2*32-1)) # the upscaled image
while True:
#for i in range(10):
stamp = time.monotonic()
try:
# mlx.getFrame(frame)
mlx.getFrame(npframe)
except ValueError: # these happen, no biggie - retry
continue
print("Time for data aquisition: %0.2f s" % (time.monotonic()-stamp))
stamp = time.monotonic()
# Convert in ulab.array, find the min and max and normalize to int from 0 to last_color.
# npframe=ulab.array(frame)
min_t=ulab.numerical.min(npframe)
max_t=ulab.numerical.max(npframe)
b = npframe.reshape((24,32))
# It is possible to build a 24x32 array with ulab too:
## b = inta.reshape((24,32))
# a = ulab.zeros((2*24-2, 2*32-2)) # the upscaled image
#/# a = ulab.zeros((2*32-1, 2*24-1)) # the upscaled image
# Allocate once, not in the loop.
#^# a = ulab.zeros((2*24-1, 2*32-1)) # the upscaled image
# Some old magic by @v923z
# a[::2,::2] = b
# a[1::2,::2] = 0.5 * (b[:-1,:] + b[1:, :])
# a[::2,1::2] = 0.5 * (b[:,1:] + b[:,:-1])
# a[1::2,1::2] = 0.25 * (b[:-1,1:] + b[1:,:-1] + b[1:,1:] + b[:-1,:-1])
a[::2,::2] = b
if False: # Readable arithmetic => more memory, max: n=65
a[1::2,::2] = 0.5 * (b[:-1,:] + b[1:, :])
a[::,1::2]=0.5 * (a[::,:-1:2] + a[::,2::2])
else: # Splitting into simple operation => less memory max: n=87
a[1::2,::2] = b[:-1,:]
a[1::2,::2] += b[1:, :]
a[1::2,::2] /= 2
a[::,1::2]=a[::,:-1:2]
a[::,1::2]+=a[::,2::2]
a[::,1::2]/=2
factor=last_color/(max_t-min_t)
inta=ulab.array((a-min_t)*factor,dtype=ulab.int8)
#°# minx = 0
#°# miny = 0
#°# maxx = 23
#°# maxy = 31
print("ulab processing: %0.2f s" % (time.monotonic()-stamp))
stamp_copy = time.monotonic()
bitmaptools.arrayblit(image_bitmap, inta)
print("Copy from uLab to bitmap: %0.2f s" % (time.monotonic()-stamp_copy))
# min and max temperature indicator
#°# min_label.text="%0.2f" % (min_t)
#°# max_label.text="%0.2f" % (max_t)
# Compute average_center temperature of the middle of sensor and convert to palette color
#_# center_average = (frame[11*32 + 15] + frame[12*32 + 15] + frame[11*32 + 16] + frame[12*32 + 16]) / 4
#_# center_color = int(map_range(center_average, min_t, max_t, 0, last_color ))
#_# center_label.text = "%0.2f" % (center_average)
#_# center_label.color = palette[center_color]
# Set the location of X for lowest temperature
#°# x_label.x = maxx * total_factor
#°# x_label.y = (23-maxy) * total_factor
# Set the location of O for highest temperature
#°# o_label.x = minx * total_factor
#°# o_label.y = (23-miny) * total_factor
# print("Total time for aquisition and display %0.2f s" % (time.monotonic()-stamp))
stop_time = time.monotonic_ns()
gc.collect()
stop_mem = gc.mem_free()
print(stop_time - start_time, stop_mem - start_mem)
### mlx90640_scalex2_clue.py
import time
import board
import busio
import adafruit_mlx90640
import displayio
import terminalio
import bitmaptools
from adafruit_display_text.label import Label
from simpleio import map_range
import adafruit_fancyled.adafruit_fancyled as fancy
import gc
import ulab
gc.collect()
start_mem = gc.mem_free()
start_time = time.monotonic_ns()
# On PyPortal, the scale factor works from 3 to 9
# On Clue the biggest scale is 7
other_factor = 2
scale_factor = 3
total_factor = scale_factor * other_factor
text_x = (32 * scale_factor) - 30
text_y = (24 * scale_factor) + 8
gradian_y = 24 + int (20 / total_factor)
#gradian_y = int( (text_y + 8) / 2 )
number_of_colors = 32 # Number of color in the gradian
last_color = number_of_colors-1 # Last color in palette
palette = displayio.Palette(number_of_colors) # Palette with all our colors
# gradian for fancyled palette generation
grad = [(0.00, fancy.CRGB(0, 0, 255)), # Blue
(0.25, fancy.CRGB(0, 255, 255)),
(0.50, fancy.CRGB(0, 255, 0)), # Green
(0.75, fancy.CRGB(255, 255, 0)),
(1.00, fancy.CRGB(255, 0, 0))] # Red
# create our palette using fancyled expansion of our gradian
fancy_palette = fancy.expand_gradient(grad, number_of_colors)
for c in range(number_of_colors):
palette[c] = fancy_palette[c].pack()
# Bitmap for colour coded thermal value
image_bitmap = displayio.Bitmap( 2*32-1, 2*24-1, number_of_colors )
# Create a TileGrid using the Bitmap and Palette
image_tile= displayio.TileGrid(image_bitmap, pixel_shader=palette)
# Create a Group that scale 32*24 to 256*192
image_group = displayio.Group(scale=scale_factor)
image_group.append(image_tile)
#^# scale_bitmap = displayio.Bitmap( number_of_colors, 1, number_of_colors )
# Create a Group Scale
#^# scale_group = displayio.Group(scale=total_factor)
#^# scale_tile = displayio.TileGrid(scale_bitmap, pixel_shader=palette, x = 0, y = gradian_y)
#^# 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=text_y)
#_# center_label = Label(terminalio.FONT, max_glyphs=10, color=palette[int(number_of_colors/2)], x=int (text_x/2), y=text_y)
#°# max_label = Label(terminalio.FONT, max_glyphs=10, color=palette[last_color], x=text_x, y=text_y)
# Indicator for the minimum and maximum location
#°# o_label = Label(terminalio.FONT, max_glyphs = 1, text = "o", color = 0xFFFFFF, x = 0, y = 0)
#°# x_label = Label(terminalio.FONT, max_glyphs = 1, text = "x", color = 0xFFFFFF, x = 0, y = 0)
# Add all the sub-group to the SuperGroup
group.append(image_group)
#^# group.append(scale_group)
#°# group.append(min_label)
#_# group.append(center_label)
#°# group.append(max_label)
#°# group.append(o_label)
#°# group.append(x_label)
# Add the SuperGroup to the Display
board.DISPLAY.show(group)
min_t = 20 # Initial minimum temperature range, before auto scale
max_t = 37 # Initial maximum temperature range, before auto scale
i2c = busio.I2C(board.SCL, board.SDA, frequency=800000)
mlx = adafruit_mlx90640.MLX90640(i2c)
print("MLX addr detected on I2C")
#mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_2_HZ
#Time for data aquisition: 1.36 s
#ulab processing: 0.05 s
#Copy from uLab to bitmap: 0.00 s
mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_4_HZ
frame = [0] * 768
####npframe = ulab.zeros(768)
gc.collect()
mid_mem = gc.mem_free()
print("Memory now:", mid_mem - start_mem)
# Allocate once, not in the loop.
a = ulab.zeros((2*24-1, 2*32-1)) # the upscaled image
while True:
#for i in range(10):
stamp = time.monotonic()
try:
mlx.getFrame(frame)
except ValueError: # these happen, no biggie - retry
continue
print("Time for data aquisition: %0.2f s" % (time.monotonic()-stamp))
stamp = time.monotonic()
# Convert in ulab.array, find the min and max and normalize to int from 0 to last_color.
npframe=ulab.array(frame)
min_t=ulab.numerical.min(npframe)
max_t=ulab.numerical.max(npframe)
b = npframe.reshape((24,32))
# It is possible to build a 24x32 array with ulab too:
## b = inta.reshape((24,32))
# a = ulab.zeros((2*24-2, 2*32-2)) # the upscaled image
#/# a = ulab.zeros((2*32-1, 2*24-1)) # the upscaled image
# Allocate once, not in the loop.
#^# a = ulab.zeros((2*24-1, 2*32-1)) # the upscaled image
# Some old magic by @v923z
# a[::2,::2] = b
# a[1::2,::2] = 0.5 * (b[:-1,:] + b[1:, :])
# a[::2,1::2] = 0.5 * (b[:,1:] + b[:,:-1])
# a[1::2,1::2] = 0.25 * (b[:-1,1:] + b[1:,:-1] + b[1:,1:] + b[:-1,:-1])
a[::2,::2] = b
if False: # Readable arithmetic => more memory, max: n=65
a[1::2,::2] = 0.5 * (b[:-1,:] + b[1:, :])
a[::,1::2]=0.5 * (a[::,:-1:2] + a[::,2::2])
else: # Splitting into simple operation => less memory max: n=87
a[1::2,::2] = b[:-1,:]
a[1::2,::2] += b[1:, :]
a[1::2,::2] /= 2
a[::,1::2]=a[::,:-1:2]
a[::,1::2]+=a[::,2::2]
a[::,1::2]/=2
factor=last_color/(max_t-min_t)
inta=ulab.array((a-min_t)*factor,dtype=ulab.int8)
#°# minx = 0
#°# miny = 0
#°# maxx = 23
#°# maxy = 31
print("ulab processing: %0.2f s" % (time.monotonic()-stamp))
stamp_copy = time.monotonic()
bitmaptools.arrayblit(image_bitmap, inta)
print("Copy from uLab to bitmap: %0.2f s" % (time.monotonic()-stamp_copy))
# min and max temperature indicator
#°# min_label.text="%0.2f" % (min_t)
#°# max_label.text="%0.2f" % (max_t)
# Compute average_center temperature of the middle of sensor and convert to palette color
#_# center_average = (frame[11*32 + 15] + frame[12*32 + 15] + frame[11*32 + 16] + frame[12*32 + 16]) / 4
#_# center_color = int(map_range(center_average, min_t, max_t, 0, last_color ))
#_# center_label.text = "%0.2f" % (center_average)
#_# center_label.color = palette[center_color]
# Set the location of X for lowest temperature
#°# x_label.x = maxx * total_factor
#°# x_label.y = (23-maxy) * total_factor
# Set the location of O for highest temperature
#°# o_label.x = minx * total_factor
#°# o_label.y = (23-miny) * total_factor
# print("Total time for aquisition and display %0.2f s" % (time.monotonic()-stamp))
stop_time = time.monotonic_ns()
gc.collect()
stop_mem = gc.mem_free()
print(stop_time - start_time, stop_mem - start_mem)
@dglaude
Copy link
Author

dglaude commented May 10, 2021

I am merging all of my thermal camera with interpolation into a single source file: https://gist.github.com/dglaude/cdd4ede9e43fe620637a2199e05ba8cb

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