Tutorial - Creating SQs from Vanilla Python Functions

To break down this tutorial into manageable chunks, we first start with developing the camera pipeline. This camera is situated at the top of the intersection and should output the coordinates of the car. For ease of the tutorial, we’ve provided raw camera footage so you won’t have to get all the hardware necessary and set up the entire project.

The first step in our application is to write a function to set up the cameras and to grab 5 frames of the stored camera stream. We see an example below in camera_sampler:

def camera_sampler(trigger):
        import sys, time
        sys.path.insert(0, '/content/ticktalkpython/libraries')
        import camera_recognition
        global sq_state
        if sq_state.get('camera', None) == None:
                # Setup our various camera settings
                camera_specifications = camera_recognition.Settings()
                camera_specifications.darknetPath = '/content/darknet/'
                camera_specifications.useCamera = False
                camera_specifications.inputFilename = '/content/yolofiles/cav1/live_test_output.avi'
                camera_specifications.camTimeFile = '/content/yolofiles/cav1/cam_output.txt'
                camera_specifications.cameraHeight = .2
                camera_specifications.cameraAdjustmentAngle = 0.0
                camera_specifications.fps = 60
                camera_specifications.width = 1280
                camera_specifications.height = 720
                camera_specifications.flip = 2
                sq_state['camera'] = camera_recognition.Camera(camera_specifications)

        # Package up 5 frames so that we can parse them
        output_package = []
        for idx in range(5):
                frame_read, camera_timestamp = sq_state['camera'].takeCameraFrame()
                output_package.append([frame_read, camera_timestamp])

        return [output_package, time.time()]

Here, the function camera_sampler is written in Python without any direct mention of TTPython constructs. The simplest way to include this Python function into TTPython is to use the @SQify function decorator around camera_sampler as shown below.

@SQify
def camera_sampler(trigger):
    ...

The @SQify decorator transforms a “well-behaved” Python function into a SQ that can then be instantiated one or more times in a TTPython graph. We cannot transform arbitrary Python into a valid SQ. The SQ defines the basic unit of computation in TTPython. The graph execution of our program is transparent to the programmer; they do not need to worry about converting data to tokens to communicate between SQs. When the SQ has the tokens it needs to run, it will provide the values in those tokens as arguments to the supplied function.

Our SQs may still need to keep persistent state, so we use the global variable sq_state to store state between multiple executions of a SQ. Note that this does not share the same semantics as Python’s global keyword. Each SQ has its own notion of an sq_state, and any call will only access the local SQ’s version of persistent state. All data in TTPython follows pass-by-value semantics.

The contained function should not have any mention of TTPython constructs. TTPython treats the contained SQified function as a black box and vice versa, so any attempts to use TTPython constructs within will fail.

NOTE: @SQify limits the expressiveness of Python functions it decorates. *args is not allowed in function definitions as our graph requires a statically known number of input arguments. **kwargs are allowed in function calls if they are also defined within the function definition.

We can now write a basic “Hello World” program to call camera_sampler once! We have informed TTPython how to include our sensing function as a SQ into our graph. Now, we need to define the graph structure for our program. To do so, we use another function decorator: @GRAPHify. We’ve also included some more functions to process the data generated by camera_sampler.

@SQify
    def camera_sampler(trigger):
            import sys, time
            sys.path.insert(0, '/content/ticktalkpython/libraries')
            import camera_recognition
            global sq_state
            if sq_state.get('camera', None) == None:
                    # Setup our various camera settings
                    camera_specifications = camera_recognition.Settings()
                    camera_specifications.darknetPath = '/content/darknet/'
                    camera_specifications.useCamera = False
                    camera_specifications.inputFilename = '/content/yolofiles/cav1/live_test_output.avi'
                    camera_specifications.camTimeFile = '/content/yolofiles/cav1/cam_output.txt'
                    camera_specifications.cameraHeight = .2
                    camera_specifications.cameraAdjustmentAngle = 0.0
                    camera_specifications.fps = 60
                    camera_specifications.width = 1280
                    camera_specifications.height = 720
                    camera_specifications.flip = 2
                    sq_state['camera'] = camera_recognition.Camera(camera_specifications)

            # Package up 5 frames so that we can parse them
            output_package = []
            for idx in range(5):
                    frame_read, camera_timestamp = sq_state['camera'].takeCameraFrame()
                    output_package.append([frame_read, camera_timestamp])

            return output_package

@SQify
    def process_camera(cam_sample):
            import sys, time
            sys.path.insert(0, '/content/ticktalkpython/libraries')
            import camera_recognition
            global sq_state

            for each in cam_sample:
                    camera_frame = each[0]
                    camera_timestamp = each[1]
                    if sq_state.get('camera_recognition', None) == None:
                            # Setup our various camera settings
                            camera_specifications = camera_recognition.Settings()
                            camera_specifications.darknetPath = '/content/darknet/'
                            camera_specifications.useCamera = False
                            camera_specifications.inputFilename = '/content/yolofiles/cav1/live_test_output.avi'
                            camera_specifications.camTimeFile = '/content/yolofiles/cav1/cam_output.txt'
                            camera_specifications.cameraHeight = .2
                            camera_specifications.cameraAdjustmentAngle = 0.0
                            camera_specifications.fps = 60
                            camera_specifications.width = 1280
                            camera_specifications.height = 720
                            camera_specifications.flip = 2
                            sq_state['camera_recognition'] = camera_recognition.ProcessCamera(camera_specifications)

                    coordinates, processed_timestamp = sq_state['camera_recognition'].processCameraFrame(camera_frame, camera_timestamp)

            return coordinates

    @GRAPHify
    def example_1_test(trigger):
            with TTClock.root() as root_clock:
                    cam_sample = camera_sampler(trigger)

                    processed_camera = process_camera(cam_sample)

The function decorator @GRAPHify uses the decorated function as the main program for the graph. It defines the connections between the SQs decorated from the functions above. @GRAPHify requires that the underlying wrapped function contains at least one argument, as this argument acts as the start of the execution of the graph. You can change the value given to these parameters, but we’ll ignore this for the purpose of this tutorial.

Furthermore, any function called within @GRAPHify needs to have been decorated by @SQify. This can be observed in the function process_camera. Since this function is used inside @GRAPHify to process the camera_frame obtained from camera_sampler, we need to SQify the process_camera function. All TTPython abstractions work directly under the @GRAPHify decorator, and the programmer can worry about the correctness of programs in these functional SQs without TTPython constructs.

Now that we understand how to insert SQs into TTPython with @SQify and run them through @GRAPHify, take a look at the CAVExamples.ipynb file and run Steps 2 and 3 to see how to compile and run a basic TTPython program.