Plugin Development

The MAP Client Manual provides thorough information on MAP Client plugin development.

Plugin Walkthrough

Let’s take a look at how the key components of a simple plugin are implemented in real life. We will look at the TRC Frame Selector Step plugin. This step takes as input data from a TRC file and outputs the coordinates of each marker at a user-configured frame. You can view its source code on the Github website or you can clone the repository and view the source code locally.

Browse to trcframeselectorstep/mapclientplugins/trcframeselectorstep/step.py. Every step must have a step.py which must contain a the step’s class derived from mapclient.mountpoints.workflowstep.WorkflowStepMountPoint, e.g.:

class TRCFrameSelectorStep(WorkflowStepMountPoint):

In the class’s __init__ method, the step name, category, icon, ports, and default configurations are defined:

def __init__(self, location):
    super(TRCFrameSelectorStep, self).__init__('TRC Frame Selector', location)
    self._configured = False
    self._category = 'Anthropometry'
    # icon
    self._icon = QtGui.QImage(':/trcframeselectorstep/images/trcframeselectoricon.png')
    # Ports:
    self.addPort(('http://physiomeproject.org/workflow/1.0/rdf-schema#port',
                  'http://physiomeproject.org/workflow/1.0/rdf-schema#uses',
                  'http://physiomeproject.org/workflow/1.0/rdf-schema#trcdata'))
    self.addPort(('http://physiomeproject.org/workflow/1.0/rdf-schema#port',
                  'http://physiomeproject.org/workflow/1.0/rdf-schema#uses',
                  'integer'))
    self.addPort(('http://physiomeproject.org/workflow/1.0/rdf-schema#port',
                  'http://physiomeproject.org/workflow/1.0/rdf-schema#provides',
                  'http://physiomeproject.org/workflow/1.0/rdf-schema#landmarks'))

    # configuration defaults
    self._config = {}
    self._config['identifier'] = ''
    self._config['Frame'] = '1'

    # other private attributes of the step
    self._trcdata = None
    self._inputFrame = None
    self._landmarks = None

The execute method performs the main work of the step. In this case, it is extracting the marker coordinates at a particular frame:

def execute(self):

    ...

    landmarksNames = self._trcdata['Labels']
    try:
        time, landmarksCoords = self._trcdata[frame]
    except KeyError:
        print('Frame {} not found'.format(frame))
        raise KeyError

    landmarksNamesData = [frame, time] + landmarksCoords
    self._landmarks = dict(zip(landmarksNames, landmarksNamesData))
    if 'Frame#' in self._landmarks:
        del self._landmarks['Frame#']
    if 'Time' in self._landmarks:
        del self._landmarks['Time']

    for k, v in self._landmarks.items():
        self._landmarks[k] = np.array(v)
    self._doneExecution()

In a simple plugin, all the “execution” code can be contained in the execute method. For more complicated steps however, we recommend creating a separate function, possibly in a separate module, that execute calls. The self._doneExecution() call at the end notifies the MAP Client that this step has finished execution.

The setPortData and getPortData methods assign and returns values to and from step class attributes, respectively:

def setPortData(self, index, dataIn):
    if index == 0:
        self._trcdata = dataIn # trcdata
    else:
        self._inputFrame = dataIn # integer

def getPortData(self, index):
    return self._landmarks # ju#landmarks

Ports (input and output combined) are numbered in the order they are defined in the __init__ method.

The configure method launches a dialogue to accept user-defined configuration parameters:

def configure(self):
    '''
    This function will be called when the configure icon on the step is
    clicked.  It is appropriate to display a configuration dialog at this
    time.  If the conditions for the configuration of this step are complete
    then set:
        self._configured = True
    '''
    dlg = ConfigureDialog(QtGui.QApplication.activeWindow().currentWidget())
    dlg.identifierOccursCount = self._identifierOccursCount
    dlg.setConfig(self._config)
    dlg.validate()
    dlg.setModal(True)

    if dlg.exec_():
        self._config = dlg.getConfig()

    self._configured = dlg.validate()
    self._configuredObserver()

The getIdentifier and setIdentifier methods allow MAP Client to set and get the name of the step:

def getIdentifier(self):
    '''
    The identifier is a string that must be unique within a workflow.
    '''
    return self._config['identifier']

def setIdentifier(self, identifier):
    '''
    The framework will set the identifier for this step when it is loaded.
    '''
    self._config['identifier'] = identifier

These methods are automatically generated by the Plugin Wizard. No customiation is needed.

The serialize and deserialize methods allow the MAP Client to write step configurations to file and read them from file, respectively:

def serialize(self):
    return json.dumps(self._config, default=lambda o: o.__dict__, sort_keys=True, indent=4)

def deserialize(self, string):
    '''
    Add code to deserialize this step from disk. Parses a json string
    given by mapclient
    '''
    self._config.update(json.loads(string))

    d = ConfigureDialog()
    d.identifierOccursCount = self._identifierOccursCount
    d.setConfig(self._config)
    self._configured = d.validate()

The step.py file along with the step class and the methods above are automatically generated by the MAP Client Plugin Wizard. For simple plugins, only the __init__, execute, getPortData, and setPortData method need to be added to by the developer.

Writing some Simple Plugins

The MAP Client Plugin Wizard helps writing new plugins by generating the boiler-plate code and folder structure for any plugins. We will use the Plugin Wizard to write two simple plugin.

  1. Run MAP Client, go to tools>Plugin Wizard. Follow the steps here to write a simple plugin called “Float Source” that outputs a user-configured float. The output port data type can be http://physiomeproject.org/workflow/1.0/rdf-schema#images.
  2. Run the Plugin wizard again. This time write a plugin called “Float Sum” that accepts two floats and prints to terminal and outputs their sum.
  3. Create a new workflow to test these 2 plugins.