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.PipeNode
to do complex numpy array data pipline processing.
Here is all of the code examples we have seen so far: