decorators

decorators

showWaitCursorWhileExecuting

ORSServiceClass.decorators.decorators.showWaitCursorWhileExecuting(func)

This decorator is used to change the cursor to busy while executing a method/function.

run_in_background_no_waiting

ORSServiceClass.decorators.decorators.run_in_background_no_waiting(func)

Decorator intended to make a function run in a separate thread (asynchronously).

Example:

@run_in_background_no_waiting
def task1():
    do_something

@run_in_background_no_waiting
def task2():
    do_something_too

t1 = task1()
t2 = task2()
...
t1.join()
t2.join()

run_in_background_no_waiting_auto_stop

ORSServiceClass.decorators.decorators.run_in_background_no_waiting_auto_stop(func)

Decorator intended to make a function run in a separate thread (asynchronously). The thread will stop on system exit.

Example:

@run_in_background_no_waiting_auto_stop
def task1():
    do_something

@run_in_background_no_waiting_auto_stop
def task2():
    do_something_too

t1 = task1()
t2 = task2()
...
t1.join()
t2.join()

run_in_background_waiting_until_finished

ORSServiceClass.decorators.decorators.run_in_background_waiting_until_finished(func)

Decorator intended to make a function run in a separate thread (synchronously).

Example:

@run_in_background_waiting_until_finished
def task1():
    do_something

task1()

printCaller

ORSServiceClass.decorators.decorators.printCaller(func)

This decorator prints the caller of the function.

printCallerWitArgs

ORSServiceClass.decorators.decorators.printCallerWitArgs(func)

This decorator prints the caller of the function (and the parameters).

infrastructure

interfaceMethod

ORSServiceClass.decorators.infrastructure.interfaceMethod(func)

This decorator is used to declare a method as an interface method. It can decorate only class methods.

This decorator takes care of the call to the logger with the given arguments. If the interface method has to be called without logging, call the interface method with the optional argument logging at False. This could be used, for example, to perform the logging only at the release of a slider motion instead of doing it at every slider value change.

The decorated method should contain a docstring written in reStructuredText. This docstring is used for validation and logging purposes.

Basic structure

The docstring of the method contains:

  • one or more paragraphs of text describing what the method does;

  • the declaration of all input arguments. These fields are used for each input argument:

  • the declaration of all output arguments. These fields are used for each output argument:

    Note that, when a single output is returned, there is no need to specify a name with the field declaration. However, when multiple outputs are declared, names should be given to each of these output variables and the method should always return all of these variables in the same order as they are declared in the docstring. The return values don’t have to be assigned to a variable. If they are assigned, the names of the variables in the code don’t have to match the names declared in the docstring (but it is recommended to use the same names for clarity).

@classmethod
@interfaceMethod
def createROIFromStructuredGrid(cls, aStructuredGrid, aTitle, fillROI):
    """
    This method creates a ROI with the shape of the given StructuredGrid instance
    (a Channel, a ROI or a MultiROI).
    The given title is set for the new ROI.

    A publish is also made on the new ROI so that it can appear in the list of objects.

    :param aStructuredGrid: the StructuredGrid instance (Channel, ROI or MultiROI) used as reference
        for the shape of the new ROI.
    :type aStructuredGrid: ORSModel.ors.StructuredGrid
    :param aTitle: title of the new ROI.
    :type aTitle: str
    :param fillROI: if True, the ROI will be filled.
        If False, the ROI will be empty.
    :type fillROI: bool

    :return: created ROI.
        This ROI has the same shape as the input StructuredGrid instance.
    :rtype: ORSModel.ors.ROI
    """

    if aStructuredGrid is None:
        return None

    # Creating a new ROI
    aNewROI = ROI()

    # Copying the shape of the StructuredGrid instance and setting the title
    aNewROI.copyShapeFromStructuredGrid(aStructuredGrid)
    aNewROI.setTitle(aTitle)

    # At this point, the ROI is empty
    if fillROI:
        # Filling on itself
        aNewROI.getReversed(aNewROI)

    # Making the ROI known to all the interested plugins and services
    aNewROI.publish()

    # Returning the new ROI
    return aNewROI

Source code example:

  1. Download the compressed file;
  2. Extract these files into a plugin extension folder;
  3. Start the application;
  4. Open the top level menu Demos to see the menu item named Demo: open the plugin DemoBasicStructureInterfaceMethod. Click on that menu item to create an instance of the plugin and open his mainform;
  5. Import a dataset;
  6. Write the title to give to the new ROI in the mainform of the plugin (in the field Title);
  7. Select the check box Fill ROI so that the ROI will be filled;
  8. Press the button OK to generate the new ROI with these specifications;
  9. Open the Action Log Viewer (in the Tools menu) and go the end of the text to see the logging of this call with the corresponding values.

