Tools are processing units that operate on specific data elements.
interface.
. In this case,
is used under the hoods.
If a data element is selected, a list of tools that can operate on that data should appear.
Double clicking on the tool will open an associated view (for non-task tools) or a dialog for settings parameters (for task tools).
Up to now you have made you task globally available to the system by specifying an instance of that task within the __init__.py
file of your sub-system, e.g.:
# __init__.py file
compute = ComputeTask()
To make your task appear in the "Tasks" view, you need to add the following lines:
from herschel.ia.task.views import TaskToolRegistry
toolRegistry = TaskToolRegistry.getInstance()
toolRegistry.register(compute)
For PACS users, this __init__.py
file is located at $install_dir/data/toolbox/your_sub_system.
You can also specify that your task belongs to one or more Category
:
from herschel.ia.gui.kernel.Tool import Category
toolRegistry.register(compute, [Category.IMAGE, Category.PACS]))
Your task will now be enabled whenever a session variable is selected which matches the type of the first input parameter within your task!
Within your task, you can control which parameter signs-up to be the prime parameter (the one which reacts on a selected data variable) by the Task API:
class ComputeTask extends Task {
ComputeTask() {
super("compute");
prime = new TaskParameter("spectrum", SpecificProduct.class)
:
getSignature().setPrimeInput(prime)
}
}
Naming conventions for task when to be registered in Hipe should follow this example assuming that the task will perform the functionality named "reduce" :
Name of the Class : ReduceTask
Name of the Task (getName()) : reduce
Name of the variablein Jython : reduce
The registration of the Modifier is done again in the __init__.py
via the Extension Registry with the usual syntax (please note the name of the factory: factory.modifier).
Be aware that the registration is system wise so the registration overrides any other registered modifier for that type.
REGISTRY.register(COMPONENT,Extension(
"MyModifier",
"herschel.ia.mymodifier.MyModifier",
"factory.modifier",
"herschel.ia.MyClass")
In case the Modifier you have created is only applicable to a specific task or even to a specific parameter of a specific task you can simply assign it to the Task Parameter itself:
// In your task constructor
TaskParameter parameter = new TaskParameter("input", String.class);
parameter.setModifier(new MyModifer());
If a modifier is associated to a task parameter in the task constructor, like in the example above, it is instantiated during initialization, when the task is created. This means that the JComponents used for your Modifier may not have the same GUI theme than the application, because the GUI theme is set just after the initialization of the registry. In that case, you may consider to write a custom Signature Component instead, which would be instantiated after the theme is set.
In case the default input area based on Modifiers doesn't fit your needs you can just replace it by your own implementation.
Rotate Alternative Signature:
If this is the case you need to:
- Implement a Task Signature Component
- Register it to the system.
Implement a Task Signature Component
The TaskSignatureComponent
interface consists of four explicit contracts
- Support the
setVariableSelection
for initial assignment from the Tool Window
- Assign the Signature to display (
setSignature
)
- Return a map of parameters and assigned values (in
Map<TaskParameter, VariableSelection> getParameters
)
- Clear and check user inputs (used by the default buttons)
and the two implicit contracts inherited by the Extension Registry
- Be JComponent
- Have an empty constructor
An easy way of implementing TaskSignatureComponent
is by extending JTaskSignatureComponent
and providing your own implementation for the makeModifierMap()
method.
For example, if you want to use a custom Signature Component that just wants to use JFilePathModifier
for a parameter aimed for a file name, you could do it like this:
public class MySignatureComponent extends JTaskSignatureComponent {
private static final long serialVersionUID = 1L;
protected Map<TaskParameter, Modifier> makeModifierMap() {
SignatureApi signature = getSignature();
Map<TaskParameter, Modifier> m = new LinkedHashMap<TaskParameter, Modifier>();
m.put(signature.getTaskParameter("file"), new JFilePathModifier(SAVE));
m.put(signature.getTaskParameter("number"), new JIntegerModifier());
return m;
}
}
Register a Task Signature Component
The registration of the Task Signature Component is done again in the __init__.py
via the Extension Registry with the usual syntax (please note the name of the factory: factory.editor.tool.task.signature).
REGISTRY.register(COMPONENT,Extension(
"Rotate Signature",
"herschel.ia.task.example.RotateSignatureComponent",
"factory.editor.tool.task.signature",
"herschel.ia.image.Rotate"))
See also the Extension Registry documentation for more details.
Custom Task Dialogs
Eventually, if the above options still do not accommodate you needs you can replace the the default Task Panel with your own implementation
If this is the case you need to:
- Implement a Task Panel
- Register it to the system.
Implement a Task Panel
The TaskPanel
interface consists of three explicit contracts
- Support the setVariableSelection for initial assignment from the Tool Window
- Assign the Task to display ( the setTask)
- Notify request of executions to the framework by
and the two implicit contracts inherited by the Extension Registry
- Be JComponent
- Have an empty constructor
The Rotate Panel example (herschel.ia.task.example.RotatePanel):
Register a Task Panel
The registration of the Task Panel Component is done again in the __init__.py
via the Extension Registry with the usual syntax (please note the name of the factory: factory.editor.tool.task.signature).
REGISTRY.register(COMPONENT,Extension(
"Rotate Task Panel",
"herschel.ia.task.example.RotatePanel",
"factory.editor.tool.task",
"herschel.ia.image.Rotate"));
See also the Extension Registry documentation for more details.
Task compliance
- Write user documentation (jtags)! That will be automatically picked up whenever a user asks the system for help on your task.
- The name of the task should be a legal variable name in the global name-space. For example your instance of
MyTask
should report itself as e.g.: "myTask" and not "This is my task".
- If your prime parameter is not the first parameter in your task, specify the prime parameter using the
setPrimeInput
method in the signature
- Write a parameter validator for your prime parameter if your task should be listed not only on prime data type but on prime data contents as well.
Recommendations for simple tasks and limitations
- Use a function that fully initializes TaskParameters: you avoid working with partially initialized data or data with non obvious defaults.
- Please take care to register your task properly: otherwise it may half-work, which is much worse than not working at all!
- Capture exceptions and check your params before trying to properly execute, if you want to provide better error reporting than the one provided by default.
- You should not do user interaction after you have started execute() (not on EDT).
- Validators: you cannot compare multiple parameters, order of execution is not specified, will be executed several times. So, if you need to validate dependent parameters, it should be done later.
- Once you are in execute() console has already been updated with the command : Although you can change the value of TaskParameters, this will not be properly reflected on the UI, so don't do it. So once you are on execute() you can check , possibly abort, and properly execute (nothing else).
Adding a Tool that is not a Task
If you have an existing task and want to make it available in HIPE, you just need to follow the steps described in the above section.
Now, a task has its limitations. It is somewhat an atomic operation for which you provide some inputs and expect some result.
Therefore, it is not expected for acting interactively with a user, and it is not meant for holding internal status either, that a user can modify during its execution.
If you need more flexibility, you can write your own implementation of the Tool
interface.
Besides, you would most probably need a viewer associated to your tool, for letting the user interact with it.
This follows in some way the MVC pattern: your target data is the Model, your associated viewer is the View, and your tool is the Controller.
Tool Implementation
The Tool
interface is simple:
public interface Tool {
// Known categories to which a tool may belong to
public enum Category { SPIRE, PACS, HIFI, GENERAL, IMAGE }
// Get the tool name
String getName();
// Get the actual object that does the work
Object getToolObject();
// Set the actual object that does the work
void setToolObject(Object o);
// Get an array of categories to which this tool belongs to
Category[] getCategories();
// Return the prime input parameter
Parameter getPrimeInput();
}
You provide the variable types you are interested in within the prime input: just return a ToolParameter
initiated with the proper class of data you want to handle.
private ToolParameter _prime = new ToolParameter("data", MyTargetDataType.class);
public Parameter getPrimeInput() { return _prime; }
The actual job to be done can be delegated to a third object (the "tool object"), or just be executed by the tool class itself.
In this latter case, the method Object getToolObject()
should return this
.
Moreover, you may return the categories you think the tool is meaningful for, through the proper implementation of Category[] getCategories()
.
Tool Viewer
Every tool has an associated viewer, which must implement EditorComponent
(by extending AbstractEditorComponent
or one of its subclasses).
Tool Registry
Once you have your tool and the corresponding viewer, you need to register them like this:
# Associate the tool with the viewer
REGISTRY.register(COMPONENT,Extension(
"My Tool",
"herschel.path.to.MyToolComponent",
"factory.editor.tool",
"herschel.path.to.MyTool"))
# Register the tool so it is automatically available for the proper variables in HIPE
from herschel.ia.gui.kernel import ToolRegistry
from herschel.path.to import MyTool
ToolRegistry.getInstance().register(MyTool())
Communicating Tool & Viewer
In the viewer, you can access the tool and the selected data within the makeEditorContent
method provided by AbstractEditorComponent
.
At this point, you can let the tool know about the viewer as well, if you want:
protected boolean makeEditorContent() {
// Get the tool and the selected data
ToolSelection selection = getSelection();
Tool tool = selection.getTool();
Object data = selection.getSelection().getValue();
// Optional - you would need to provide a setViewer method
((MyTool)tool).setViewer(this);
// Build the editor contents ...
}
Simple sample
This simple reproducible example wraps up the just explained steps altogether.
It is just a button whose label is changed by the tool when the user clicks on it:
1. The tool class
public class ButtonTool implements Tool {
private Category[] _categories = { Category.GENERAL };
private ToolParameter _prime = new ToolParameter("data", ArrayData.class);
private ArrayData _data;
private boolean _flag = true;
public Category[] getCategories() {
return _categories;
}
public String getName() {
return "Button Tool";
}
public Parameter getPrimeInput() {
return _prime;
}
public Object getToolObject() {
return this;
}
public void setToolObject(Object o) {
// do nothing
}
public void setData(ArrayData data) {
_data = data;
}
void updateLabel(JButton button) {
int size = _data.getSize();
int rank = _data.getRank();
button.setText("Data has " + (_flag? "size " + size : "rank " + rank));
_flag = !_flag;
}
}
2. The viewer class
public class ButtonToolComponent extends AbstractEditorComponent<ToolSelection> {
private static final long serialVersionUID = 1L;
private static int _counter = 1;
private ButtonTool _tool;
public ButtonToolComponent() {
super(new BorderLayout());
}
protected Class<ToolSelection> getSelectionType() {
return ToolSelection.class;
}
protected boolean makeEditorContent() {
final JButton button = new JButton();
setName("Button Tool " + _counter++);
_tool = (ButtonTool)getSelection().getTool();
_tool.setData((ArrayData)getSelection().getSelection().getValue());
_tool.updateLabel(button);
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
_tool.updateLabel(button);
}
});
add(button);
return true;
}
public Icon getComponentIcon() {
return IconLibrary.VARIABLE;
}
}
3. The registration
REGISTRY.register(COMPONENT,Extension(
"Button Tool",
"herschel.your.package.ButtonToolComponent",
"factory.editor.tool",
"herschel.your.package.ButtonTool"))
from herschel.ia.gui.kernel import ToolRegistry
from herschel.your.package import ButtonTool
ToolRegistry().getInstance().register(ButtonTool())
4. Executing the example
For executing this simple tool, just include it in a package owned by you, open the workbench in HIPE, and execute the following in the console:
x = Int1d.range(12)
y = Double2d([[1,2,3],[4,5,6]])
Then open the x and y variables with the Button Tool and click the button: its label is updated by the tool.
Triggering Events
For a full detailed section about triggering events have a look at DpHipeCommonUtilities.