Sequence Animation in Python

This sample package demonstrates programmatic generation of bridge animations using popular open-source Python tools. The scripts output video files in a standard format which can be played as media on the bridge.

The general intent of the tool is to provide a text-based programmatic alternative to Pharos Designer. The visual content is specified as data and code and run to generate video file output. The Python can be extended to create custom generative visual effects and filtering.

This approach supports a deliberative edit/compile compositional approach better than interactive or improvisational approaches.

The code requires a working installation of Python 3 with OpenCV, NumPy, and several other third-party packages. For suggestions on setting up your system please see Python 3 Installation.

The code can be browsed on the course site under sequence/, downloaded as sequence.zip, or cloned from the github repo.

For a visual overview, please see the introductory Google Slides show.

Code Overview

The PauschBridge object is responsible for maintaining a sequence of frames which will be turned into an .avi file, which can be imported into the Pharos timeline.

The supported effects include:

  • solid color

  • hue shift

  • basic sprite animation

  • sparkle effect

  • wave

  • color block: each panel on the bridge is a different color from specified palette

These effects can be composited together; i.e. you can place a solid color as background, add a wave on top of that, and sparkles on top of both.

Sample Script

Example 1: compositing a solid background color, wave, and sparkle.

# import the PauschBridge.py library file from the same folder
from PauschBridge import PauschBridge

# instantiate object
pbl = PauschBridge()

# add the desired effects with their RGB values, for the specified duration (10 seconds)
pbl.solid_color((255, 0, 0), 10)
pbl.wave((0, 255, 0), 10)
pbl.sparkle((255, 255, 255), 10)

# save the output to an .avi file with the specified filename
pbl.save('test_composite')

Example 2: the effects return the object directly, so you can use method chaining (i.e. calling PauschBridge().effect1().effect2()) to compactly composite multiple effects together. The + operator allows you to merge two sets of frames together so they play in sequence.

# import the PauschBridge.py library file from the same folder
from PauschBridge import PauschBridge

# each instantiated objects gets iteratively added
# you can composite multiple effects together using method chaining
pbl = PauschBridge().solid_color((255, 0, 0), 5)
pbl += PauschBridge().hue_shift((255, 0, 0), (255, 255, 0), 5)
pbl += PauschBridge().solid_color((255, 0, 0), 5).wave((255, 255, 255), 5)
pbl += PauschBridge().sparkle((255, 255, 255), 5, base_rgb=(0, 0, 0))
pbl += PauschBridge().sprite_from_file('sprite_data.yaml', 5)

pbl.save('test_composite_and_merging')

API Documentation

class sequence.PauschBridge.PauschBridge(num_frames: int = 0)[source]
add_missing_frames(end_time: int)[source]

if self.frames is not large enough to incorporate end_time, pad it :param end_time: time (sec) to fill self.frames up to

color_block(palette: list[sequence.PauschBridge.RGB], end_time: int, start_time: int = 0, slices: Optional[list[sequence.PauschBridge.Indices]] = None, width: int = 4, speed: int = 30)[source]

effect that displays a wave of desired color & width on a base color :param palette: list of RGB values to randomly pick from :param end_time: time (sec) of effect end :param start_time: [optional] time (sec) of effect start :param base_rgb: [optional] RGB values of the desired base color. If not specified, will overlay wave on top of existing color in frames :param slices: [optional] list of the subset of the frame to display effect on, defaults to whole frame :param width: desired width of wave in relation to bridge width, i.e. 0.5 means half the bridge width :param speed: desired speed of wave in pixels / second

get_bottom(duration, start_time=0)[source]

gets list of indices specifying the bottom half of Pausch Bridge only :param duration: time (sec) of effect end :param start_time: [optional] time (sec) of effect start

get_region(duration, region_start, region_end, start_time=0)[source]

gets list of indices specifying the bottom half of Pausch Bridge only :param duration: time (sec) of effect end :param start_time: [optional] time (sec) of effect start

get_top(duration, start_time=0)[source]

gets list of indices specifying the top half of Pausch Bridge only :param duration: time (sec) of effect end :param start_time: [optional] time (sec) of effect start

hue_shift(start_rgb: RGB, end_rgb: RGB, end_time: int, start_time: int = 0, slices: Optional[list[sequence.PauschBridge.Indices]] = None)[source]