Using inputs as lists

@classmethod
@interfaceMethod
def getTotalSize(cls, aListOfFiles):
    """
    This method computes the total size of the given files.

    :param aListOfFiles: the files to examine.
    :type aListOfFiles: file
    :count aListOfFiles: [0, None]

    :return: total size in bytes
    :rtype: int
    """

    totalSizeInBytes = 0  # Initialization
    for aFile in aListOfFiles:
        statinfo = os.stat(aFile)
        currentFileSizeInBytes = statinfo.st_size
        totalSizeInBytes += currentFileSizeInBytes

    return totalSizeInBytes

Source code example:

  1. Download the compressed file;
  2. Extract these files into a plugin extension folder;
  3. Start the application;
  4. Open the top level menu Demos to see the menu item named Demo: open the plugin DemoInputAsListInterfaceMethod. Click on that menu item to create an instance of the plugin and open his mainform;
  5. Press the button Select files to open a file explorer. Select a few files to examine and press Open. The total size in bytes is written in the mainform of the plugin.

Using multiple outputs

@classmethod
@interfaceMethod
def getBiggestMasks(cls, aChannel):
    """
    This method computes the masks (ROIs) over a dataset on a set of ranges.
    The ranges are obtained by splitting the full range of the dataset in 10 equal parts.
    Each mask is made by taking all the values of the dataset on that range, including the limits of the range.
    An overlap may exist in some situations.

    From all the masks computed, the two having the greatest count of voxels are returned.

    A list of the voxel count for each range is also returned. Since the ranges are overlapping,
    the total count of voxel from that list may exceed the total voxel count of the dataset.

    :param aChannel: input dataset to analyze
    :type aChannel: ORSModel.ors.Channel

    :return biggestMask: the mask having the most voxels
    :rtype biggestMask: ORSModel.ors.ROI
    :return secondBiggestMask: the mask having the most voxels after biggestMask
    :rtype secondBiggestMask: ORSModel.ors.ROI
    :return listVoxelCountInEachRange: list of the voxel count in each range
    :rtype listVoxelCountInEachRange: int
    :rcount listVoxelCountInEachRange: 10
    """

    if aChannel is None:
        # Returning all outputs with default values
        return None, None, 10*[0]

    IProgress = None
    outputROI = None

    listMasks = []  # Initialization
    listVoxelCountInEachRange = []  # Initialization
    minimalValue = aChannel.getMinimumValue()
    maximalValue = aChannel.getMaximumValue()
    step = (maximalValue - minimalValue)/10
    for stepIndex in range(10):
        # Computing the range limits
        minimalValueInRange = minimalValue + stepIndex*step
        maximalValueInRange = minimalValue + (stepIndex+1)*step

        # Getting the mask
        maskInRange = aChannel.getAsROIWithinRange(minimalValueInRange, maximalValueInRange, IProgress, outputROI)

        # Keeping the mask as a temporary result
        listMasks.append(maskInRange)

        # Getting the voxel count in this mask
        timeIndex = 0
        voxelCountInMask = maskInRange.getVoxelCount(timeIndex)
        listVoxelCountInEachRange.append(voxelCountInMask)

    # Finding the biggest masks
    # Sorting on the voxel count
    combinedListMaskAndVoxelCount = [(aMask, aCount) for aMask, aCount in zip(listMasks, listVoxelCountInEachRange)]
    combinedListMaskAndVoxelCount.sort(key=lambda val:val[1], reverse=True)

    biggestMask, _ = combinedListMaskAndVoxelCount.pop(0)
    secondBiggestMask, _ = combinedListMaskAndVoxelCount.pop(0)

    # Deleting all other temporary ROIs
    while len(combinedListMaskAndVoxelCount) > 0:
        aMask, _ = combinedListMaskAndVoxelCount.pop(0)
        aMask.deleteObject()

    # Setup of ROIs to return
    biggestMask.setTitle('Biggest mask')
    biggestMask.setInitialColor(orsColor(1, 0, 0, 0.5))
    biggestMask.publish()

    secondBiggestMask.setTitle('Second biggest mask')
    secondBiggestMask.setInitialColor(orsColor(0, 1, 0, 0.5))
    secondBiggestMask.publish()

    return biggestMask, secondBiggestMask, listVoxelCountInEachRange

