11. API - Screen Arrays

This chapter covers the ScreenArray class, how it should be constructed, how it can be used to manipulate the Sense HAT’s display, and how to convert it to various different formats.

11.1. ScreenArray Class

class pisense.ScreenArray(shape=(8, 8))

The ScreenArray class is a descendant of ndarray with customizations to make working with the Sense HAT screen a little easier.

In most respects, a ScreenArray will act like any other numpy array. Exceptions to the normal behaviour are documented in the following sections.

Instances of this class should not be created directly. Rather, obtain the current state of the screen from the array attribute of SenseHAT.screen or use the array() function to create a new instance from a variety of sources (a PIL Image, another array, a list of Color instances, etc).

pisense.array(data=None, shape=(8, 8))[source]

Use this function to construct a new ScreenArray and fill it with an initial source of data, which can be:

  • A single Color. The resulting array will have the specified shape.
  • A list of Color values. The resulting array will have the specified shape.
  • An Image. The resulting array will have the shape of the image (the shape parameter is ignored).
  • Any compatible ndarray. In this case the shape of the array is preserved (the shape parameter is ignored).

11.2. Display Association

If the ScreenArray instance was obtained from the array attribute of SenseHAT.screen it will be “associated” with the display. Manipulating the content of the array will manipulate the appearance of the display on the Sense HAT:

>>> from pisense import *
>>> hat = SenseHAT()
>>> arr = hat.screen.array
>>> arr[0, 0] = (1, 0, 0)  # set the top-left pixel to red

Copying an array that is associated with a display (via the copy() method) breaks the association. This is a convenient way to take a copy of the current display, fiddle around with it without intermediate states displaying, and then update the display by copying it back:

>>> from pisense import *
>>> hat = SenseHAT()
>>> arr = hat.screen.array.copy()
>>> arr[4:, :] = (1, 0, 1)  # HAT's pixels are *not* changed (yet)
>>> hat.screen.array = arr  # HAT's bottom pixels are changed to purple

Operations in numpy that create a new array will also break the display association (e.g. adding two arrays together to create a new array; the new array will not “derive” its display association from the original arrays). However, operations that don’t create a new array (e.g. slicing, flipping, etc.) will normally maintain the association. This is why you can update portions of the display using slices.

11.3. Data Type

The data-type of the array is fixed and cannot be altered. Specifically the data-type is a triple of single-precision floating point values between 0.0 and 1.0, labelled “r”, “g” and “b”. In other words, each element of the array is a triple RGB value representing the color of a single pixel.

The 0.0 to 1.0 range of color values is not enforced. Hence if you add two screen arrays together you may wind up with values greater than 1.0 or less than 0.0 in one or more color planes. This is deliberate as intermediate values exceeding this range can be useful in some calculations.

Hint

The numpy clip() method is a convenient way of limiting values to the 0.0 to 1.0 range before updating the display.

11.4. Previews

While you can see the state of the HAT’s array visually, what about arrays that you create separately with the array() function? For this, the show() method is provided:

>>> from pisense import *
>>> arr = array(draw_text('Hello!'))
>>> arr.show()

██      ██              ████    ████                ██
██      ██                ██      ██                ██
██      ██    ██████      ██      ██      ██████    ██
██████████  ██      ██    ██      ██    ██      ██  ██
██      ██  ██████████    ██      ██    ██      ██  ██
██      ██  ██            ██      ██    ██      ██
██      ██    ██████    ██████  ██████    ██████    ██
>>> arr.show('##', width=16, overflow='$')
              $
##      ##    $
##      ##    $
##      ##    $
##########  ##$
##      ##  ##$
##      ##  ##$
##      ##    $
>>> arr[:8, :8].show()

██      ██
██      ██
██      ██    ██
██████████  ██
██      ██  ████
██      ██  ██
██      ██    ██

Note that the method is not limited to the size of the Sense HAT’s screen, which makes it useful for previewing constructions that you intend to slice for display later.

ScreenArray.show(element='\u2588\u2588', colors=None, width=None, overflow='\u00BB')

Print a preview of the screen to the console.

