/pictograph

Pictograph is a graphical interface for creating and running programs built out of small blocks of python code.

Primary LanguagePythonOtherNOASSERTION

README

This README gives some info about what Pictograph is, and how it works.

DOI

What is Pictograph

Pictograph is a graphical interface for connecting and running small pieces of python code. At its core it is meant to provide a rich, graphical, way to create and run workflows, called pictographs. These pictographs are created by chaining together nodes or glyphs; it's a visual way to create computer programs. Each glyph represents a small piece of python code, and visually shows its inputs and output as anchors which can be connected to other nodes. Each input is connected to the output of a single other node, and each output can be connected to multiple nodes. The "ready state" of each node is recorded and automatically communicated to all connected nodes, thus allowing the computations represented by the pictograph to proceed automatically once all inputs are defined.

Pictographs are saved in json format for maximum openness and flexibility. Since json is plain text, you can do anything with them that you can do with a text file... like edit them by hand, put them in version control, copy/paste them into an email, etc.

Pictograph is completely written in python, both for speed of development and for maximum "hackability". The quickest way to get started extending Pictograph is to define your own node types. Node.py defines the base Node class, and your custom subclasses can do any computations that you desire. You define what your node does, and Pictograph turns it into a drag-and-drop black box for incorporating into your workflows.

Here's some ideas of what to do with Pictograph:

  • Create computational workflows... visually
  • Design and test new computational algorithms
  • Create workflows for capturing data in the lab, and running postprocessing routines
  • Define pictographs that take your data and automatically create polished graphs
  • Anything else you could do with python!

How do I get set up?

Pictograph is currently only available in source format. You need a few things to get up and running with it.

  • Some sort of python environment (Using Anaconda is strongly encouraged!)
  • PyQt5 and numpy

Once you have your python environment set up, just run python main.py!

The Node base class

The node base class defined in Node.py is set up to provide nearly all the necessary functionality that makes pictographs work. The behavior of a node is described by a small set of functions, which you can think of as a mini API:

  • list_inputs(): returns a list of names/keys of the inputs. In traditional programming, these would be the names of function arguments. Each input is another Node.
  • connect_input( input_name, node ): sets node to be the input for input_name
  • disconnect_input( input_name ): removes the connection for input_name
  • connect_output( node ): adds node to the list of output connections
  • disconnect_output( node ): removes node from the list of output connections
  • is_output_valid(): check if cached output is accurate given the current values of the inputs
  • process(): do the computation that defines this node
  • set_auto_process(flag): set auto_process to value of True/False flag, to say if this node should automatically perform its process once its inputs become valid
  • description: the text that describes this node

For the nerds who care (that's all of you, right?), the Node class works by message passing, using a simple implementation of the Observer design pattern. Each node is both a publisher (subject) and a subscriber (observer). When the node's output becomes valid (or invalid), it sends a message to all nodes connected to its output. Meanwhile it is subscribed to receive messages from all the nodes connected to its inputs. And that's pretty much it; not too complicated is it? Note that the Node class is implemented in a separate file from the rest of the app, so that if you want to use the nodes without the GUI, you totally can.

Also, the output of a node can be anything you want. Since objects in python are fundamental to how the language works, this means that nodes can be much more powerful than you might first imagine. (Want a node that returns the definition of a new python class? You can totally do that.)

Here are a couple examples of node definitions, so that you can see how easy it really is to write your own custom nodes.

class AdditionNode(Node):
    def __init__(self):
        super().__init__()
        self.displayName = "Add"
        self.description = "Adds two input values"
        self._input_terminals = {"arg1": None, "arg2": None}

    def _process_core(self):
        return self.arg1 + self.arg2

Note in this example that the keys in the _input_terminals dictionary become variables that you can use in the _process_core function.

class NumberNode(Node):
    def __init__(self, the_value=0):
        super().__init__()
        self.displayName = "Number"
        self.description = "A constant numerical value"
        self._adjustable_parameters = 
                {'Number': AdjustableParameter(
                name="Number", 
                type="double", 
                val=the_value)}
        self._output_data_cache = self._adjustable_parameters['Number']._value
        self._is_output_valid = True
    
    def _process_core(self):
        self._output_data_cache = self._adjustable_parameters["Number"]._value
        return self._output_data_cache

This example shows a node with "adjustable parameters". These are values that will have a widget in the GUI so that you can, unsurprisingly, adjust their values. For simple parameter types, the GUI widgets will be created automatically. For more complicated nodes, you can create your own QT widget for adjusting the node, displaying its value... whatever you want, really.