Skip to content

Instantly share code, notes, and snippets.

@dglaude
Last active May 10, 2021
Embed
What would you like to do?
WIP: Thermal Camera++ FeatherS2 + FeatherWing Keyboard + MLX90640 + ulab x5 bilinear scaling + x2 displayio scaling
from bbq10keyboard import BBQ10Keyboard, STATE_PRESS, STATE_RELEASE, STATE_LONG_PRESS
from adafruit_stmpe610 import Adafruit_STMPE610_SPI
import adafruit_ili9341
import adafruit_sdcard
import digitalio
import displayio
import neopixel
import storage
import board
import time
import os
import busio
import adafruit_mlx90640
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
# Release any resources currently in use for the displays
displayio.release_displays()
spi = board.SPI()
# Feather S2 # M4
tft_cs = board.D5 # tft_cs = board.D9
tft_dc = board.D6 # tft_dc = board.D10
touch_cs = board.D21 # touch_cs = board.D6
sd_cs = board.D20 # sd_cs = board.D5
neopix_pin = board.D9 # neopix_pin = board.D11
display_bus = displayio.FourWire(spi, command=tft_dc, chip_select=tft_cs)
display = adafruit_ili9341.ILI9341(display_bus, width=320, height=240)
### mlx90640_scalex2_clue.py + 64
### same as mlx90640_scalex2_clue.py but with 64 colours rather than 32 => need one optimisation on memory size.
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 = 5
scale_factor = 2
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_x=other_factor*24+(1-other_factor)
bitmap_y=other_factor*32+(1-other_factor)
# Bitmap for colour coded thermal value (FIXME: Why x and y are inverted)
image_bitmap = displayio.Bitmap( bitmap_y, bitmap_x, 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 with displayio scale
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
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
#mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_8_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((other_factor*24+(1-other_factor), other_factor*32+(1-other_factor))) # the upscaled image
a = ulab.zeros((bitmap_x, bitmap_y)) # 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)
# min_t=min(0,ulab.numerical.min(npframe))
max_t=ulab.numerical.max(npframe)
b = npframe.reshape((24,32))
if other_factor == 2:
print("x2 factor")
# 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
elif other_factor == 4:
print("x4 factor")
a[::4,::4] = b
a[1::4,::4] = b[:-1,:] * (3/4) + b[1:,:] * (1/4)
a[2::4,::4] = b[:-1,:] * (2/4) + b[1:,:] * (2/4)
a[3::4,::4] = b[:-1,:] * (1/4) + b[1:,:] * (3/4)
a[::,1::4] = a[::,:-1:4] * (3/4) + a[::,4::4] * (1/4)
a[::,2::4] = a[::,:-1:4] * (2/4) + a[::,4::4] * (2/4)
a[::,3::4] = a[::,:-1:4] * (1/4) + a[::,4::4] * (3/4)
elif other_factor == 5:
print("x5 factor")
a[::5,::5] = b
a[1::5,::5] = b[:-1,:] * (4/5) + b[1:,:] * (1/5)
a[2::5,::5] = b[:-1,:] * (3/5) + b[1:,:] * (2/5)
a[3::5,::5] = b[:-1,:] * (2/5) + b[1:,:] * (3/5)
a[4::5,::5] = b[:-1,:] * (1/5) + b[1:,:] * (4/5)
a[::,1::5] = a[::,:-1:5] * (4/5) + a[::,5::5] * (1/5)
a[::,2::5] = a[::,:-1:5] * (3/5) + a[::,5::5] * (2/5)
a[::,3::5] = a[::,:-1:5] * (2/5) + a[::,5::5] * (3/5)
a[::,4::5] = a[::,:-1:5] * (1/5) + a[::,5::5] * (4/5)
else:
print("Unknown other_factor: ", other_factor)
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