Numpy Array Processing with PipeNode¶
Introduction¶
This example will present a complete command-line program to print an equal-space, bitmap / pixel-font display of the current Python version (or, with extension, something else more useful). The display will be configurable with (1) a scaling factor and (2) a variable character to be used per pixel. For example:
% python3 pyv.py --scale=1 --pixel=*
**** * * ***** ***** ** *****
* * * * * * * * *
***** ***** *** ***** * *****
* * * * * * *
* * ***** * ***** * * *****
% python3 pyv.py --scale=2 --pixel=.
........ .. .. .......... .......... .... ..........
........ .. .. .......... .......... .... ..........
.. .. .. .. .. .. .. .. ..
.. .. .. .. .. .. .. .. ..
.......... .......... ...... .......... .. ..........
.......... .......... ...... .......... .. ..........
.. .. .. .. .. .. ..
.. .. .. .. .. .. ..
.. .. .......... .. .......... .. .. ..........
.. .. .......... .. .......... .. .. ..........
Tutorial¶
Rather than explicitly defining each character as a fixed bit map, we can use simple PipeNode functions to define characters as pipeline operations on Boolean NumPy arrays. Operations include creating an empty frame, drawing horizontal or vertical lines, shifting those lines, selectively inverting specific pixels, and taking the union or intersection of any number of frames. Since we want to model linear pipelining of frames through transformational nodes, but also need to expose a scale parameter to numerous nodes, we will use PipeNode functions and a PipeNodeInput instance rather than simple function composition.
We will use the follow imports throughout these examples. The numpy third-party package can be installed with pip.
In order to minimize the number of function_pipe stdout logs, we will partial in a forwarding lambda that does not print.
A derived PipeNodeInput class can specify fixed (as class attributes) or configurable (as arguments passed at initialization and set to instance attributes) parameters, available to all PipeNode functions when called. For this example, we set a fixed frame shape of 5 by 5 pixels as SHAPE, and expose scale and pixel as instance attributes.
Next, we define pipe_node decorated functions (that that take no expression-level arguments) for creating an empty matrix, a vertical line, and a horizontal line. The frame function serves in the innermost position to provide an empty two-dimensional NumPy array filled with False. In the innermost position it only has access to the fpn.PN_INPUT key-word argument. From the fpn.PN_INPUT it can read the SHAPE and scale attributes to correctly construct the frame. The v_line and h_line functions expect a frame passed via fpn.PREDECESSOR_RETURN, and use the scale attribute from fpn.PN_INPUT to write correctly sized Boolean True values in a vertical or horizontal line through the origin (index 0, 0, or the upper left corner) on that frame.
Next, we can create some transformation functions that, given a frame via fpn.PREDECESSOR_PN, transform and return a new frame. The pipe_node_factory decorated functions v_shift and h_shift use the NumPy roll function to shift the two-dimensional array vertically or horizontally by the steps argument, passed via expression-level arguments. The steps passed are interpreted at the unit level, and are thus multipled by scale via fpn.PN_INPUT. As a convenience to users (and catching an error made developing these tools), we check and raise an Exception if we try to do a meaningless shift, such as vertically shifting a vertical line, or horizontall shifting a horizontal line. The PipeNode.unwrap attribute exposes the core callable wrapped by the PipeNode, permitting direct comparison regardless of PipeNode state.
We will need at times to draw points directly, either setting a False pixel to True or vice versa. The pipe_node_factory decorated function flip will, given coordinate pairs in positional arguments, invert the Boolean value found. Again, we use the fpn.PN_INPUT to get the scale argument so coordinates can be passed at the unit level, independent of the scale.
The following pipe_node_factory decorated functions combine variable numbers of PipeNode instances passed via positional arguments. The `` union`` and intersect functions perform logical OR and logical AND, respectively, on all positional arguments. The concat function concatenates frames into a longer frame, inserting a unit-width space bewteen frames.
We will need a function to print any frame to standard out. For this, we can create a pipe_node decorated function that, given a frame via fpn.PREDECESSOR_RETURN, simply walks over the rows and prints the fpn.PN_INPUT defined pixel when a frame value is True, a space otherwise. Since this node returns the fpn.PREDECESSOR_RETURN unchanged, it can be used anywhere in an expression to view a frame mid-pipeline.
We have the tools now to define pipelines to produce the individual characters we need. We will define these in a dictionary, named chars, so that we can map string characters to PipeNode expressions, pass them to concat, and then pipe the results to display. For brevity, we will not define a complete alphabet. For most characters the process involves taking the union of a number of lines (some shifted) and then flipping a few pixels. The font here is based on the Visitor font:
http://www.dafont.com/visitor.font
We need a function to produce the final PipeNode expression. The msg_display_pipeline function, given a string message, will return the PipeNode expression combining concat and display, where concat is called with PipeNode positional arguments, mapped from chars, for each character passed in msg. We map the “_” character for any characters not defined in chars.
Finally, we can define the outer-most application function, which will parse command-line arguments for pixel and scale with argparse.ArgumentParser. The msg_display_pipeline function is called with the prepared msg string, returning f, a PipeNode function configured to generate and display the msg as a banner. A PixelFontInput instance is created with the pixel and scale arguments received from the command line. At last, all core callables are called with the evocation of f with the __getitem__ syntax, passing the PixelFontInput instance pixel_font_input.
Conclusion¶
After going through this tutorial, you should now have an understanding of:
How to use
fpn.PipeNodeto do complex numpy array data pipline processing.
Here is all of the code examples we have seen so far: