Illuminate: Randy Pausch Bridge Light-Up Night

Written by Garth Zeglin.

This live event on April 5, 2021 was a community event to create short new shows for the bridge. The meeting and collaboration all took place online, but the results were shown physically on the bridge.

The event was created as a collaboration between the School of Computer Science and the ScottyLabs student organization. Detailed acknowledgements can be found on the Illuminate event page.

The underlying technology includes an online ‘piano-roll’ animation editor created by ScottyLabs and an image animation pipeline created by Garth Zeglin.

Image to Video Animation Tools

video.py

General purpose image and video generation functions for the Pausch Bridge.

pbridge.video.color_code_image(value)[source]

Generate a single video frame for the Pausch Bridge representing the given integer code. Each digit is encoded as a color block spanning two fixture groups, separated by a single group in the margin color. The code is preceded and trailed by margin color blocks to distinguish black code blocks from the margins. There are 35 contiguous CG4 fixture in the main span. With allowances for margins, this may encode as many as 11 digits. If zero-padding is desired, pass a string value including leading zeros. Returns a tuple (frame, code_name).

pbridge.video.image_keyframes(frame, beats=5)[source]

Generator function to produce a sequence of keyframes for presenting a static image including a fade-in and fade-out. The keyframes are assumed to occur at constant rate, so the first keyframe is black, then the image is repeated for a specified number of beats, then the final two keyframes are black.

pbridge.video.image_row_keyframes(source)[source]

Generator function to produce a sequence of keyframes by expanding successive rows of a source image into full frames.

Parameters

source – a NumPy array representing a 228-pixel-wide image with each row a keyframe

pbridge.video.keyframe_interpolator(keyframe_generator, tempo=60)[source]

Generator function to produce successive frames of a video sequence by linear interpolation between keyframes at a constant tempo. Yields a video image frame, or None when the sequence is complete.

Parameters
  • keyframe_generator – generator function which returns a video frame or None

  • tempo – keyframe rate in beats per minute

pbridge.video.read_image_file(path)[source]

Read an image file and preprocess into a 228-pixel wide BGR image in which each row represents a keyframe. This subsamples vertically every eight pixels and deletes any alpha channel. Returns None or an image array.

pbridge.video.validate_animation_timing(source, tempo, max_duration=60, min_duration=15)[source]

Check the size and rate of a source animation image, adjusting values to stay within policy limits. This will apply a rate policy, maximum duration policy, and minimum duration policy. Overly long animations may be truncated, overly short animations will be looped.

Parameters
  • source – source image for which each row is a keyframe

  • tempo – keyframe rate in beats per minute

Returns

tuple (image, tempo)

pbridge.video.write_video_file(filepath, frame_generator)[source]

Write a video file using frames returned from a generator function. The function yields either an image frame or None once the sequence is complete. The video file format is determined from the extension in the path.

Animation Workflow Manager

All the code for the image animation pipelines can be browsed online in illuminate. The full text of the top-level script appears below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#!/usr/bin/env python3

# Workflow manager for Illuminate: Randy Pausch Bridge Light-Up Night 2021
# https://www.cs.cmu.edu/~PauschBridge/Illuminate-2021.html

# This tool performs several tasks:
#
#  1. submissions
#     1. monitoring the database of image submissions
#     2. downloading the unfiltered new image submissions to a local cache
#     3. presenting new submissions in an 'inbox' folder
#
#  2. conversion
#     1. monitoring the 'approved' folder for manually approved images
#     2. converting images to a video clip for the bridge
#     3. preparing a preview video clip
#     4. uploading the approved images and preview videos to Google Drive
#
# All state is kept in the file system itself; this tool can be stopped and
# restarted.  Files can be deleted from the local cache to trigger reprocessing.
#
#================================================================
# Dependencies:
#
# The pbridge modules assume the availability of several third-party libraries.
# For details, please see:
#
#   pbridge/video.py:   OpenCV and numpy libraries
#   pbridge/gdrive.py:  Google Drive API libraries
#
# Several modules require configuration settings to be created in config.py.
# If installing from scratch, please follow the instructions in config-prototype.py.
# 
#================================================================
# Import standard Python modules.
import argparse, os, time, sys, signal, logging

import config
import pbridge.gdrive
import pbridge.convmonitor
import pbridge.submonitor

#================================================================
def configure_logging():
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.DEBUG)
    console_handler.setFormatter(logging.Formatter('%(levelname)s:%(name)s: %(message)s'))
    logging.getLogger().addHandler(console_handler)

    logging.getLogger().setLevel(logging.DEBUG)
    logging.getLogger('googleapiclient.discovery').setLevel(logging.INFO)
    logging.getLogger('google.auth.transport.requests').setLevel(logging.INFO)
    logging.getLogger('urllib3.connectionpool').setLevel(logging.INFO)

#================================================================
def sigint_handler(signal, frame):
    logging.info("Keyboard interrupt caught, shutting down...")
    sys.exit(0)
            
#================================================================
def main():
    # Configure logging to stream messages to the terminal console.
    configure_logging()
    
    # Attach a handler to the keyboard interrupt (control-C).
    signal.signal(signal.SIGINT, sigint_handler)

    # Connect to the Google Drive API.  This may trigger a browser-based
    # authentication process if the token is not found.
    gdrive = pbridge.gdrive.GDrive(config)
    gdrive.authenticate_and_connect()

    sub_monitor  = pbridge.submonitor.SubmissionMonitor(config, gdrive)
    conv_monitor = pbridge.convmonitor.ConversionMonitor(config, gdrive)

    if sub_monitor.ready and conv_monitor.ready:
        while True:
            sub_monitor.poll()
            time.sleep(8.0)            
            conv_monitor.poll()
            time.sleep(8.0)
    else:
        logging.warning("One or more components not ready to run, quitting.")
        
#================================================================
# Main script follows.  This sequence is executed when the script is initiated from the command line.

if __name__ == "__main__":
    main()