Source code example:

  1. Download the compressed file;
  2. Extract these files into a plugin extension folder;
  3. Start the application;
  4. Open the top level menu Demos to see the menu item named Demo: open the plugin DemoMultipleOutputsInterfaceMethod. Click on that menu item to create an instance of the plugin and open his mainform;
  5. Import a dataset;
  6. Press the button OK to compute the masks for a set of 10 equal ranges of the dataset. The 2 ROIs having the most voxels are created. The relative voxel counts for each range are displayed on the UI.

action

ORSServiceClass.decorators.infrastructure.action(state='', title='', allowOutsideWorkingArea=False, condition=None)

This decorator is used to declare a method as containing an action.

The method decorated by this decorator should return a valid instance of Action (ORSServiceClass.actionAndMenu.action.Action) with all the required values.

The first argument (state) is the state associated to that action.

The second argument (title) is the string visible in the Configurable Actions section of the preferences, to identify the action.

The third argument (allowOutsideWorkingArea) is to allow the state-dependent action to be triggered even when the cursor is not on a view.

The fourth argument (condition) is an optional condition to be respected for the action to be triggered. This condition is provided as a Python method to be evaluated at the moment the action may be triggered. The action will be triggered only if that method returns True.

Processing sequence

When a key or mouse is pressed, the application looks for an associated action to be triggered. It starts by looking for actions having specified a state matching the current state of the application. The actions specifying a condition are examined first (the method of the condition is called to determine if the action is applicable), then the action without condition (if existing). If no such action has been found or these actions are not applicable, the actions having no state specification are examined. The actions specifying a condition are examined first (the method of the condition is called to determine if the action is applicable), then the action without condition (if existing).

When an action is triggered, a 3-stages mechanism is started:

  1. EnterAction stage: the enterAction string of the action definition is executed once;
  2. StayAction stage: the action string of the action definition is executed repetitively. When using a key press, this is done at least once, and repetitively until the key is released. When using a mouse click, this is called at each mouse movement (it is never called if the mouse doesn’t move between the mouse click and the mouse button release);
  3. ExitAction stage: the exitAction string of the action definition is executed once.

Flowcharts explaining the analysis of Qt events and translation into actions are provided in this document.

In-depth explanations about the processing of events into actions are provided in this video:

Plugin used in this video: compressed file.

Class action

@classmethod
@action(title='Open the plugin DemoClassAction')
def actionOpenGUI(cls):
    anAction = Action(enterAction=cls.getActionStringForStartupDefault(),
                      action='',
                      exitAction='')
    return anAction

Source code example:

  1. Download the compressed file;
  2. Extract these files into a plugin extension folder;
  3. Start the application;
  4. Open the Preferences and look in the Configurable Actions section for the name Open the plugin DemoClassAction. Set an unused keyboard key for that action, apply the changes and exit the Preferences;
  5. By using the specified action key, an instance of the plugin will be created and his mainform will be displayed.

Instance action

@action(title='Increment a counter in DemoInstanceAction')
def actionIncrementCounter(self):
    # The string in enterAction is an example of direct execution
    # The string in action is an example of call to an instance method
    anAction = Action(enterAction='{selfVarName}._triggersCount = 0; {selfVarName}.updateUI()'.format(selfVarName=self._varName),
                      action='{}.updateTriggersCount(1)'.format(self._varName),
                      exitAction='')
    return anAction

Note

A single instance will receive the trigger of an action even if there is multiple instances of the class. Therefore, it is recommended to use the mechanism of instance actions only when a single instance can be created. For plugins, this can be done by using the multiple=False definition flag. See ORSServiceClass.OrsPlugin.abstractPlugin.AbstractPlugin.

Source code example:

  1. Download the compressed file;
  2. Extract these files into a plugin extension folder;
  3. Start the application;
  4. Open the top level menu Demos to see the menu item named Demo: open the plugin DemoInstanceAction. Click on that menu item to create an instance of the plugin and open his mainform;
  5. Open the Preferences and look in the Configurable Actions section for the name Increment a counter in DemoInstanceAction. Set an unused keyboard key for that action, apply the changes and exit the Preferences;
  6. By using the specified action key, the counter will be reset when the key is pressed and will be incremented as long as the key is pressed. This value is shown on the UI.

State-dependent action

@action(state='stateDemoStateDependentAction_triggersA', title='Increment counter A in DemoStateDependentAction')
def actionIncrementCounterA(self):
    anAction = Action(enterAction='{selfVarName}._triggersACount = 0; {selfVarName}.updateUI()'.format(selfVarName=self._varName),
                      action='{}.updateTriggersCount("A", 1)'.format(self._varName),
                      exitAction='')
    return anAction

Source code example:

  1. Download the compressed file;
  2. Extract these files into a plugin extension folder;
  3. Start the application;
  4. Open the top level menu Demos to see the menu item named Demo: open the plugin DemoStateDependentAction. Click on that menu item to create an instance of the plugin and open his mainform;
  5. Open the Preferences and look in the Configurable Actions section for the name Increment counter A in DemoStateDependentAction. Set an unused keyboard key for that action. Set the same keyboard key for the action with the name Increment counter B in DemoStateDependentAction. Apply the changes and exit the Preferences;
  6. On the mainform of the plugin DemoStateDependentAction, press the button “Set the state for triggers A” to make responsive the action for the triggers A. Press the specified action key to make the counter A reset and increment as long as the key is pressed.
  7. On the mainform of the plugin DemoStateDependentAction, press the button “Set the state for triggers B” to make responsive the action for the triggers B. Press the specified action key to make the counter B reset and increment as long as the key is pressed.

Passing the event data

If the event data (instance of OrsEvent.eventdata.InputEventData) is required by an action method, it can be obtained by adding the word event in the action string.

Note

This is possible only in plugins extensions, not with generic action extensions.

@action(title='A title')
def anAction(self):
    anAction = Action(enterAction='{selfVarName}._enterAction(event)'.format(selfVarName=self._varName),
                      action='{}._action(myEvent=event)'.format(self._varName),
                      exitAction='{}._exitAction(-0.1, True, event)'.format(self._varName))
    return anAction

def _enterAction(self, myEvent):
    pass

def _action(self, myEvent):
    pass

def _exitAction(self, aValue, aBoolean, myEvent):
    pass

Conditional action

The actions can define a condition to be respected before it is considered as applicable. This may be used to specify the same key/mouse combination for different actions, especially those that are state-independent. Using the condition avoids the consumption of the event (and allow the other actions having the same key/mouse combination to be considered) and the call to the EnterAction stage when the conditional action is not applicable.

The condition is a Python method defined by the class.

The condition method receives the same event data instance (OrsEvent.eventdata.InputEventData) as provided to the action.

Conditional actions are supported for class actions and instance actions.

def actionACondition(self, eventData):
    return True

@action(title='A title', condition=actionACondition)
def actionA(self):
    anAction = Action(enterAction=f'{self._varName}._enterActionA(event)')
    return anAction

def _enterActionA(self, eventData):
    pass

Source code example:

  1. Download the compressed file;

  2. Extract these files into a plugin extension folder;

  3. Start the application;

  4. Go to the Preferences to define the keys/mouse buttons for these actions:

    Action Condition Key Mouse
    A Generate demonstration objects in DemoConditionAction
    • there should be no ROI nor VisualShapes3D
    LeftCtrl + I  
    B Inverts the selected ROI in DemoConditionAction
    • there should be exactly 1 selected ROI;
    • the view under cursor should be a 2D view;
    • the cursor should be on top of a painted voxel of that ROI.
    LeftCtrl + I  
    C Simplify the object title in DemoConditionAction
    • there should be exactly 1 selected object;
    • the title of that object should contain the character “(“.
    LeftCtrl + I  
    D Move shape center at cursor in DemoConditionAction
    • there should be exactly 1 selected VisualShape3D;
    • the selected VisualShape3D should be visible;
    • the view under cursor should be a 2D view.
    LeftCtrl + I  
    E Set current state as stateDemoConditionActionA
    • there should be exactly 1 selected ROI.
    Space Bar  
    F Selects the ROI under cursor in DemoConditionAction None   Left mouse
    G Probe the intersection of shapes in selected ROI in DemoConditionAction
    • there should be exactly 1 selected ROI;
    • the view under cursor should be a 2D view;
    • the point under cursor should be contained in the selected ROI;
    • there should be at least 1 visible instance of VisualShape3D in the current view.
      Left mouse
    H Add intersection of shapes in selected ROI in DemoConditionAction
    • there should be exactly 1 selected ROI;
    • the view under cursor should be a 2D view;
    • the point under cursor should be contained in the selected ROI;
    • there should be at least 1 visible instance of VisualShape3D in the current view.
    LeftCtrl Left mouse
    I Remove intersection of shapes from selected ROI in DemoConditionAction
    • there should be exactly 1 selected ROI;
    • the view under cursor should be a 2D view;
    • the point under cursor should be contained in the selected ROI;
    • there should be at least 1 visible instance of VisualShape3D in the current view.
    LeftShift Left mouse

    Apply the changes and exit the Preferences;

  5. Make sure that there is no ROI nor VisualShape (delete them all or start a new session). Press LeftCtrl + I to generate the demonstration objects (action A). This action works even when there is no plugin instance because it is a class action. Pressing again the combination LeftCtrl + I will not generate new objects because the condition for the action A is that there is no ROI nor VisualShape3D instance. Show the demonstration objects (2 ROIs, 3 VisualShapes), select them all and fit the view;

  6. Open the top level menu Demos to see the menu item named Demo: open the plugin DemoConditionAction. Click on that menu item to create an instance of the plugin and open his mainform;

  7. Select the green ROI. Bring the cursor on top of a painted area of the green ROI and press LeftCtrl + I to invert the painting (action B). Pressing again the combination LeftCtrl + I will not invert the ROI unless the mouse cursor is moved to another painted area of the ROI;

  8. Change the title of a dataset so that it contains a “(” or generate such an object (for example, by cropping a dataset). Select that object. Press LeftCtrl + I to remove the portion of the title after the first “(” (action C);

  9. Select a VisualShape, like the capsule. Make sure it is visible and the cursor is in the view. Press and hold LeftCtrl + I to move the center of the capsule at the location of the cursor (action D). Move the cursor while the key is pressed to move the center of the shape with the cursor;

  10. Select the green ROI. Press the Space Bar to set the current state to “stateDemoConditionActionA” (where the following actions depend on) (action E);

  11. In the state “stateDemoConditionActionA”, press the left-mouse button on a ROI in the view (action F). The selection is made for that ROI;

  12. In the state “stateDemoConditionActionA” and with a single ROI selected, press and hold the left-mouse button (action G) over that ROI in an area inside one or more shapes. There is a temporary ROI that is shown until the mouse button is released;

  13. In the state “stateDemoConditionActionA” and with a single ROI selected, press and hold the LeftCtrl and the left-mouse button (action H) over that ROI in an area inside one or more shapes. The painting of that ROI gets added in the intersection of the shapes;

  14. In the state “stateDemoConditionActionA” and with a single ROI selected, press and hold the LeftShift and the left-mouse button (action I) over that ROI in an area inside one or more shapes. The painting of that ROI gets removed in the intersection of the shapes.

interest

ORSServiceClass.decorators.infrastructure.interest(listSubjects=None)

This decorator is used to declare a method with an interest of specific entities. The decorated method is called when one of those entities is added or modified in the context of the plugin instance.

The first argument (listSubjects) is a single string or a list of strings. Each of these strings is the name of an entity. See Entities for the strings defined by the application.

The string ALL can be used to receive a call when any entity is added or modified.

Interest on entities defined by the application

from COMWrapper.ORS_def import OrsSelectedObjects
@interest(OrsSelectedObjects)
def interestSelectedObjects(self):
    # Do something
    pass

Source code example:

  1. Download the compressed file;
  2. Extract these files into a plugin extension folder;
  3. Start the application;
  4. Open the top level menu Demos to see the menu item named Demo: open the plugin DemoInterestOnApplicationDefinedEntities. Click on that menu item to create an instance of the plugin and open his mainform;
  5. Import a dataset;
  6. Draw a ruler;
  7. Select the dataset in the list of objects. The title of that object is written in the UI of the plugin with his class name Channel;
  8. Add the ruler in the selection. The title and class name VisualRuler of the ruler is added in the UI of the plugin.

Interest on user defined entities

@interest('DemoInterestOnUserDefinedEntities')
def interestDemoInterestOnUserDefinedEntities(self):
    # Do something
    pass

Source code example:

  1. Download the compressed file;
  2. Extract these files into a plugin extension folder. Note that there is 2 distinct plugins contained in the compressed file;
  3. Start the application;
  4. Open the top level menu Demos to see the menu item named Demo: open the plugin DemoInterestOnUserDefinedEntities A. Click on that menu item to create an instance of the plugin A and open his mainform;
  5. Open the top level menu Demos to see the menu item named Demo: open the plugin DemoInterestOnUserDefinedEntities B. Click on that menu item to create an instance of the plugin B and open his mainform. Move that window so that both UIs can be seen simultaneously;
  6. Change the value in the spinbox of the plugin A. That value is updated in the UI of the plugin B;
  7. Note that multiple instances of each plugin may be used. When the value of the spinbox of an instance of the plugin A is changed, all instances of the plugin B will be updated with that new value.