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()
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
loggingatFalse. 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:
param: mandatory. Paragraph explaining to the user what this argument will be used for;type: mandatory. Required type of this argument. See Types of arguments for the acceptable strings.count: optional. This could be used to specify that the input argument is a Python list containing a specific number of elements of the typetype. SeeORSServiceClass.OrsPlugin.InterfaceMethodArgumentDescriptors.argumentdescriptor.ArgumentDescriptor.determineCountValue().
the declaration of all output arguments. These fields are used for each output argument:
return: mandatory. Paragraph explaining to the user what this argument is;rtype: mandatory. Type of this argument. See Types of arguments for the acceptable strings.rcount: optional. This could be used to specify that the output argument is a Python list containing a specific number of elements of the typertype. This should be a fixed number (this is essentially for the purpose of playing back a recorded macro). SeeORSServiceClass.OrsPlugin.InterfaceMethodArgumentDescriptors.argumentdescriptor.ArgumentDescriptor.determineCountValue().
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:
- Download the
compressed file; - Extract these files into a plugin extension folder;
- Start the application;
- 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;
- Import a dataset;
- Write the title to give to the new ROI in the mainform of the plugin (in the field Title);
- Select the check box Fill ROI so that the ROI will be filled;
- Press the button OK to generate the new ROI with these specifications;
- 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:
- Download the
compressed file; - Extract these files into a plugin extension folder;
- Start the application;
- 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;
- 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:
- Download the
compressed file; - Extract these files into a plugin extension folder;
- Start the application;
- 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;
- Import a dataset;
- 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 returnsTrue.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:
- EnterAction stage: the
enterActionstring of the action definition is executed once; - StayAction stage: the
actionstring 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); - ExitAction stage: the
exitActionstring 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:
- Download the
compressed file; - Extract these files into a plugin extension folder;
- Start the application;
- 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;
- 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=Falsedefinition flag. SeeORSServiceClass.OrsPlugin.abstractPlugin.AbstractPlugin.Source code example:
- Download the
compressed file; - Extract these files into a plugin extension folder;
- Start the application;
- 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;
- 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;
- 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:
- Download the
compressed file; - Extract these files into a plugin extension folder;
- Start the application;
- 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;
- 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;
- 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.
- 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 wordeventin 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:
Download the
compressed file;Extract these files into a plugin extension folder;
Start the application;
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;
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;
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;
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;
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);
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;
Select the green ROI. Press the Space Bar to set the current state to “stateDemoConditionActionA” (where the following actions depend on) (action E);
In the state “stateDemoConditionActionA”, press the left-mouse button on a ROI in the view (action F). The selection is made for that ROI;
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;
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;
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.
- EnterAction stage: the
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
ALLcan 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:
- Download the
compressed file; - Extract these files into a plugin extension folder;
- Start the application;
- 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;
- Import a dataset;
- Draw a ruler;
- 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;
- 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:
- Download the
compressed file; - Extract these files into a plugin extension folder. Note that there is 2 distinct plugins contained in the compressed file;
- Start the application;
- 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;
- 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;
- Change the value in the spinbox of the plugin A. That value is updated in the UI of the plugin B;
- 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.
- Download the