Tutorial - Creating SQs from Vanilla Python Functions ===================================================== .. _tutorial-sqify: 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``: .. code-block:: python 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. .. code-block:: python @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``. .. code-block:: python @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.