The element parameter specifies the string used to represent each element of the display. This defaults to “██” (two Unicode full block drawing characters) which is usually sufficient to provide a fairly accurate representation of the screen.

The colors parameter indicates the sort of ANSI coding (if any) that should be used to depict the colors of the display. The following values are accepted:

Value Description
16m Use true-color ANSI codes capable of representing ≈16 million colors. This is the default if stdout is a TTY. The default terminal in Raspbian supports this style of ANSI code.
256 Use 256-color ANSI codes. Most modern terminals (including Raspbian’s default terminal) support this style of ANSI code.
8 Use old-style DOS ANSI codes, only capable of representing 8 colors. There is rarely a need to resort to this setting.
0 Don’t use ANSI color codes. Instead, any pixel values with a brightness >25% (an arbitrary cut-off) will be displayed, while darker pixels will be rendered as spaces. This is the default if stdout is not a TTY.

The width parameter specifies the maximum width for the output. This defaults to None which means the method will attempt to use the terminal’s width (if this can be determined; if it cannot, then 80 will be used as a fallback). Pixels beyond the specified width will be excluded from the output, and a column of overflow strings will be shown to indicate that horizontal truncation has occurred in the output.

11.5. Format Strings

Screen arrays can also be used in format strings to return the string that the show() method would print. The format string specification for screen arrays consists of colon-separated sections (in any order):

  • A section prefixed with “e” specifies the string used to represent an individual element of the display. This defaults to ██ (two filled Unicode block characters, which usually represents the display fairly accurately), and is equivalent to the element parameter of show().
  • A section prefixed with “o” specifies the string used to represent horizontal overflow (equivalent to the overflow parameter). When the string will be longer than the specified width (or the terminal width if none is given), it will be truncated and the overflow string displayed at the right.
  • A section prefixed with “w” specifies the maximum width that the rendered array can take up in character widths (equivalent to the width parameter). Note that ANSI color codes (which render with zero width) will not count towards this limit, so each line returned may be longer than the specified width but shouldn’t render longer than this. The default is the width of the terminal, if it can be detected, or 80 columns otherwise.
  • A section prefixed with “c” specifies the style of ANSI color codes to use in the output (equivalent to the colors parameter). If unspecified, full true-color ANSI codes will be used if the terminal is detected to be a TTY. Otherwise, no ANSI codes will be used and elements will only be rendered if their lightness exceeds 1/4 (an arbitrary cut-off which seems to work tolerably well in practice). See the show() method for more information on valid values for this parameter.

Some examples of operation:

>>> from pisense import *
>>> arr = array(draw_text('Hello!'))
>>> print('{}'.format(arr))

██      ██              ████    ████                ██
██      ██                ██      ██                ██
██      ██    ██████      ██      ██      ██████    ██
██████████  ██      ██    ██      ██    ██      ██  ██
██      ██  ██████████    ██      ██    ██      ██  ██
██      ██  ██            ██      ██    ██      ██
██      ██    ██████    ██████  ██████    ██████    ██
>>> print('{:e#:c0}'.format(arr))

#   #       ##  ##        #
#   #        #   #        #
#   #  ###   #   #   ###  #
##### #   #  #   #  #   # #
#   # #####  #   #  #   # #
#   # #      #   #  #   #
#   #  ###  ### ###  ###  #
>>> print('{:e#:o$:w16}'.format(arr))
               $
#   #       ## $
#   #        # $
#   #  ###   # $
##### #   #  # $
#   # #####  # $
#   # #      # $
#   #  ###  ###$
>>> print('{:e##:o$:w16}'.format(arr))
              $
##      ##    $
##      ##    $
##      ##    $
##########  ##$
##      ##  ##$
##      ##  ##$
##      ##    $

Note

The last example demonstrates that elements will never be chopped in half by the truncation; either a display element is included in its entirety or not at all.

A more formal description of the format string specification for ScreenArray would be as follows:

<format_spec> ::= <format_part> (":" <format_part>)*
<format_part> ::= (<elements> | <overflow> | <colors> | <width>)
<elements>    ::= "e" <any characters except : or {}>+
<overflow>    ::= "o" <any characters except : or {}>+
<colors>      ::= "c" ("0" | "8" | "256" | "16m")
<width>       ::= "w" <digit>+
<digit>       ::= "0"..."9"

