-
Notifications
You must be signed in to change notification settings - Fork 4
How to develop
Please make sure you have a basic understanding of object-oriented programming and are comfortable with Python.
To understand how to create new widgets, first let’s look at the class hierarchy and relationships of those core classes in labpype.widget
.
A widget is composed of attributes, behaviors, a set of anchors for input and output, a dialog for interaction, and the task it does. By subclassing Widget
and Dialog
class, and occasionally Anchor
and BaseWidget
class, we create new widgets.
Classes in blue are the ones provided by LabPype:
-
BaseWidget
- Defines everything else of a widget except for the components we just mentioned. -
Widget
- A subclass ofBaseWidget
that tells the base class how to behave. This built-in child class ofBaseWidget
is a 5-state state machine that is sufficient for most cases. -
Anchor
- It is used for building connections between widgets. -
Dialog
- The superclass of all dialogs that are associated with widgets. It has many useful functions to make it simple for us to create dialogs.
Classes in green are the ones we would normally subclass. Subclass the Widget
class, then specify a few class attributes and implement the task, we get a new widget. Among the class attributes, INCOMING
and OUTGOING
are the ones that specify the data type for input and output. They are by default instances of Anchor
, which we do not need to subclass in most cases. However, each anchor should have an attribute that specifies the data type this anchor transmit. This attribute is called anchor type. Another attribute DIALOG
specifies what dialog class should be used for interacting with this widget. If the widget is simple enough, then a dialog can be generated automatically by introspection. In this case, the base class Dialog
will be assigned to the widget.
Classes in yellow are the ones that we only subclass when more flexibility is needed. For example, we can create a new subclass of BaseWidget
that is an 8-state state machine, so that it can have more complicated behaviors. By subclassing the Anchor
, we can change how connections are made and used in the workflow. See Advanced for more details.
First, "a set of widgets" means a python package that is installed in LabPype. From now on, we will call it a widget package. It has the following minimal structure:
a_widget_package/
__init__.py
widget.py
dialog.py
-
__init__.py
- tells LabPype what widgets it provides and what types of connections can be made between the widgets. -
widget.py
- contains widgets that are child classes ofWidget
. -
dialog.py
- contains dialogs that are child classes ofDialog
.
We can put other scripts or resources in the widget package. Note that, we cannot know where this package will be installed on the user's computer. Therefore, to refer to files in the package folder, always use __file__
to find the current path for the widget package:
pkgPath = os.path.dirname(os.path.realpath(__file__))
Here are the general steps for making a widget package.
- Create the file structure as mentioned before.
- In the
widget.py
, first define data types (also called anchor types) by subclassingANCHOR_REGULAR
(fromlabpype.widget
). Note that this is different from subclassingAnchor
. The purpose ofAnchor
is to define behaviors, while anchor types simply represent types of data. They are empty classes that are used to define legit connections between anchor types later. Then, subclassWidget
for as many widgets as you have. - Create dialogs for your widgets.
- In the
__init__.py
, import all the anchor types and widget classes you created in step 2. You do not need to import the dialogs, as they are the value of class attributeDIALOG
of widgets. Then, define all kinds of legit connections between those anchor types in a list calledANCHORS
. Last, put all the widgets in a list calledWIDGETS
, along with their colors and icons. - Now you can move this package into
workspace/installed/
, restart LabPype. If no errors occurred, you should see the new widget set in the widget panel. Alternatively, you can zip the package, and in the widget manage dialog, install the package, as demonstrated in How to use section.
I strongly suggest that you take a look at the code in the ToyWidgetSet. Download and install the ToyWidgetSet
, then check out workspace/installed/toy/
.
So far, we haven't talked about what are the purposes of widget and anchor. We will use the following example to demonstrate the concept. Suppose we have a task that goes like this: we want to generate the vocabulary of an input text, then display the vocabulary. To perform this task in LabPype, we need these three widgets derived from class Widget
:
-
InputText
- lets us directly enter text through its dialog and send the text to downstream widgets. This type of input directly from the widget itself is calledINTERNAL
. The output is calledOUTGOING
. -
ExtractVocabulary
- accepts a text like input, extract all the unique words, and send the word list to its downstream widgets. This type of input from upstream widget is calledINCOMING
. -
DisplayVocabulary
- accepts a word list and displays it.
Basically, a widget represents some data, a task, or visualization of some output. It can, of course, represent more and have combined functions. For example, we can merge the InputText
and ExtractVocabulary
widgets into a new one that directly takes input from its dialog and generates vocabulary for downstream widgets. We can even merge all three of them. You might ask, why do we still need a pipeline? All we need is a single function that does all the things.
The idea is that, by splitting problems into atomic data types and tasks, we can achieve maximum reusability and flexibility. To elaborate, consider things we can do with the three example widgets:
- We can reuse the text in
InputText
for other tasks. - We can use the vocabulary from
ExtractVocabulary
for tasks other than showing it inDisplayVocabulary
. For example, we can connect it to a widget that saves a list of words to a file. - We can use other ways to provide input for
ExtractVocabulary
. For example, the text can be from a widget that loads a.txt
file.
Connections between widgets are made by linking the anchors (small rectangles or diamonds around the widgets). A white anchor is for input, while a cyan one is for output. A rectangle anchor allows multiple connections, while a diamond anchor allows a single connection. The following rules determine if two anchors can connect:
- One of them is for input, the other is for output, i.e., they have different directions.
- Connecting them won't create a circular reference.
- The connection between their data types is legit.
Again, subclassing Anchor
is not necessary unless you want to change some behaviors (e.g., allow circular reference). To define legit connections, subclass ANCHOR_REGULAR
to create anchor types, then provide a list of legit connections between them.
For InputText
, create ANCHOR_TEXT
for the output. For ExtractVocabulary
, create ANCHOR_TEXT_LIKE
for the input and ANCHOR_VOCABULARY
for the output. For DisplayVocabulary
, just use the same ANCHOR_VOCABULARY
anchor type:
class ANCHOR_TEXT(ANCHOR_REGULAR): pass
class ANCHOR_TEXT_LIKE(ANCHOR_REGULAR): pass
class ANCHOR_VOCABULARY(ANCHOR_REGULAR): pass
In the __init__.py
, add:
ANCHORS = [
(False, ANCHOR_TEXT, ANCHOR_TEXT_LIKE),
(False, ANCHOR_VOCABULARY, ANCHOR_VOCABULARY),
]
Each tuple in the ANCHORS
represents a legit connection. The first element is a Boolean value that tells LabPype if the reverse direction between the anchor types is allowed. The second and the third elements are the anchor types for the output and the input respectively. Therefore, (False, ANCHOR_TEXT, ANCHOR_TEXT_LIKE)
means a connection from an output anchor of type ANCHOR_TEXT
to an input anchor of type ANCHOR_TEXT_LIKE
is allowed, while a connection from an output anchor of type ANCHOR_TEXT_LIKE
to an input anchor of type ANCHOR_TEXT
is not allowed.
Then, in the __init__.py
, tell LabPype what widgets are in this widget package:
WIDGETS = [
"MyWidgets",
("#00c0ff", InputText),
("#00c0ff", ExtractVocabulary),
("#00c0ff", DisplayVocabulary),
]
The first element of WIDGETS
is the group name. After that, each tuple has two or three elements: color in hex format, the widget class, and an optional relative path of the image to be used as an icon.
Try to build some widgets with the help of Class reference.