Skip to content

Instantly share code, notes, and snippets.

@dglaude
Created May 10, 2021 13:39
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save dglaude/cdd4ede9e43fe620637a2199e05ba8cb to your computer and use it in GitHub Desktop.
Thermal camera mono-source multiple platform
### 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))
@dglaude
Copy link
Author

dglaude commented Jun 6, 2021

The ulab and displayio part are stuff like this:

import displayio
import ulab
...
npframe = ulab.zeros(768) # A buffer for giving to the mlx library
...
a = ulab.zeros((bitmap_x, bitmap_y)) # the storage for the upscaled image
...
intb=ulab.array((npframe-min_t)*factor,dtype=ulab.int16) # copying the data from the thermal cam in a ulab array
b = intb.reshape((24,32)) # making a matrix out of that
...
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])
...
inta=ulab.array((a),dtype=ulab.int8) # Conversion from int16 to int8
...
bitmaptools.arrayblit(image_bitmap, inta) # Copie from ulab array to bitmap

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