Created
May 10, 2021 13:39
-
-
Save dglaude/cdd4ede9e43fe620637a2199e05ba8cb to your computer and use it in GitHub Desktop.
Thermal camera mono-source multiple platform
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
### Thermal camera mono-source multiple platform: | |
### | |
### On a FeatherWing Keyboard: | |
### 'FeatherS2 with ESP32S2' | |
### 'Adafruit Feather RP2040 with rp2040' | |
### | |
### With build in screen: | |
### 'Adafruit CLUE nRF52840 Express with nRF52840' | |
### 'Adafruit Matrix Portal M4 with samd51j19' + 32x64 Matrix | |
### If a 64x64 matrix (or two time 32x64) then the following | |
### 32x24 (pixel doubling) => 64x48 | |
### 32x24 (upscale) => 63x47 | |
### | |
### Add some gamma_adjust to the colour for LED display | |
### | |
import time | |
import board | |
import digitalio | |
import displayio | |
import storage | |
import board | |
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 | |
#number_of_colors: must be define based on test and memory consumption. Typical value: 128, 64, 32. | |
#other_factor: must be define and is the bilineal interpolation value | |
#scale_factor: must be define and is the displayio scaling factor | |
machine=os.uname().machine | |
if machine in ("Adafruit Feather RP2040 with rp2040"): | |
tft_cs = board.D9 | |
tft_dc = board.D10 | |
touch_cs = board.D6 | |
sd_cs = board.D5 | |
neopix_pin = board.D11 | |
other_factor = 4; scale_factor = 2; number_of_colors = 128; readable_math = False; | |
elif machine in ('FeatherS2 with ESP32S2'): | |
tft_cs = board.D5 | |
tft_dc = board.D6 | |
touch_cs = board.D21 | |
sd_cs = board.D20 | |
neopix_pin = board.D9 | |
other_factor = 5; scale_factor = 2; number_of_colors = 128; readable_math = False; | |
elif machine in ('Adafruit Matrix Portal M4 with samd51j19'): | |
other_factor = 1; scale_factor = 1; number_of_colors = 128; readable_math = False; | |
elif machine in ('Adafruit CLUE nRF52840 Express with nRF52840'): | |
other_factor = 3; scale_factor = 2; number_of_colors = 64; readable_math = False; | |
elif machine in ('Adafruit PyPortal with samd51j20'): | |
other_factor = 4; scale_factor = 2; number_of_colors = 64; readable_math = False; | |
# other_factor = 5; scale_factor = 2; number_of_colors = 64; readable_math = False; # This fail on memory. | |
else: | |
print("Unknown machine:", machine) | |
print(42/0) | |
# Release any resources currently in use for the displays | |
if not hasattr(board,"DISPLAY"): | |
displayio.release_displays() | |
if machine in ('Adafruit Matrix Portal M4 with samd51j19'): | |
# the display | |
from adafruit_matrixportal.matrix import Matrix | |
matrix = Matrix(width=64, height=32, bit_depth=6) | |
display = matrix.display | |
display.rotation = 90 # matrixportal up | |
# display.rotation = 270 # matrixportal down | |
elif machine in ('Adafruit CLUE nRF52840 Express with nRF52840', 'Adafruit PyPortal with samd51j20'): | |
print("CLUE or PyPortal with build in display") | |
else: | |
import adafruit_ili9341 | |
spi = board.SPI() | |
display_bus = displayio.FourWire(spi, command=tft_dc, chip_select=tft_cs) | |
display = adafruit_ili9341.ILI9341(display_bus, width=320, height=240) | |
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 | |
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 ) | |
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): | |
color = fancy_palette[c] | |
if machine in ('Adafruit Matrix Portal M4 with samd51j19'): | |
color = fancy.gamma_adjust(color) # Adjust only for MatrixPortal | |
palette[c] = color.pack() | |
### Only saving 192 but a test in the use of del() | |
gc.collect() | |
aaa_mem = gc.mem_free() | |
del(fancy) | |
del(grad) | |
gc.collect() | |
bbb_mem = gc.mem_free() | |
print("Saved mem:", bbb_mem-aaa_mem) | |
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 | |
if machine in ('Adafruit CLUE nRF52840 Express with nRF52840', 'Adafruit PyPortal with samd51j20'): | |
board.DISPLAY.show(group) | |
else: | |
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 before a:", mid_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 | |
gc.collect(); mid_mem = gc.mem_free(); print("Memory after a:", mid_mem) | |
div_factor = 1 | |
while True: | |
gc.collect() | |
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) | |
factor=last_color/(max_t-min_t) | |
# Maybe this will lose a lot of precision as it goes in int16 | |
# But that save twice the memory of working with floating point | |
intb=ulab.array((npframe-min_t)*factor,dtype=ulab.int16) | |
b = intb.reshape((24,32)) | |
gc.collect(); x_mem = gc.mem_free(); | |
del(intb) | |
gc.collect(); y_mem = gc.mem_free(); print("del(intb)", x_mem, y_mem, (y_mem-x_mem)) | |
if other_factor == 1: | |
a = b | |
elif 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]) | |
if readable_math: # Readable arithmetic => more memory, max: n=65 | |
print("x2 factor readable") | |
a[::2,::2] = b | |
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 | |
print("x2 factor memory") | |
a[::2,::2] = b | |
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 | |
div_factor = 2 | |
elif other_factor == 3: | |
if readable_math: # Readable arithmetic => more memory | |
print("x3 factor readable") | |
a[::3,::3] = b | |
a[1::3,::3] = b[:-1,:] * (2/3) + b[1:,:] * (1/3) | |
a[2::3,::3] = b[:-1,:] * (1/3) + b[1:,:] * (2/3) | |
a[::,1::3] = a[::,:-1:3] * (2/3) + a[::,3::3] * (1/3) | |
a[::,2::3] = a[::,:-1:3] * (1/3) + a[::,3::3] * (2/3) | |
else: # Splitting into simple operation => less memory max: n=87 | |
print("x3 factor memory") | |
a[::3,::3] = b | |
a[1::3,::3] = b[:-1,:] | |
a[1::3,::3] *= 2 | |
a[1::3,::3] += b[1:,:] | |
a[1::3,::3] /= 3 | |
a[2::3,::3] = b[1:,:] | |
a[2::3,::3] *= 2 | |
a[2::3,::3] += b[:-1,:] | |
a[2::3,::3] /= 3 | |
a[::,1::3] = a[::,:-1:3] | |
a[::,1::3] *= 2 | |
a[::,1::3] += a[::,3::3] | |
a[::,1::3] /= 3 | |
a[::,2::3] = a[::,3::3] | |
a[::,2::3] *= 2 | |
a[::,2::3] += a[::,:-1:3] | |
a[::,2::3] /= 3 | |
elif other_factor == 4: | |
if readable_math: # Readable arithmetic => more memory | |
print("x4 factor readable") | |
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) | |
else: # Splitting into simple operation => less memory max: n=87 | |
print("x4 factor memory") | |
a[::4,::4] = b | |
# a[2::4,::4] = b[:-1,:] * (2/4) + b[1:,:] * (2/4) | |
a[2::4,::4] = b[:-1,:] | |
a[2::4,::4] += b[1:,:] | |
a[2::4,::4] /= 2 | |
# a[1::4,::4] = b[:-1,:] * (3/4) + b[1:,:] * (1/4) | |
a[1::4,::4] = b[:-1,:] | |
a[1::4,::4] *= 3 | |
a[1::4,::4] += b[1:,:] | |
a[1::4,::4] /= 4 | |
# a[3::4,::4] = b[:-1,:] * (1/4) + b[1:,:] * (3/4) | |
a[3::4,::4] = b[1:,:] | |
a[3::4,::4] *= 3 | |
a[3::4,::4] += b[:-1,:] | |
a[3::4,::4] /= 4 | |
# a[::,2::4] = a[::,:-1:4] * (2/4) + a[::,4::4] * (2/4) | |
a[::,2::4] = a[::,:-1:4] | |
a[::,2::4] += a[::,4::4] | |
a[::,2::4] /= 2 | |
# a[::,1::4] = a[::,:-1:4] * (3/4) + a[::,4::4] * (1/4) | |
a[::,1::4] = a[::,:-1:4] | |
a[::,1::4] *= 3 | |
a[::,1::4] += a[::,4::4] | |
a[::,1::4] /= 4 | |
# a[::,3::4] = a[::,:-1:4] * (1/4) + a[::,4::4] * (3/4) | |
a[::,3::4] = a[::,4::4] | |
a[::,3::4] *= 3 | |
a[::,3::4] += a[::,:-1:4] | |
a[::,3::4] /= 4 | |
elif other_factor == 5: | |
gc.collect(); mid_mem = gc.mem_free(); print("Memory before x5:", mid_mem) | |
if readable_math: # Readable arithmetic => more memory | |
print("x5 factor readable") | |
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: # Splitting into simple operation => less memory max: n=87 | |
print("x5 factor memory") | |
p1 = b[:-1,:] | |
p2 = b[1:,:] | |
a[::5,::5] = b | |
# a[1::5,::5] = b[:-1,:] * (4/5) + b[1:,:] * (1/5) | |
a[1::5,::5] = p1 | |
a[1::5,::5] *= 4 | |
a[1::5,::5] += p2 | |
a[1::5,::5] /= 5 | |
# a[2::5,::5] = b[:-1,:] * (3/5) + b[1:,:] * (2/5) | |
a[2::5,::5] = p1 | |
a[2::5,::5] *= 3 | |
a[2::5,::5] += p2 | |
a[2::5,::5] += p2 | |
a[2::5,::5] /= 5 | |
# a[3::5,::5] = b[:-1,:] * (2/5) + b[1:,:] * (3/5) | |
a[3::5,::5] = p2 | |
a[3::5,::5] *= 3 | |
a[3::5,::5] += p1 | |
a[3::5,::5] += p1 | |
a[3::5,::5] /= 5 | |
# a[4::5,::5] = b[:-1,:] * (1/5) + b[1:,:] * (4/5) | |
a[4::5,::5] = b[1:,:] | |
a[4::5,::5] *= 4 | |
a[4::5,::5] += b[:-1,:] | |
a[4::5,::5] /= 5 | |
del(p1) | |
del(p2) | |
gc.collect(); mid_mem = gc.mem_free(); print("Memory mid x5:", mid_mem) | |
# | |
t1 = a[::,:-1:5] | |
t2 = a[::,5::5] | |
# a[::,1::5] = a[::,:-1:5] * (4/5) + a[::,5::5] * (1/5) | |
a[::,1::5] = t1 * 4 | |
a[::,1::5] += t2 | |
a[::,1::5] /= 5 | |
# a[::,2::5] = a[::,:-1:5] * (3/5) + a[::,5::5] * (2/5) | |
a[::,2::5] = t1 * 3 | |
a[::,2::5] += t2 | |
a[::,2::5] += t2 | |
a[::,2::5] /= 5 | |
# a[::,3::5] = a[::,:-1:5] * (2/5) + a[::,5::5] * (3/5) | |
a[::,3::5] = t2 | |
a[::,3::5] *= 3 | |
a[::,3::5] += t1 | |
a[::,3::5] += t1 | |
a[::,3::5] /= 5 | |
# a[::,4::5] = a[::,:-1:5] * (1/5) + a[::,5::5] * (4/5) | |
a[::,4::5] = t2 | |
a[::,4::5] *= 4 | |
a[::,4::5] += t1 | |
a[::,4::5] /= 5 | |
del(t1) | |
del(t2) | |
gc.collect(); mid_mem = gc.mem_free(); print("Memory end x5:", mid_mem) | |
elif other_factor == 6: | |
print("x6 factor") # ulab processing: 0.42 s | |
a[::6,::6] = b | |
a[1::6,::6] = b[:-1,:] * (5/6) + b[1:,:] * (1/6) | |
a[2::6,::6] = b[:-1,:] * (4/6) + b[1:,:] * (2/6) | |
a[3::6,::6] = b[:-1,:] * (3/6) + b[1:,:] * (3/6) | |
a[4::6,::6] = b[:-1,:] * (2/6) + b[1:,:] * (4/6) | |
a[5::6,::6] = b[:-1,:] * (1/6) + b[1:,:] * (5/6) | |
a[::,1::6] = a[::,:-1:6] * (5/6) + a[::,6::6] * (1/6) | |
a[::,2::6] = a[::,:-1:6] * (4/6) + a[::,6::6] * (2/6) | |
a[::,3::6] = a[::,:-1:6] * (3/6) + a[::,6::6] * (3/6) | |
a[::,4::6] = a[::,:-1:6] * (2/6) + a[::,6::6] * (4/6) | |
a[::,5::6] = a[::,:-1:6] * (1/6) + a[::,6::6] * (5/6) | |
else: | |
print("Unknown other_factor: ", other_factor) | |
print(42/0) | |
gc.collect(); mid_mem = gc.mem_free(); print("Memory now:", mid_mem) | |
# Going from int16 to int8 because that is what we need for bitmap conversion | |
inta=ulab.array((a),dtype=ulab.int8) | |
print("ulab processing: %0.2f s" % (time.monotonic()-stamp)) | |
print("Memory now:", gc.mem_free()) | |
stamp_copy = time.monotonic() | |
bitmaptools.arrayblit(image_bitmap, inta) | |
print("Copy from uLab to bitmap: %0.2f s" % (time.monotonic()-stamp_copy)) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The ulab and displayio part are stuff like this: