PyGamer Thermal Camera Source Code

Download the project's source files and copy them to the PyGamer's CIRCUITPY root directory, including the fonts and index_to_rgb folders.

In the code window below, click the link Download Project Bundle. This will download a zip file containing the code (4 .py files), the index_to_rgb folder, needed library files and the font folder.

temperature___humidity_TC_v2_circuitpy_directory.png
The PyGamer's CIRCUITPY drive contents.

The zip folder contains the following folders and files:

  • fonts folder
    • OpenSans-9.bdf  font file
  • index_to_rgb folder
    • iron_spectrum.py color converter method file
  • code.py  main thermal camera code
  • thermal_cam_config.py  start-up default settings
  • thermal_cam_converters.py  temperature converter helpers
  • thermal_cam_splash.bmp  startup screen graphic
  • A lib folder containing these required libraries:

    • adafruit_amg88xx
    • adafruit_bitmap_font
    • adafruit_display_text
    • adafruit_display_shapes
    • adafruit_register
    • neopixel

Here's the main CircuitPython code for the Thermal Camera. It's contained in the project zip folder as code.py. Copy this to the main (root) folder of the CIRCUITPY drive that appears when your PyGamer is connected to your computer via a known good USB cable.

# SPDX-FileCopyrightText: 2021 Jan Goolsbey for Adafruit Industries
# SPDX-License-Identifier: MIT

# Thermal_Cam_v60_PyBadge_code.py
# 2021-06-07 v6.0

import time
import board
import busio
import ulab
import displayio
import neopixel
from analogio import AnalogIn
from digitalio import DigitalInOut
from simpleio import map_range, tone
from adafruit_display_text.label import Label
from adafruit_bitmap_font import bitmap_font
from adafruit_display_shapes.rect import Rect
import adafruit_amg88xx
from gamepadshift import GamePadShift
from index_to_rgb.iron_spectrum import index_to_rgb
from thermal_cam_converters import celsius_to_fahrenheit, fahrenheit_to_celsius
from thermal_cam_config import ALARM_F, MIN_RANGE_F, MAX_RANGE_F, SELFIE

# Instantiate display, joystick, speaker, and neopixels
display = board.DISPLAY
# Load the text font from the fonts folder
font_0 = bitmap_font.load_font("/fonts/OpenSans-9.bdf")

if hasattr(board, "JOYSTICK_X"):
    has_joystick = True  # PyGamer with joystick
    joystick_x = AnalogIn(board.JOYSTICK_X)
    joystick_y = AnalogIn(board.JOYSTICK_Y)
else:
    has_joystick = False  # PyBadge with buttons

speaker_enable = DigitalInOut(board.SPEAKER_ENABLE)
speaker_enable.switch_to_output(value=True)

pixels = neopixel.NeoPixel(board.NEOPIXEL, 5, pixel_order=neopixel.GRB)
pixels.brightness = 0.25  # Set NeoPixel brightness
pixels.fill(0x000000)  # Clear all NeoPixels

# Define and instantiate front panel buttons
BUTTON_LEFT = 0b10000000
BUTTON_UP = 0b01000000
BUTTON_DOWN = 0b00100000
BUTTON_RIGHT = 0b00010000
BUTTON_SELECT = 0b00001000
BUTTON_START = 0b00000100
BUTTON_A = 0b00000010
BUTTON_B = 0b00000001

panel = GamePadShift(
    DigitalInOut(board.BUTTON_CLOCK),
    DigitalInOut(board.BUTTON_OUT),
    DigitalInOut(board.BUTTON_LATCH),
)

# Establish I2C interface for the AMG8833 Thermal Camera
i2c = busio.I2C(board.SCL, board.SDA, frequency=400000)
amg8833 = adafruit_amg88xx.AMG88XX(i2c)

