Last active
May 10, 2021 22:12
-
-
Save dglaude/fc1a2ccf49e681f6c4af75dced6eee08 to your computer and use it in GitHub Desktop.
WIP: Thermal Camera++ FeatherS2 + FeatherWing Keyboard + MLX90640 + ulab x5 bilinear scaling + x2 displayio scaling
This file contains 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
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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I am merging all of my thermal camera with interpolation into a single source file: https://gist.github.com/dglaude/cdd4ede9e43fe620637a2199e05ba8cb