feat: state sync service and hanging protocol updates to preserve state (#3131)

* feat: Add state sync and use it to remember viewport grid info

fix: Version updates

Fixes for toggling MPR mode

Fix the display when the interleaved load module fails

Fix the memory of the state to restore correctly

PR fixes for the state sync service

PR fixes

PR fixes

PR fixes

Added a hack warning to remove volumeDeactivate

Fixes for TMTV colormap setting

Fix the casing

Missed renames

fix: tests not running due to variance in ordering

Reverting some fixes to change case

PR changes - mostly comments and minor improvements

fix: All display sets were being updated on drag and drop

PR fixes - mostly renames

PR fixes

Test support for OHIF, for HP branch

test: Add at least a minimal set of automated tests for hanging protocols

Docs

PR fixes

Merge fixes

DOCS updates

Add an example of the mn hanging protocol

PR fixes

PR fixes

PR fixes

* Fix the drag and drop

PR fixes

* PR changes - update default keys for next/previous stage

* fix: Was storing the custom viewport grid too aggressively

Caused by a PR change misspelling a variable
This commit is contained in:
Bill Wallace authored and GitHub committed 2023-03-15 12:41:41 -04:00
1 parent b7fff77e17
commit 803f638401
87 files changed
+3413 -1919

No files matched your search

@@ -27,6 +27,27 @@ registered automatically to the HangingProtocolService.
All protocols are stored in the `HangingProtocolService` using their `id` as the key, and the protocol itself as the value.
## Protocol Definition
Protocols are defined in a getHangingProtocolModule inside an extension. As such,
they are defined with a module structure that starts with an id, and has field protocol
that is the actual protocol definition. This setup allows defining more than
one protocol within a module, each one needing it's own definition file.
```javascript
import MyProtocol from './MyProtocol';
export default function getHangingProtocolModule() {
return [
{
id: MyProtocol.id,
protocol: MyProtocol,
},
];
}
```
Within the protocol itself, the structure is layed out as described in the HangingProtocol.ts
type definition, starting with `Protocol`. See the type definition for more details.
## Events
There are two events that get publish in `HangingProtocolService`:
@@ -34,31 +55,101 @@ There are two events that get publish in `HangingProtocolService`:
| Event | Description |
| ------------ | -------------------------------------------------------------------- |
| NEW_LAYOUT | Fires when a new layout is requested by the `HangingProtocolService` |
| STAGE_CHANGE | Fires when the the stage is changed in the hanging protocols |
| PROTOCOL_CHANGED | Fires when the the protocol is changed in the hanging protocols |
| HANGING_PROTOCOL_APPLIED_FOR_VIEWPORT | Fires when the hanging protocol applies for a viewport (sets its displaySets) |
| PROTOCOL_CHANGED | Fires when the the protocol is changed in the hanging protocols, or when the applied stage is changed. |
| RESTORE_PROTOCOL | Fires when the protocol or stage is restored, for example, after turning off MPR mode |
| STAGE_ACTIVATION | Fires when the stages are known to have stage.status set. |
## Stage Activation and Status
Sometimes a hanging protocol can be applicable generally, but not all stages
should be shown by default, or should be shown at all. This can be handled by
using the stage activation to control whether the stage is shown by default (`enabled`),
whether it can be navigated to (`passive`) or whether it should not be shown
at all (`disabled`).
The `stage.status` is used to control this, and the status is controlled by
the stage activate. The status values are:
* enabled - meaning that the stage is fully applicable
* passive - meaning that the stage can be applied, but might be missing details
* disabled - meaning that the study has insuffient information for this stage
The default values for no `stageActivation` are to assume that `enabled` has `minViewports` of 1,
and `passive` has `minViewports=0`. That is, enable the stage if at least one
viewport is filled, and make it passive if no viewports are filled.
The setting for these are controlled by the stageActivation property, for example
the following:
```javascript
stageActivation: {
// The enabled activation specifies requirements to enable the stage, that is,
// make it preferred.
enabled: {
// The default value here is 1, and indicates how many non-blank viewports
// are required.
minViewportsMatched: 3,
// This enables specifying cross cutting concerns, such as having a stage
// only apply to males or females, and is a list of display set selector ids
displaySetSelectorsMatched: ['dsMale'],
},
// The passive check is performed first. If it fails, the enabled is NOT
// checked, but the status set to disabled. The default passive check
// should always be passed, so it is fine to just define enabled if desired.
passive: {
// The default is 0, which means allow the stage even if no viewports are
// filled. This allows dragging and dropping into the viewports to
// make matches manually, which can then be re-used for other stages.
minViewportsMatched: 0,
displaySetSelectorsMatched: [...],
},
}
```
## API
- `destroy`: Destroys the HP service
- `reset` and `onModeEnter`: Resets the HP service to not have any active
hanging protocols
- `getActiveProtocol`: Returns an object of the internal state of the HP service,
useful for storing said state, as well as for getting direct access to the
protocol and stage objects. Users of this should count on it being not completely
stable as to exactly what this returns, as internal details can change.
- `getState`: Returns the currently applied protocol ID, stage index and active study UID.
This information is storable/useable as state information to be used elsewhere.
- `getDefaultProtocol`: Returns the default protocol to apply.
- `getMatchDetails`: returns an object which contains the details of the
matching for the viewports, displaySets and whether the protocol is
applied to the viewport or not yet.
applied to the viewport or not yet. This is deprecated as it is expected
to be communicated by events instead.
- `getProtocols`: Returns a list of the currently active protocols.
- `getProtocolById`: Gets the protocol with the given id.
- `addProtocol`: adds provided protocol to the list of registered protocols
for matching
for matching. Will replacing any protocol with the same id, allowing, for example,
to replace the default protocol.
- `setActiveProtocols`: Choose the protocols which are active. Can take a
single protocol id or a list. When a single one is provided, that one will be
applied whether or not the required rules match. Called automatically on mode
init.
- `setActiveStudyUID`: Sets the given study UID as active, which has significance
in terms of the matching rules being able to match against the active study.
- `run({studies, activeStudy, displaySets }, protocolId)`: runs the HPService with the provided
studyMetaData and optional protocolId. If protocol is not given, HP Matching
engine will search all the registered protocols for the best matching one
based on the constraints.
- `registerImageLoadStrategy`: Adds a custom image load strategy.
- `addCustomAttribute`: adding a custom attribute for matching. (see below)
- `setProtocol`: applies a protocol to the current studies, it can be used for instance to apply a
@@ -68,6 +159,12 @@ init.
used for the protocol. If no options are provided, all displaySets will
be used to match the protocol.
- `getStageIndex`: Finds the stage index for a given set of match keys. Currently
only works on the currently active protocol, but is supposed to be able to work
with other protocols as well.
- `getMissingViewport`: Returns a viewport object to be used as the missing
viewport instance. This is used to fill out new viewports.
Default initialization of the modes handles running the `HangingProtocolService`
@@ -78,7 +175,7 @@ do not overlap, with the suggested id being `${moduleId}.${simpleName}`. The
'default' name is used as the hanging protocol id when no other protocol applies,
and can be set as the last module listed containing 'default'.
A hanging protocol can also be defined with a generator.
A hanging protocol can also be defined with a generator.
A generator is a function we can write this way:
```ts
@@ -93,6 +190,33 @@ function protocolGenerator({ servicesManager, commandsManager }) {
See the typescript definitions for more details on the structure of protocols.
## Additional viewports for layout - `defaultViewport`
Sometimes the user manually selects a layout of a given size, say `2x3`. The
hanging protocol can define what viewport options to use for this viewport by
defining an extra viewport option in `defaultViewport`. For example:
```javascript
defaultViewport: {
viewportOptions: {
viewportType: 'stack',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
id: 'defaultDisplaySetId',
matchedDisplaySetsIndex: -1,
},
],
},
```
This allows defining the type of additional viewports, what tool group etc they
are allowed in, and which display set is used to fill them. In the above case,
the display set is the same as the other viewports, but the
`matchedDisplaySetsIndex=-1`, so that means find the next matching display set
from the display set selector which isn't already filling a view.
## Custom Attribute
In some situations, you might want to match based on a custom attribute and not the DICOM tags. For instance,
if you have assigned a `timepointId` to each study, and you want to match based on it.
@@ -102,7 +226,7 @@ There are various ways that you can let `HangingProtocolService` know of you
custom attribute. We will show how to add it inside the mode configuration.
```js
const deafultProtocol = {
const defaultProtocol = {
id: 'defaultProtocol',
/** ... **/
protocolMatchingRules: [
@@ -0,0 +1,72 @@
---
sidebar_position: 8
sidebar_label: State Sync Service
---
# State Sync Service
## Overview
The state sync service is designed to allow short and long term memory of things such as
annotations applied, last annotation state, hanging protocol viewport state,
window level etc. This allows for better interaction with things like navigation
between hanging protocols, ensuring that the previously displayed layouts
can be redisplayed after returning to a given hanging protocol.
Currently, all the state sync service configurations have one of the following two
lifetimes. See the mode description for general information on the mode lifetime.
* Application load - when the application is restarted, the state is lost
* `clearOnModeExit` - which stores state until the mode onModeExit is called, and then throws away the remaining state. This is useful for mode specific information.
### TODO work - add more storage locations
It is expected to add a few more storage locations, which will store to various
locations on updates:
* User specific server store - to store things between application restarts at the user level
* Browser state store - to store things in the browser local state, to recover after crashing.
* Study specific server store - to store things relevant to a given study between application restarts, on the server.
## Events
Currently the service does not fire events.
## API
- `register`: to create a new named state storage
- `reduce`: to apply a set of changes to several states at once
- `getState`: to retrieve the current state
- `onModeExit`: clears the states configured as clearOnModeExit states
### register
The register call is typically added to an extension to create a new
syncable state. A typical call is shown below, registering the viewport
grid store state as a modal state.
```javascript
stateSyncService.register('viewportGridStore', { clearOnModeExit: true });
```
### getState
The `getState` call returns an object containing all of the reigstered states,
by id. The values can be read directly, but should not be modified.
### reduce
The `reduce` call is used to apply a set of updates to various states. The
updates are performed for every state as a simply "set" call.
### onModeExit
When the Mode is exited, the onModeExit is called on the sync state, and this
clears all states registered with `clearOnModeExit: true`.
To avoid clearing the state, the mode definition should store any transient
state in the mode onModeExit and recover it in the `mode.onModeEnter`.
## OHIF Registered State
There are a number of defined states here. It is recommended to update this
list as states are added:
* `viewportGridStore` has viewport grid restore information for returning to an earlier grid layout.
* `reuseIdMap` has a map of names to display sets for preserving user changes to hp display set selections.
* `hanging` has a map of the hanging protocol stage information applied (HPInfo)
* `presentationSync` has the cornerstone presentation state information
* `toggleHangingProtocol` has the previously applied hanging protocol, to toggle an HP off.
* `querySync` has the previously applied query information. Not fully implemented yet.
@@ -33,6 +33,7 @@ button is clicked by the user.
presets.
- `commandName`: if tool has a command attached to run
- `commandOptions`: arguments for the command.
- `setActive`: Sets a given tool active (not as primary but as secondary)
- `reset`: reset the state of the toolbarService, set the primary tool to be
`Wwwc` and unsubscribe tools that have registered their functions.
@@ -19,7 +19,8 @@ We maintain the following non-ui Services:
- [Hanging Protocol Service](../data/HangingProtocolService.md)
- [Toolbar Service](../data/ToolBarService.md)
- [Measurement Service](../data/MeasurementService.md)
- [Customization Service](customization-service.md)
- [Customization Service](../data/customization-service.md)
- [State Sync Service](../data/StateSyncService.md)
- [Panel Service](../data/PanelService.md)
## Service Architecture