# CircuitPython 6 & 7 compatible
# Display splash graphics
with open("/thermal_cam_splash.bmp", "rb") as bitmap_file:
    bitmap = displayio.OnDiskBitmap(bitmap_file)
    splash = displayio.Group(scale=display.width // 160)
    splash.append(displayio.TileGrid(bitmap, pixel_shader=getattr(bitmap, 'pixel_shader', displayio.ColorConverter())))
    display.show(splash)
    time.sleep(0.1)  # Give the splash graphic some time to display

# # CircuitPython 7+ compatible
# Display splash graphics
# splash = displayio.Group(scale=display.width // 160)
# bitmap = displayio.OnDiskBitmap("/thermal_cam_splash.bmp")
# splash.append(displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader))
# board.DISPLAY.show(splash)
# time.sleep(0.1)  # Allow the splash to display

# Set up ulab arrays
n = 8  # Thermal sensor grid axis size; AMG8833 sensor is 8x8
sensor_data = ulab.array(range(n * n)).reshape((n, n))  # Color index narray
grid_data = ulab.zeros(((2 * n) - 1, (2 * n) - 1))  # 15x15 color index narray
histogram = ulab.zeros((2 * n) - 1)  # Histogram accumulation narray

# Convert default alarm and min/max range values from config file
ALARM_C = fahrenheit_to_celsius(ALARM_F)
MIN_RANGE_C = fahrenheit_to_celsius(MIN_RANGE_F)
MAX_RANGE_C = fahrenheit_to_celsius(MAX_RANGE_F)

# The board's integral display size
WIDTH = display.width
HEIGHT = display.height

GRID_AXIS = (2 * n) - 1  # Number of cells along the grid x or y axis
GRID_SIZE = HEIGHT  # Maximum number of pixels for a square grid
GRID_X_OFFSET = WIDTH - GRID_SIZE  # Right-align grid with display boundary
CELL_SIZE = GRID_SIZE // GRID_AXIS  # Size of a grid cell in pixels

PALETTE_SIZE = 100  # Number of colors in spectral palette (must be > 0)

# Default colors for temperature value sidebar
BLACK = 0x000000
RED = 0xFF0000
YELLOW = 0xFFFF00
CYAN = 0x00FFFF
BLUE = 0x0000FF
WHITE = 0xFFFFFF

# Text colors for setup helper's on-screen parameters
param_colors = [("ALARM", WHITE), ("RANGE", RED), ("RANGE", CYAN)]

# ### Helpers ###
def play_tone(freq=440, duration=0.01):
    tone(board.A0, freq, duration)
    return


def flash_status(text="", duration=0.05):  # Flash status message once
    status_label.color = WHITE
    status_label.text = text
    time.sleep(duration)
    status_label.color = BLACK
    time.sleep(duration)
    status_label.text = ""
    return


def spectrum():  # Load a test spectrum into the grid_data array
    for row in range(0, GRID_AXIS):
        for col in range(0, GRID_AXIS):
            grid_data[row][col] = ((row * GRID_AXIS) + col) * 1 / 235
    return


def update_image_frame(selfie=False):  # Get camera data and update display
    for row in range(0, GRID_AXIS):
        for col in range(0, GRID_AXIS):
            if selfie:
                color_index = grid_data[GRID_AXIS - 1 - row][col]
            else:
                color_index = grid_data[GRID_AXIS - 1 - row][GRID_AXIS - 1 - col]
            color = index_to_rgb(round(color_index * PALETTE_SIZE, 0) / PALETTE_SIZE)
            if color != image_group[((row * GRID_AXIS) + col)].fill:
                image_group[((row * GRID_AXIS) + col)].fill = color
    return


def update_histo_frame():  # Calculate and display histogram
    min_histo.text = str(MIN_RANGE_F)  # Display histogram legend
    max_histo.text = str(MAX_RANGE_F)

    histogram = ulab.zeros(GRID_AXIS)  # Clear histogram accumulation array
    for row in range(0, GRID_AXIS):  # Collect camera data and calculate histo
        for col in range(0, GRID_AXIS):
            histo_index = int(map_range(grid_data[col, row], 0, 1, 0, GRID_AXIS - 1))
            histogram[histo_index] = histogram[histo_index] + 1

    histo_scale = ulab.numerical.max(histogram) / (GRID_AXIS - 1)
    if histo_scale <= 0:
        histo_scale = 1

    for col in range(0, GRID_AXIS):  # Display histogram
        for row in range(0, GRID_AXIS):
            if histogram[col] / histo_scale > GRID_AXIS - 1 - row:
                image_group[((row * GRID_AXIS) + col)].fill = index_to_rgb(
                    round((col / GRID_AXIS), 3)
                )
            else:
                image_group[((row * GRID_AXIS) + col)].fill = BLACK
    return


def ulab_bilinear_interpolation():  # 2x bilinear interpolation
    # Upscale sensor data array; by @v923z and @David.Glaude
    grid_data[1::2, ::2] = sensor_data[:-1, :]
    grid_data[1::2, ::2] += sensor_data[1:, :]
    grid_data[1::2, ::2] /= 2
    grid_data[::, 1::2] = grid_data[::, :-1:2]
    grid_data[::, 1::2] += grid_data[::, 2::2]
    grid_data[::, 1::2] /= 2
    return


def setup_mode():  # Set alarm threshold and minimum/maximum range values
    status_label.color = WHITE
    status_label.text = "-SET-"

    ave_label.color = BLACK  # Turn off average label and value display
    ave_value.color = BLACK

    max_value.text = str(MAX_RANGE_F)  # Display maximum range value
    min_value.text = str(MIN_RANGE_F)  # Display minimum range value

    time.sleep(0.8)  # Show SET status text before setting parameters
    status_label.text = ""  # Clear status text

    param_index = 0  # Reset index of parameter to set

    # Select parameter to set

    buttons = panel.get_pressed()
    while not buttons & BUTTON_START:
        buttons = panel.get_pressed()
        while (not buttons & BUTTON_A) and (not buttons & BUTTON_START):
            up, down = move_buttons(joystick=has_joystick)
            if up:
                param_index = param_index - 1
            if down:
                param_index = param_index + 1
            param_index = max(0, min(2, param_index))
            status_label.text = param_colors[param_index][0]
            image_group[param_index + 226].color = BLACK
            status_label.color = BLACK
            time.sleep(0.25)
            image_group[param_index + 226].color = param_colors[param_index][1]
            status_label.color = WHITE
            time.sleep(0.25)
            buttons = panel.get_pressed()

        buttons = panel.get_pressed()
        if buttons & BUTTON_A:  # Hold (button A) pressed
            play_tone(1319, 0.030)  # E6
        while buttons & BUTTON_A:  # Wait for button release
            buttons = panel.get_pressed()
            time.sleep(0.1)

        # Adjust parameter value
        param_value = int(image_group[param_index + 230].text)
        buttons = panel.get_pressed()
        while (not buttons & BUTTON_A) and (not buttons & BUTTON_START):
            up, down = move_buttons(joystick=has_joystick)
            if up:
                param_value = param_value + 1
            if down:
                param_value = param_value - 1
            param_value = max(32, min(157, param_value))
            image_group[param_index + 230].text = str(param_value)
            image_group[param_index + 230].color = BLACK
            status_label.color = BLACK
            time.sleep(0.05)
            image_group[param_index + 230].color = param_colors[param_index][1]
            status_label.color = WHITE
            time.sleep(0.2)
            buttons = panel.get_pressed()

        buttons = panel.get_pressed()
        if buttons & BUTTON_A:  # Button A pressed
            play_tone(1319, 0.030)  # E6
        while buttons & BUTTON_A:  # Wait for button release
            buttons = panel.get_pressed()
            time.sleep(0.1)

    # Exit setup process
    buttons = panel.get_pressed()
    if buttons & BUTTON_START:  # Start button pressed
        play_tone(784, 0.030)  # G5
    while buttons & BUTTON_START:  # Wait for button release
        buttons = panel.get_pressed()
        time.sleep(0.1)

    status_label.text = "RESUME"
    time.sleep(0.5)
    status_label.text = ""

    # Display average label and value
    ave_label.color = YELLOW
    ave_value.color = YELLOW
    return int(alarm_value.text), int(max_value.text), int(min_value.text)


def move_buttons(joystick=False):  # Read position buttons and joystick
    move_u = move_d = False
    if joystick:  # For PyGamer: interpret joystick as buttons
        if joystick_y.value < 20000:
            move_u = True
        elif joystick_y.value > 44000:
            move_d = True
    else:  # For PyBadge read the buttons
        buttons = panel.get_pressed()
        if buttons & BUTTON_UP:
            move_u = True
        if buttons & BUTTON_DOWN:
            move_d = True
    return move_u, move_d


play_tone(440, 0.1)  # A4
play_tone(880, 0.1)  # A5

# ### Define the display group ###
t0 = time.monotonic()  # Time marker: Define Display Elements
image_group = displayio.Group(scale=1)

# Define the foundational thermal image grid cells; image_group[0:224]
#   image_group[#] = image_group[ (row * GRID_AXIS) + column ]
for row in range(0, GRID_AXIS):
    for col in range(0, GRID_AXIS):
        cell_x = (col * CELL_SIZE) + GRID_X_OFFSET
        cell_y = row * CELL_SIZE
        cell = Rect(
            x=cell_x,
            y=cell_y,
            width=CELL_SIZE,
            height=CELL_SIZE,
            fill=None,
            outline=None,
            stroke=0,
        )
        image_group.append(cell)

# Define labels and values
status_label = Label(font_0, text="", color=None)
status_label.anchor_point = (0.5, 0.5)
status_label.anchored_position = ((WIDTH // 2) + (GRID_X_OFFSET // 2), HEIGHT // 2)
image_group.append(status_label)  # image_group[225]

alarm_label = Label(font_0, text="alm", color=WHITE)
alarm_label.anchor_point = (0, 0)
alarm_label.anchored_position = (1, 16)
image_group.append(alarm_label)  # image_group[226]

max_label = Label(font_0, text="max", color=RED)
max_label.anchor_point = (0, 0)
max_label.anchored_position = (1, 46)
image_group.append(max_label)  # image_group[227]

min_label = Label(font_0, text="min", color=CYAN)
min_label.anchor_point = (0, 0)
min_label.anchored_position = (1, 106)
image_group.append(min_label)  # image_group[228]

ave_label = Label(font_0, text="ave", color=YELLOW)
ave_label.anchor_point = (0, 0)
ave_label.anchored_position = (1, 76)
image_group.append(ave_label)  # image_group[229]

alarm_value = Label(font_0, text=str(ALARM_F), color=WHITE)
alarm_value.anchor_point = (0, 0)
alarm_value.anchored_position = (1, 5)
image_group.append(alarm_value)  # image_group[230]

max_value = Label(font_0, text=str(MAX_RANGE_F), color=RED)
max_value.anchor_point = (0, 0)
max_value.anchored_position = (1, 35)
image_group.append(max_value)  # image_group[231]

min_value = Label(font_0, text=str(MIN_RANGE_F), color=CYAN)
min_value.anchor_point = (0, 0)
min_value.anchored_position = (1, 95)
image_group.append(min_value)  # image_group[232]

ave_value = Label(font_0, text="---", color=YELLOW)
ave_value.anchor_point = (0, 0)
ave_value.anchored_position = (1, 65)
image_group.append(ave_value)  # image_group[233]

min_histo = Label(font_0, text="", color=None)
min_histo.anchor_point = (0, 0.5)
min_histo.anchored_position = (GRID_X_OFFSET, 121)
image_group.append(min_histo)  # image_group[234]

max_histo = Label(font_0, text="", color=None)
max_histo.anchor_point = (1, 0.5)
max_histo.anchored_position = (WIDTH - 2, 121)
image_group.append(max_histo)  # image_group[235]

range_histo = Label(font_0, text="-RANGE-", color=None)
range_histo.anchor_point = (0.5, 0.5)
range_histo.anchored_position = ((WIDTH // 2) + (GRID_X_OFFSET // 2), 121)
image_group.append(range_histo)  # image_group[236]

# ###--- PRIMARY PROCESS SETUP ---###
t1 = time.monotonic()  # Time marker: Primary Process Setup
display_image = True  # Image display mode; False for histogram
display_hold = False  # Active display mode; True to hold display
display_focus = False  # Standard display range; True to focus display range
orig_max_range_f = 0  # Establish temporary range variables
orig_min_range_f = 0

# Activate display and play welcome tone
display.show(image_group)
spectrum()
update_image_frame()
flash_status("IRON", 0.75)
play_tone(880, 0.010)  # A5

# ###--- PRIMARY PROCESS LOOP ---###
while True:
    t2 = time.monotonic()  # Time marker: Acquire Sensor Data
    if display_hold:
        flash_status("-HOLD-", 0.25)
    else:
        sensor = amg8833.pixels  # Get sensor_data data
    sensor_data = ulab.array(sensor)  # Copy to narray

    t3 = time.monotonic()  # Time marker: Constrain Sensor Values
    for row in range(0, 8):
        for col in range(0, 8):
            sensor_data[col, row] = min(max(sensor_data[col, row], 0), 80)

    # Update and display alarm setting and max, min, and ave stats
    t4 = time.monotonic()  # Time marker: Display Statistics
    v_max = ulab.numerical.max(sensor_data)
    v_min = ulab.numerical.min(sensor_data)
    v_ave = ulab.numerical.mean(sensor_data)

    alarm_value.text = str(ALARM_F)
    max_value.text = str(celsius_to_fahrenheit(v_max))
    min_value.text = str(celsius_to_fahrenheit(v_min))
    ave_value.text = str(celsius_to_fahrenheit(v_ave))

    # Normalize temperature to index values and interpolate
    t5 = time.monotonic()  # Time marker: Normalize and Interpolate
    sensor_data = (sensor_data - MIN_RANGE_C) / (MAX_RANGE_C - MIN_RANGE_C)
    grid_data[::2, ::2] = sensor_data  # Copy sensor data to the grid array
    ulab_bilinear_interpolation()  # Interpolate to produce 15x15 result

    # Display image or histogram
    t6 = time.monotonic()  # Time marker: Display Image
    if display_image:
        update_image_frame(selfie=SELFIE)
    else:
        update_histo_frame()

    # If alarm threshold is reached, flash NeoPixels and play alarm tone
    if v_max >= ALARM_C:
        pixels.fill(RED)
        play_tone(880, 0.015)  # A5
        pixels.fill(BLACK)

    # See if a panel button is pressed
    buttons = panel.get_pressed()
    if buttons & BUTTON_A:  # Toggle display hold (shutter)
        play_tone(1319, 0.030)  # E6
        display_hold = not display_hold

        while buttons & BUTTON_A:
            buttons = panel.get_pressed()
            time.sleep(0.1)

    if buttons & BUTTON_B:  # Toggle image/histogram mode (display image)
        play_tone(659, 0.030)  # E5
        display_image = not display_image
        while buttons & BUTTON_B:
            buttons = panel.get_pressed()
            time.sleep(0.1)

        if display_image:
            min_histo.color = None
            max_histo.color = None
            range_histo.color = None
        else:
            min_histo.color = CYAN
            max_histo.color = RED
            range_histo.color = BLUE

    if buttons & BUTTON_SELECT:  # Toggle focus mode (display focus)
        play_tone(698, 0.030)  # F5
        display_focus = not display_focus
        if display_focus:
            # Set range values to image min/max for focused image display
            orig_min_range_f = MIN_RANGE_F
            orig_max_range_f = MAX_RANGE_F
            MIN_RANGE_F = celsius_to_fahrenheit(v_min)
            MAX_RANGE_F = celsius_to_fahrenheit(v_max)
            # Update range min and max values in Celsius
            MIN_RANGE_C = v_min
            MAX_RANGE_C = v_max
            flash_status("FOCUS", 0.2)
        else:
            # Restore previous (original) range values for image display
            MIN_RANGE_F = orig_min_range_f
            MAX_RANGE_F = orig_max_range_f
            # Update range min and max values in Celsius
            MIN_RANGE_C = fahrenheit_to_celsius(MIN_RANGE_F)
            MAX_RANGE_C = fahrenheit_to_celsius(MAX_RANGE_F)
            flash_status("ORIG", 0.2)

        while buttons & BUTTON_SELECT:
            buttons = panel.get_pressed()
            time.sleep(0.1)

    if buttons & BUTTON_START:  # Activate setup mode
        play_tone(784, 0.030)  # G5
        while buttons & BUTTON_START:
            buttons = panel.get_pressed()
            time.sleep(0.1)

        # Invoke startup helper; update alarm and range values
        ALARM_F, MAX_RANGE_F, MIN_RANGE_F = setup_mode()
        ALARM_C = fahrenheit_to_celsius(ALARM_F)
        MIN_RANGE_C = fahrenheit_to_celsius(MIN_RANGE_F)
        MAX_RANGE_C = fahrenheit_to_celsius(MAX_RANGE_F)

    t7 = time.monotonic()  # Time marker: End of Primary Process
    print("*** PyBadge/Gamer Performance Stats ***")
    print(f"    define displayio: {(t1 - t0):6.3f}")
    print("")
    print(f" 1) data acquisition: {(t4 - t2):6.3f}    rate: {(1 / (t4 - t2)):5.1f}")
    print(f" 2) display stats:    {(t5 - t4):6.3f}")
    print(f" 3) interpolate:      {(t6 - t5):6.3f}")
    print(f" 4) display image:    {(t7 - t6):6.3f}")
    print(f"                     =======")
    print(f"total frame:          {(t7 - t2):6.3f}    rate: {(1 / (t7 - t2)):5.1f}")
    print("")

The Thermal Camera needs some helpers to convert back and forth between Celsius and Fahrenheit units. This file is contained in the project zip folder as thermal_cam_converters.py.

# thermal_cam_converters.py

def celsius_to_fahrenheit(deg_c=None):  # convert C to F; round to 1 degree C
    return round(((9 / 5) * deg_c) + 32)

def fahrenheit_to_celsius(deg_f=None):  # convert F to C; round to 1 degree F
    return round((deg_f - 32) * (5 / 9))

The color spectrum is calculated by the index_to_rgb helper of the iron_spectrum.py file within the index_to_rgb folder. The helper calculates a 24-bit red, green, and blue (RGB) color value from an input value of 0 to 1.0.

# iron_spectrum.py
# 2021-05-27 version 1.2
# Copyright 2021 Cedar Grove Studios
# Temperature Index to Iron Pseudocolor Spectrum RGB Converter Helper

def map_range(x, in_min, in_max, out_min, out_max):
    """
    Maps and constrains an input value from one range of values to another.
    (from adafruit_simpleio)
    :return: Returns value mapped to new range
    :rtype: float
    """
    in_range = in_max - in_min
    in_delta = x - in_min
    if in_range != 0:
        mapped = in_delta / in_range
    elif in_delta != 0:
        mapped = in_delta
    else:
        mapped = 0.5
    mapped *= out_max - out_min
    mapped += out_min
    if out_min <= out_max:
        return max(min(mapped, out_max), out_min)
    return min(max(mapped, out_max), out_min)

def index_to_rgb(index=0, gamma=0.5):
    """
    Converts a temperature index to an iron thermographic pseudocolor spectrum
    RGB value. Temperature index in range of 0.0 to 1.0. Gamma in range of
    0.0 to 1.0 (1.0=linear), default 0.5 for color TFT displays.
    :return: Returns a 24-bit RGB value
    :rtype: integer
    """

    band = index * 600  # an arbitrary spectrum band index; 0 to 600

    if band < 70:  # dark gray to blue
        red = 0.1
        grn = 0.1
        blu = (0.2 + (0.8 * map_range(band, 0, 70, 0.0, 1.0))) ** gamma
    if band >= 70 and band < 200:  # blue to violet
        red = map_range(band, 70, 200, 0.0, 0.6) ** gamma
        grn = 0.0
        blu = 1.0 ** gamma
    if band >= 200 and band < 300:  # violet to red
        red = map_range(band, 200, 300, 0.6, 1.0) ** gamma
        grn = 0.0
        blu = map_range(band, 200, 300, 1.0, 0.0) ** gamma
    if band >= 300 and band < 400:   # red to orange
        red = 1.0 ** gamma
        grn = map_range(band, 300, 400, 0.0, 0.5) ** gamma
        blu = 0.0
    if band >= 400 and band < 500:  # orange to yellow
        red = 1.0 ** gamma
        grn = map_range(band, 400, 500, 0.5, 1.0) ** gamma
        blu = 0.0
    if band >= 500:  # yellow to white
        red = 1.0 ** gamma
        grn = 1.0 ** gamma
        blu = map_range(band, 500, 580, 0.0, 1.0) ** gamma

    return (int(red * 255) << 16) + (int(grn * 255) << 8) + int(blu * 255)

Finally, the power-up alarm threshold, temperature display range settings, and camera orientation are contained in the thermal_cam_config.py file. All values are in degrees Fahrenheit. A SELFIE value of True adjusts the image for a front-facing camera orientation; False is used for cameras facing away from the viewer.

# thermal_cam_config.py
# ### Alarm and range default values in Farenheit ###
ALARM_F     = 120
MIN_RANGE_F =  60
MAX_RANGE_F = 120

# ### Display characteristics
SELFIE = False  # Rear camera view; True for front view

After copying all the project files to the PyGamer, you'll see the camera's splash graphics and a sample of the iron color spectrum. After a couple of beeps, the thermal image will appear.

The next section shows the features of the camera and how it operates.

For this version of the thermal camera project, the ulab array code is compatible with versions imbedded in CircuitPython 6.3.0 or earlier. The project code will be updated to be compatible with the ulab changes in CircuitPython version 7.0.0 when released.

This guide was first published on Jun 09, 2021. It was last updated on 2021-06-09 17:11:13 -0400.

This page (CircuitPython Code) was last updated on Sep 02, 2021.

Text editor powered by tinymce.