effect that displays a gradual (linear) shift from one color to another :param start_rgb: RGB values of the desired starting color :param end_rgb: RGB values of the desired ending color :param end_time: time (sec) of effect end :param start_time: [optional] time (sec) of effect start :param slices: [optional] list of the subset of the frame to display effect on, defaults to whole frame

save(basename)[source]

save frame output to .avi file :param basename: base filename (without extension)

set_values(indices: list[sequence.PauschBridge.Indices], frames: list[numpy.matrix], start_time, end_time)[source]

set frame values within the specified timeframe :param indices: subset of frame on which the effect takes place :param frames: frame list to update self.frames, should match size specified by indices :param start_time: time (sec) of effect start :param end_time: time (sec) of effect end

solid_color(rgb: RGB, end_time: int, start_time: int = 0, slices: Optional[list[sequence.PauschBridge.Indices]] = None)[source]

effect that displays a solid color on the bridge :param rgb: RGB values of the desired color :param end_time: time (sec) of effect end :param start_time: [optional] time (sec) of effect start, defaults to 0 :param slices: [optional] list of the subset of the frame to display effect on, defaults to whole frame

sparkle(highlight_rgb: RGB, end_time: int, start_time: int = 0, base_rgb: RGB = (-1, -1, -1), slices: Optional[list[sequence.PauschBridge.Indices]] = None)[source]

effect that displays sparkles of a desired color on a solid background color :param highlight_rgb: RGB values of the desired sparkle color :param end_time: time (sec) of effect end :param start_time: [optional] time (sec) of effect start :param base_rgb: [optional] RGB values of the desired base color. If not specified, will not overwrite base color :param slices: [optional] list of the subset of the frame to display effect on, defaults to whole frame

sprite(highlight_rgb: RGB, start_time: int, end_time: int, pos: tuple[int, int], velocity: tuple[int, int], acceleration: tuple[int, int], base_rgb: RGB, slices: Optional[list[sequence.PauschBridge.Indices]] = None)[source]

effect that displays a small sprite moving linearly :param highlight_rgb: RGB values of the desired sparkle color :param start_time: time (sec) of effect start :param end_time: time (sec) of effect end :param pos: starting position of small sprite :param velocity: velocity of small sprite (2-d tuple) :param base_rgb: [optional] RGB values of the desired base color :param slices: [optional] list of the subset of the frame to display effect on, defaults to whole frame

sprite_from_file(filename: str, end_time: int, start_time: int = 0)[source]

effect that moves a sprite based on data given from filename :param filename: path to file :param end_time: time (sec) of effect end :param start_time: time (sec) of effect start

wave(highlight_rgb: RGB, end_time: int, start_time: int = 0, base_rgb: RGB = (-1, -1, -1), slices: Optional[list[sequence.PauschBridge.Indices]] = None, width: float = 0.1, speed: int = 30) numpy.matrix[source]

effect that displays a wave of desired color & width on a base color :param highlight_rgb: RGB values of the desired wave color :param end_time: time (sec) of effect end :param start_time: [optional] time (sec) of effect start :param base_rgb: [optional] RGB values of the desired base color. If not specified, will overlay wave on top of existing color in frames :param slices: [optional] list of the subset of the frame to display effect on, defaults to whole frame :param width: desired width of wave in relation to bridge width, i.e. 0.5 means half the bridge width :param speed: desired speed of wave in pixels / second

class sequence.PauschBridge.PauschFrame[source]
get_base_indices()[source]
get_bottom(indices: Optional[Indices] = None) Indices[source]
get_region(start, end, indices: Optional[Indices] = None) Indices[source]
get_top(indices: Optional[Indices] = None) Indices[source]
set_values(indices: Indices, subframe: numpy.matrix)[source]
sequence.PauschBridge.full_day_simulation()[source]
sequence.PauschBridge.parse_field(data, field, optional=False, default=(0, 0), dtype=<class 'int'>)[source]

parse yaml field into appropriate tuple values :param data: data dictionary :param field: field to access data dictionary from :param optional: [optional] if True, return default value if field not in data :param default: [optional] value to return if optional flag is true :param dtype: [optional] what to cast tuple vals into (default is integer)

sequence.PauschBridge.parse_sprite_yaml(data, curr_time)[source]

parses color, position, etc from sprite

sequence.PauschBridge.parse_tuple(s, dtype=<class 'int'>)[source]
sequence.PauschBridge.read_palette(filename)[source]
sequence.PauschBridge.region_select_test()[source]