11.6. Format conversions

The following conversion functions are provided to facilitate converting various inputs into something either easy to manipulate or easy to display on the screen.

pisense.buf_to_image(buf)[source]

Converts buf to an RGB PIL Image. The buf parameter can be any of the types accepted by buf_to_rgb888().

pisense.buf_to_rgb(buf)[source]

Converts buf to a 2-dimensional numpy ndarray containing 3-tuples of floats between 0.0 and 1.0 (in other words, the same format as ScreenArray). The buf parameter can be any of the types accepted by buf_to_rgb888().

pisense.buf_to_rgb888(buf)[source]

Converts buf to a 3-dimensional numpy ndarray containing bytes (RGB888 format). The buf parameter can be any of the following types:

  • An PIL Image.
  • An numpy ndarray with a compatible data-type (the 3-tuple of floats used by ScreenArray, or simple bytes).
  • A buffer of 192 bytes; each 3 bytes will be taken as RGB levels for pixels, working across then down the display.

The last format is fixed size as a linear buffer has no shape and that is the one size we can reasonably guess a shape for. However, the other formats are not size limited.

11.7. Advanced conversions

The following conversion functions are used internally by pisense, and are generally not required unless you want to work with SenseScreen.raw directly, or you know exactly what formats you are converting between and want to skip the overhead of the buf_to_* routines figuring out the input type.

pisense.image_to_rgb565(img)[source]

Convert img (an Image) to RGB565 format in an ndarray with shape (8, 8).

pisense.rgb565_to_image(arr)[source]

Convert an ndarray in RGB565 format (unsigned 16-bit values with 5 bits for red and blue, and 6 bits for green laid out RRRRRGGGGGGBBBBB) to an Image.

pisense.image_to_rgb888(img)[source]

Convert img (an Image) to RGB888 format in an ndarray with shape (8, 8, 3).

pisense.rgb888_to_image(arr)[source]

Convert an ndarray in RGB888 format (unsigned 8-bit values in 3 planes) to an Image.

pisense.image_to_rgb(img)[source]

Convert img (an Image) to an ndarray in RGB format (structured floating-point type with 3 values each between 0 and 1).

pisense.rgb_to_image(arr)[source]

Convert arr (an ndarray in RGB format, structured floating-point type with 3 values each between 0 and 1) to an Image.

pisense.rgb888_to_rgb565(arr, out=None)[source]

Convert an ndarray in RGB888 format (unsigned 8-bit values in 3 planes) to an ndarray in RGB565 format (unsigned 16-bit values with 5 bits for red and blue, and 6 bits for green laid out RRRRRGGGGGGBBBBB).

pisense.rgb565_to_rgb888(arr, out=None)[source]

Convert an ndarray in RGB565 format (unsigned 16-bit values with 5 bits for red and blue, and 6 bits for green laid out RRRRRGGGGGGBBBBB) to an ndarray in RGB888 format (unsigned 8-bit values in 3 planes).

pisense.rgb_to_rgb888(arr, out=None)[source]

Convert a numpy ndarray in RGB format (structured floating-point type with 3 values each between 0 and 1) to RGB888 format (unsigned 8-bit values in 3 planes).

pisense.rgb888_to_rgb(arr, out=None)[source]

Convert a numpy ndarray in RGB888 format (unsigned 8-bit values in 3 planes) to RGB format (structured floating-point type with 3 values, each between 0 and 1). 1.

pisense.rgb_to_rgb565(arr, out=None)[source]

Convert a numpy ndarray in RGB format (structured floating-point type with 3 values each between 0 and 1) to RGB565 format (unsigned 16-bit values with 5 bits for red and blue, and 6 bits for green laid out RRRRRGGGGGGBBBBB).

pisense.rgb565_to_rgb(arr, out=None)[source]

Convert a numpy ndarray in RGB565 format (unsigned 16-bit values with 5 bits for red and blue, and 6 bits for green laid out RRRRRGGGGGGBBBBB) to RGB format (structured floating-point type with 3 values each between 0 and 1).