Helpers for Display, Buttons, and Setup Functions

Helpers are used to simplify the primary loop code. The helpers:

  • Play a tone to signify a button press or alert;
  • Display a status message in the center of the image area;
  • Load a set of test color spectrum values into the display array;
  • Display and refresh the sensor image;
  • Display and refresh the histogram image;
  • Enlarge the 8x8 sensor data into a 15x15 display array;
  • Change default parameters for temperature range and alarm threshold;
  • Convert joystick movement to simulate up, down, left, and right button presses to support use with PyGamer or PyBadge boards.

play_tone() Helper

Using the tone() helper that's contained in the simpleio library, the thermal camera's play_tone() helper plays a musical note through the PyGamer's speaker. The frequency in Hertz and duration in seconds are passed to the helper as the parameters freq and duration.

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

flash_status() Helper

The flash_status() helper accepts a text string and displays it in the status area of the display. The text appears as white letters for a time specified by duration then as black letters for duration length in seconds. This is very useful for flashing a message that can be seen regardless of the background colors, especially handy while displaying a sensor image.

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

spectrum() Helper

Initially used to help debug the iron spectrum conversion code, the spectrum() helper loads the image grid array with a sequence of index values that sweep through the colors of the thermal camera's visual pseudocolor spectrum. This helper is used in conjunction with the update_image_frame() helper to display a sample of the default spectrum upon startup.

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

update_image_frame() Helper

The update_image_frame() helper looks through a list of 225 indexed color values stored by row and column in the grid_data list. The helper converts the color index into a displayable RGB color value and updates the fill color of the corresponding display cell.

To save processing time and improve image frame rate, a cell is only updated if the calculated RGB value has changed from one frame to the next.

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

update_histo_frame() Helper

The update_histo_frame() helper collects a distribution of 15 temperature sub-ranges within the current temperature display range (one for each color) and displays a histogram of relative temperature values. The helper scans all 225 sensor color index values in the grid_data array and counts the number of times a value falls within one of 15 sub-ranges.

When invoked, the helper displays the histogram range legend values and clears the histogram array used to accumulate the 15 sub-range values. After collecting the histogram data from the array, the largest sub-range value is stored in the histo_scale variable is used to scale the results when the histogram is displayed.

The second part of the helper updates the image area to display the histogram as a series of vertical bars with height proportional to the accumulated sub-range value. The display update starts at the upper left of the display's image area and works down to the lower right. Each cell is filled with a color that corresponds to the color index value. The remainder of boxes in the histogram display area are colored black if not used to build a histogram bar.

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.numpy.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.numpy.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

ulab_bilinear_interpolation() Helper

The ulab_bilinear_interpolation() helper utilizes ulab array calculations to find values for cells in the 225-cell grid_data array that fall between the 64 known sensor element values. First, the even rows are scanned, assigning the average of the adjacent known cells to each unknown cell. Next, odd rows are scanned, assigning the average of the values above and below to every cell in the row. See the section, 1-2-3s of Bilinear Interpolation for the details of the interpolation method.

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

setup_mode() Helper

The setup_mode() helper pauses normal operation and collects user input to set alarm threshold and display range min/max values. During the Setup mode, the display's average value and label are blanked.

The joystick or PyBadge D-Pad is used to select the parameter to change and to increase or decrease the parameter value. The HOLD button acts as the parameter select button. Pressing the SET button at any time during the Setup mode will exit back to the primary process loop.

The first task is to temporarily display a status message that indicates the camera is in the Setup mode. The display's average value and label are blanked and the measured maximum and minimum values are replaced with the current maximum and minimum display range values (MAX_RANGE_F and MIN_RANGE_F).

After waiting a bit for the status message to be read and prior to watching for button and joystick changes, the index pointer (param_index) is reset to point to the alarm threshold parameter.

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

The following is the meat of the setup process. Before moving on to choosing which parameter to set, the process waits until the SET button has been released.

As long as the HOLD (select) or the SET (setup mode exit) buttons have not been pressed, the code loops. During the loop, the joystick is watched using the move_buttons() helper. If the joystick is moved down, the parameter index is incremented, pointing to the next parameter. If moved up, the index will point to the previous parameter. The parameter label text flashes black and white, indicating which parameter is ready to be changed.

In the image_group list (that is defined later in the display portion of the code just before the primary process loop), the three parameter text labels for alarm, maximum, and minimum are sequentially positioned in the list:

  • Alarm text label       --> image_group[226]
  • Maximum text label --> image_group[227]
  • Minimum text label  --> image_group[228]    

Using an indexed position in image_group for the parameters makes it simpler to sequentially step from one parameter to the next.

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

After the HOLD button is pressed and released, the selected parameter, represented by the value of param_index, can be changed.

The selected parameter value is incrementally changed by the joystick's up and down movements as provided to this helper from the move_buttons() helper. The new value is checked against and limited to the sensor's factory min/max limits (MIN_SENSOR_F, MAX_SENSOR_F).

In the image_group list the three parameter value labels for alarm, maximum, and minimum are sequentially positioned in the list:

  • Alarm value label       --> image_group[230]
  • Maximum value label --> image_group[231]
  • Minimum value label  --> image_group[232]  

The value label for the selected parameter is changed and displayed.

Meanwhile, a flashing status message indicates which type of parameter is being changed, either the alarm or one of the range values.

When the desired value is reached and the HOLD (select) button is pressed, the Setup process continues back to the parameter select mode.

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)

If SET is pressed instead of HOLD, the Setup process prepares to exit back to the primary process loop.

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

Before exiting, a resumption status message is displayed and the display of the average label and value are restored.

Finally, the text strings that may have changed during the Setup process are converted to integer numeric values and returned to the primary process loop.

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)

move_buttons() Helper

The move_buttons() helper first resets the variables that indicate joystick movement or D-Pad button presses. If the joystick argument is True, the joystick movements beyond set thresholds are represented as button depressions. For example, a value for panel.joystick[1] of less than 20000 means that the joystick was moved upwards; greater than 44000 indicates downward movement.

If the joystick argument is False, instead of watching the joystick, the D-Pad buttons are checked to see if any are depressed.

Finally, the movement indicating values are returned to the calling module.

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

After the helpers are defined, we move on to specifying the text and graphic features of the display.

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

This page (Helpers) was last updated on Feb 24, 2022.

Text editor powered by tinymce.