Custom Tools
This guide will give you an overview of creating a new Tool. This is applicable for Tools being built within the library as well as Tools for 3rd party plugins.
See the Third-Party functionality guide for more details on the integration and extension options available.
Choosing A Base Class
There are 3 base classes to choose from when building a tool:
BaseTool
The BaseTool
is the fundamental base class, with just the functionality required to function within the Cornerstone Tools framework. This is the base class to choose if the Tool you wish to create won't have its own annotation data (.e.g MagnifyTool
), or only interacts with a different Tool's data (e.g. FreehandRoiSculptorTool
). The other two base classes BaseAnnotationTool
and BaseBrushTool
both inherit from BaseTool
.
A BaseTool
is also the Tool type you should derive from when making a tool that interacts with the labelmap data, and isn't a brush. These are dubbed "Segmentation Tools".
BaseAnnotationTool
The BaseAnnotationTool
inherits from BaseTool
, and is intended for any Tool that will create/modify and display its own annotation data on the canvas (e.g. LengthTool
).
BaseBrushTool
The BaseBrushTool
inherits from BaseTool
and is intended specifically for Tools that want to create/modify/delete segmentation data (e.g. BrushTool
). Potential subclasses could include adaptive brush Tools, or region growing Tools that require a seed area to be drawn.
Creating Your Tool
Once you have an appropriate base class chosen (we will use the BaseTool
in this example), you can extend it to start building your Tool. In this example we shall make a Tool that logs 'Hello cornerstoneTools!'
to the console on every mouse click.
Class Definition
By convention the class name should be in PascalCase, and suffixed with Tool
.
For example we shall call our wonderful tool HelloWorldTool
:
import csTools from 'cornerstone-tools';
const BaseTool = csTools.importInternal('base/BaseTool');
// NOTE: if you're creating a tool inside the CornerstoneTools repository
// you can import BaseTool directly from `src/tools/base`.
export default class HelloWorldTool extends BaseTool {
constructor(name = 'HelloWorld') {
super({
name,
supportedInteractionTypes: ['Mouse'],
});
}
}
The constructor must call super()
, which passes an object to the constructor of the superclass (BaseTool
, in this case, but the same object is passed to BaseAnnotationTool
or BaseBrushTool
). The object passed may have the following properties:
Property | Requirement | description |
---|---|---|
name | Mandatory | The name of the Tool. |
supportedInteractionTypes | Mandatory | An array of strings listing the interaction types, mouse and/or touch . |
strategies | Optional | If your Tool has multiple strategies of operation, you may pass an array of functions for each strategy here (see the RotateTool for a good example). |
defaultStrategy | Optional | If you have multiple strategies, this one should be used by default (pass a string identical to the strategy function name). |
configuration | Optional | An object with configurable properties used by your Tool. It may include your Tool's sensitivity, how an annotation displays when rendered, etc. |
mixins | Optional | An array of mixins (commonly used behaviours/functionality) to add to the Tool. |
For our simple Tool we pass only the two mandatory fields to super
. For the Tool's own constructor, it must at minimum take name
as a parameter, and it must have a default value. By convention the default name is the same as the classname, minus the Tool
suffix.
Adding Mixins
Next you can add any mixins you wish to add to the Tool. These are passed to super
, and initialized in BaseTool
. For our example, our Tool only makes sense in Active
or Disabled
modes, as it has none of its own data, and logs 'Hello cornerstoneTools!'
on click, as such we shall include the activeOrDisabledBinaryTool
mixin:
import csTools from 'cornerstone-tools';
const BaseTool = csTools.importInternal('base/BaseTool');
export default class HelloWorldMouseTool extends BaseTool {
constructor(name = 'HelloWorldMouse') {
super({
name,
supportedInteractionTypes: ['Mouse'],
mixins: ['activeOrDisabledBinaryTool'],
});
}
}
You need not import any mixins to your class file; this is dealt with in BaseTool
.
Mode Change Callbacks
In the Cornerstone Tools framework, if a Tool changes mode, an appropriate callback is called if the Tool has one. These are, quite simply:
Active
-activeCallback (element)
Passive
-passiveCallback (element)
Enabled
-enabledCallback (element)
Disabled
-enabledCallback (element)
Note that unlike a lot of the callbacks, the element
on which the Tool resides is passed to the mode change callbacks, not evt
.
For our example Tool, this gives us more chances to log hello to the console:
import csTools from 'cornerstone-tools';
const BaseTool = csTools.importInternal('base/BaseTool');
export default class HelloWorldTool extends BaseTool {
constructor(name = 'HelloWorld') {
super({
name,
supportedInteractionTypes: ['Mouse'],
mixins: ['activeOrDisabledBinaryTool'],
});
}
activeCallback(element) {
console.log(`Hello element ${element.uuid}!`);
}
disabledCallback(element) {
console.log(`Goodbye element ${element.uuid}!`);
}
}
Event Dispatcher Callbacks
Here we can add the meat of our Tool. Event dispatchers check for methods on Tools and fire them when appropriate.
TODO: List them all?? Or just link to api? Not sure yet. TODO: An actual useful description.
For our Tool, we want to log to the console on mouse click. BaseTool
has two appropriate methods: preMouseDownCallback
and postMouseDownCallback
. These fire before or after other annotation data on the canvas has a chance to claim the mouse click. for our Tool we shall choose preMouseDownCallback
, as it's always nice to say hello before doing anything else. The method can simply be defined and it shall be called when appropriate via the mouseToolEventDispatcher
:
import csTools from 'cornerstone-tools';
const BaseTool = csTools.importInternal('base/BaseTool');
export default class HelloWorldTool extends BaseTool {
constructor(name = 'HelloWorld') {
super({
name,
supportedInteractionTypes: ['Mouse'],
mixins: ['activeOrDisabledBinaryTool'],
});
}
preMouseDownCallback(evt) {
console.log('Hello cornerstoneTools!');
}
activeCallback(element) {
console.log(`Hello element ${element.uuid}!`);
}
disabledCallback(element) {
console.log(`Goodbye element ${element.uuid}!`);
}
}