Skip to content
Danny Brown edited this page Feb 5, 2019 · 4 revisions

This documentation is for a much older version of cornerstone-tools. While it may still loosely apply, more recent information can be found at https://tools.cornerstonejs.org

Tools often need to manage data at different scopes. Here are some examples:

  1. A length measurement tool could store state in the ImageId specific scope if the measurement only applied to a single ImageId. In this case, the length measurement would only appear when its corresponding imageId is visible. It would not be visible if the user displayed a different ImageId in the enabled element. If two different enabled elements displayed the same ImageId, it should show up on both. This is the most commonly desired behavior and is therefore the default for cornerstone.
  2. A ROI measurement could store state in the stack specific scope if the measurement applied to an image stack. In this case, the ROI would continue to be visible as the user changed to different ImageIds within the stack.
  3. A flag indicating whether or not the tool is active on an enabled image could store state in the enabled element specific scope. In this case, the tool would be able to keep track if it is enabled on an enabled element regardless of which ImageId is currently being displayed. NOTE - this is currently handled via the jquery eventData mechanism - we may need APIs for this if we decide to not use jquery for mouse event handling.
  4. A flag indicating whether or not the tool is active on all enabled images could store this state in the global scope. In this case, a window/level tool could be selected (via a button) and would be made the active tool for all visible enabled images. NOTE - this is not correctly supported, the developer needs to manage this and call enable/disable for tools manually. We may add an API for this in the future though.

This is one area of tool implementation that can increase the complexity of the tool logic and make it difficult to reuse. To address this, scope management is externalized from the tool entirely. Tool implementation is therefore constrained to respond to events for a single enabled element at a time. Whether or not the tool data that the tool is dealing with is related to a single imageId, a stack or some other scope is not its concern. It is believed that this strategy will work for the vast majority of tool needs (if not all) so it is used by all cornerstone tools.

There are use cases where a tool needs to be able to operate with multiple scopes. Using the examples above, there are situations where stack specific storage is desirable for a length measurement tool as well as image specific behavior for a ROI. Cornerstone supports this through the creation of a custom ToolStateManager.

In cornerstone, tool data scope is managed through what is called a ToolStateManager. Each enabled element has a ToolStateManager associated with it. By default, enabled elements shared a global tool state manager that stores tool data with ImageId specific scope. If a different scope is desired, a developer may create a new tool state manager that complies with the expected interface of a ToolStateManager and register it with the desired enabled elements.

NOTE: The scroll tool currently includes experimental support for stack specific storage of the length measurement tool. This might be removed though to keep things simple unless there is a generic tool that we want to include where stack specific storage is really important.


The setElementToolStateManager function is called to set the toolStateManager for a given enabled element

function setElementToolStateManager(element, toolStateManager)

Parameters:

  • element - the DOM element to enable
  • toolStateManager - the new toolStateManager

Returns: nothing


The getElementToolStateManager function is called to get the current toolStateManager for a given enabled element

function getElementToolStateManager(element)

Parameters:

  • element - the DOM element to enable

Returns: the toolStateManager associated with the element


The following are some notes and thoughts that impacted the design described above. It is here for reference purposes only and has not been updated to what we are currently doing.

NOTE: There may also be a difference between the state used during creation of a tool and persistence of that tool. For example, a stack specific tool could be created but when saved, saves it as image specific using the currently visible imageId. One could also envision a viewer requiring that the user explicitly "save" the tool on a given slice at which time it captures the ImageId for persistence. While cornerstone should support these kind of scenarios, the tools layer does not actually do any persistence so this concern is "pushed up" to a higher layer.

Additionally, tools need a way to save and restore state to support persistent storage. For example, it is envisioned that there will be modules capable of parsing formats such as DICOM GSPS, DICOM SR, AIM or proprietary and restoring tool state to display the annotations and measurements stored in those formats. Likewise, it should be possible to save tool state into these formats.

Issues

  • It is possible that some persistent formats won't support stack specific scope.
  • It is possible that a persistent format has an annotation that is to be shared between multiple ImageIds but they want to show additional ImageIds in the stack that are not related. (e.g. annotation is shared between first 5 images in a 10 image stack). This seems like a fringe case and might make things more complex - we won't explicitly support it although it would be nice if such a scenario was possible with cornerstone
  • We may want to introduce the concept of a StackId to uniquely identify a collection of ImageIds. NOTE that DICOM doesn't have a strong concept of a stack - images with the same SeriesUID are often stacked, but they may also be split up (e.g. MRI multi-echo series, CT multi-phase series, multi-frame instances in the same series, etc).

Some thoughts on the design

  • A tool state context is used to manage state across multiple enabled images. This allows tool data to be shared between several different enabled images each of which could display the same ImageId (and therefore the same tool data). Likewise, if one wanted to display the same ImageId in different enabled elements but not share tool state, separate tool state contexts could be used.
  • a default tool state context should be created by cornerstone which is automatically used by enabled elements. The developer would only need to create and setup tool state contexts if they needed more than one. (NOTE: doing this is certainly more convenient but does increase the chance of someone sharing tool state contexts when they shouldn't. For example - if images from two different patients are displayed simultaneously, you probably wouldn't want a save of the tool context to include tool date from both patients)
  • The most common scope is ImageId specific. We may want to initially just implement this as it is simpler.
  • We could punt on the whole stack specific scope and leave it up the tools to handle. It should be possible to do with cornerstone, but not explicitly supported.
  • We may just need a simple opaque state management system that lets a tool store whatever it wants. This would definitely be the simplest thing to implement and most flexible, but we would lose out on consistency between state management strategies between tools.
  • It occurred to me that doing anything stack related requires that we formally introduce the concept of a stack in cornerstone. This may be tricky as there a different ways of handling stacks and there may not be a design that is both simple and meets everyones needs.
  • It might make sense to make an architecture layer layer that handles image specific tools and another higher level architectural layer that introduces the concept of stacks and tools that are stack aware. This would allow us to provide image specific tools (which everyone needs and is fairly straightforward) and give a developer an option to use our stack paradigm or a completely different one (and still benefit from our image specific tools).
Clone this wiki locally