When the PyGamer's power is turned on, the code.py module first imports all the required libraries. That includes the thermalcamera_converters and index_to_rgb helper files that we'll review later.

import time
import gc
import board
import keypad
import busio
from ulab import numpy as np
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 index_to_rgb.iron import index_to_rgb
from thermalcamera_converters import celsius_to_fahrenheit, fahrenheit_to_celsius
from thermalcamera_config import ALARM_F, MIN_RANGE_F, MAX_RANGE_F, SELFIE

After importing libraries, the display and default font are instantiated, the speaker is enabled, and the on-board NeoPixels are defined.

If the PyGamer's joystick is present, the HAS_JOYSTICK flag is set to True. If not, then the host device is probably a PyBadge or EdgeBadge. This allows the code to work for those devices in addition to the PyGamer, interpreting the Badge D-Pad buttons like the Gamer's joystick.

# Instantiate the integral display and define its size
display = board.DISPLAY
display.brightness = 1.0
WIDTH = display.width
HEIGHT = display.height

# Load the text font from the fonts folder
font_0 = bitmap_font.load_font("/fonts/OpenSans-9.bdf")

# Instantiate the joystick if available
if hasattr(board, "JOYSTICK_X"):
    # PyGamer with joystick
    joystick_x = AnalogIn(board.JOYSTICK_X)
    joystick_y = AnalogIn(board.JOYSTICK_Y)
    # PyBadge with buttons
    HAS_JOYSTICK = False  # PyBadge with buttons

# Enable the speaker

# Instantiate and clear the NeoPixels
pixels = neopixel.NeoPixel(board.NEOPIXEL, 5, pixel_order=neopixel.GRB)
pixels.brightness = 0.25

The PyGamer and PyBadge control buttons are connected to a hardware shift register chip that is controlled by the keypad.ShiftRegisterKeys class. This section of the code defines each button's bit position within the shift register. The local keypad.ShiftRegisterKeys class, panel, will be used to read the buttons in the primary process loop and setup helper.

# Initialize ShiftRegisterKeys to read PyGamer/PyBadge buttons
panel = keypad.ShiftRegisterKeys(

# Define front panel button event values
BUTTON_LEFT = 7  # LEFT button
BUTTON_UP = 6  # UP button
BUTTON_DOWN = 5  # DOWN button
BUTTON_RIGHT = 4  # RIGHT button
BUTTON_SET = 2  # START button
BUTTON_HOLD = 1  # button A
BUTTON_IMAGE = 0  # button B

Now it's time to connect to and instantiate the AMG8833 thermal camera FeatherWing using the I2C bus connection. The I2C serial bus speed is increased from the default 100K to 400K bits per second to improve data acquisition speed and ultimately the display frame rate.

This section of the code will also work if the AMG8833 thermal camera STEMMA breakout is used in place of the FeatherWing version. STEMMA cable length may impact sensor performance, so if you encounter issues with the breakout version, try reducing the I2C bus speed to the default 100K bits per second rate.

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

Next, the welcome graphics screen, thermalcamera_splash.bmp is displayed. The size of the image is scaled to fit the size of the PyGamer's display.

# Display splash graphics
splash = displayio.Group(scale=display.width // 160)
bitmap = displayio.OnDiskBitmap("/thermalcamera_splash.bmp")
splash.append(displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader))
board.DISPLAY.root_group = splash

Finally, the ulab (micro lab) arrays needed to hold the normalized 8x8 sensor index and the transformed 15x15 display grid index are defined. A sample color spectrum is placed in the display grid index array. In addition, an array to hold histogram statistical data is established.

An array defined for ulab use is an narray type; a format different than other CircuitPython arrays. The special narray array type (named after a close cousin, the numpy array) is designed to support rapid array calculation and transformation.

PALETTE_SIZE is used to select the maximum number of display colors across the iron spectrum to map to temperature values. The palette size of 100 colors was selected empirically as a value that balanced the sensor resolution of 0.5°C with the ability to visually discern objects. Increasing the number of colors beyond 160 does not improve readability and can slow the display frame rate. Fewer than 80 palette colors significantly decreases visual object detection.

# Thermal sensor grid axis size; AMG8833 sensor is 8x8

# Display grid parameters
GRID_AXIS = (2 * SENSOR_AXIS) - 1  # Number of cells per axis
GRID_SIZE = HEIGHT  # Axis size (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 display colors in spectral palette (must be > 0)

# Set up the 2-D sensor data narray
SENSOR_DATA = np.array(range(SENSOR_AXIS**2)).reshape((SENSOR_AXIS, SENSOR_AXIS))
# Set up and load the 2-D display color index narray with a spectrum
GRID_DATA = np.array(range(GRID_AXIS**2)).reshape((GRID_AXIS, GRID_AXIS)) / (
# Set up the histogram accumulation narray

A series of numerical constants are needed to set boundaries and limits for thermal camera calculations. We'll talk about those next.

This guide was first published on Jun 09, 2021. It was last updated on Nov 29, 2023.

This page (Import and Initialize) was last updated on Nov 13, 2023.

Text editor powered by tinymce.