Thermal camera display layout.

Define Display Group Layers

Within CircuitPython's displayio library, a display group is a list of label or graphic attributes that are defined for each object of the display. This section of the Thermal Camera's primary process module defines the image_groupdisplay group that the camera will use to show measured values, the sensor image or histogram, status message, and the histogram legend.

The camera's display group, image_group, consists of layered objects. The first 225 objects of the display make up the colored cells used for the image grid area.

The status message label comes next, followed by the values and labels in the display sidebar area. Finally, objects that make up the histogram legend top off the stack of display objects in image_group.

The objects and their attributes are appended to the image_group list one-at-a-time when first defined. When appended to image_group, the attributes of each object are defined. For example, the alarm label alm is defined as a Label object with attributes that include the label's font, text contents, and text color:

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]

The anchored_position (x/y coordinates) and anchor_point (left/right/center justification) of the alarm label on the PyGamer's display screen are calculated to appear in the center of the image grid area. Other display label and value positions were determined and fine-tuned empirically. After defining the display object's attributes, it is appended to the image_group display group.

This process is repeated, starting from the back of the display and progressing towards the front, as each new object is appended to the display group.

Define the Image Group

After playing a couple of musical tones and storing the elapsed time to mark the beginning of the display definition process, the image_group definition list for display group objects comes next. The scale argument adjusts image group element position and size parameters. For the PyGamer's display, no adjustment is required so scale=1.

The time marker mkr_t0 along with seven other process time markers will be reported at the end of each displayed frame to calculate thermal camera code performance. This marker establishes the time that the display group definition phase began.

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

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

Define the Thermal Image Display Group Layers

Next, the 225 square cells used to represent sensor array temperatures are defined and appended to image_group. Two for loops are used to step through each column and row of cells. Each square is defined as a rectangle with width and height equal to CELL_SIZE. No color attribute is defined for the cell, making it transparent -- for now.

# 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(

Define the Text Label Display Group Layers

Finally, the remaining text objects that display legends and values are defined and appended to the image_group display group.

For each object, the label name is defined along with the font, text contents, and font color. Next, the object's anchor_point (justification) and anchored_position (x/y coordinates) attributes are defined. After the attributes are defined, each object is appended to image_group.

# 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]

Whew. We've imported libraries, listed the essential constants, established some helpers, and defined the elements of the display. After a quick aside to talk about how a display group can be accessed, it'll be time to bring it all together in the thermal camera's primary process.

Fun Facts about Display Group Objects

Objects and their attributes in the display group can be accessed in two ways. The most commonly-used method is to assign a name attribute to the object. For example, the text of the status message label can be set to display the text WELCOME in this manner:

status_label.text = "WELCOME"

Objects in image_group can also be accessed by their indexed position in the display group. An index of 0 is the back-most object in the display group; the highest index value is front-most. The status message text can also be changed using the index:

image_group[225].text = "WELCOME"

The Thermal Camera uses both techniques. Named display objects are used whenever possible to clearly identify which object is being changed. For efficiency, however, the index position method is used when stepping through a sequence of image_group objects, as when displaying the 225 colored cells for the sensor image. The index position method is also used by the setup_mode() helper when moving on-screen to select the alarm, maximum, or minimum parameter.

This guide was first published on Jun 09, 2021. It was last updated on Jun 11, 2024.

This page (Display) was last updated on Mar 08, 2024.

Text editor powered by tinymce.