Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hp callback): Add viewport ready callback #3772

Merged
merged 10 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion extensions/cornerstone/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"@cornerstonejs/dicom-image-loader": "^1.27.3",
"@ohif/core": "3.8.0-beta.10",
"@ohif/ui": "3.8.0-beta.10",
"dcmjs": "^0.29.11",
"dcmjs": "^0.29.12",
"dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
Expand Down
21 changes: 20 additions & 1 deletion extensions/cornerstone/src/commandsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,23 @@ function commandsModule({
storePresentation: ({ viewportId }) => {
cornerstoneViewportService.storePresentation({ viewportId });
},

attachProtocolViewportDataListener: ({ protocol, stageIndex }) => {
const EVENT = cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED;
const command = protocol.callbacks.onViewportDataInitialized;
const numPanes = protocol.stages?.[stageIndex]?.viewports.length ?? 1;
let numPanesWithData = 0;
const { unsubscribe } = cornerstoneViewportService.subscribe(EVENT, evt => {
numPanesWithData++;

if (numPanesWithData === numPanes) {
commandsManager.run(...command);

// Unsubscribe from the event
unsubscribe(EVENT);
}
});
},
};

const definitions = {
Expand All @@ -606,7 +623,6 @@ function commandsModule({
storeContexts: [],
options: {},
},

deleteMeasurement: {
commandFn: actions.deleteMeasurement,
},
Expand Down Expand Up @@ -714,6 +730,9 @@ function commandsModule({
cleanUpCrosshairs: {
commandFn: actions.cleanUpCrosshairs,
},
attachProtocolViewportDataListener: {
commandFn: actions.attachProtocolViewportDataListener,
},
};

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,14 +283,21 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
this.viewportsById.set(viewportId, viewportInfo);

const viewport = renderingEngine.getViewport(viewportId);
this._setDisplaySets(viewport, viewportData, viewportInfo, presentations);
const displaySetPromise = this._setDisplaySets(
viewport,
viewportData,
viewportInfo,
presentations
);

// The broadcast event here ensures that listeners have a valid, up to date
// viewport to access. Doing it too early can result in exceptions or
// invalid data.
this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, {
viewportData,
viewportId,
displaySetPromise.then(() => {
this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, {
viewportData,
viewportId,
});
});
}

Expand All @@ -312,12 +319,12 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
return this.viewportsById.get(viewportId);
}

_setStackViewport(
private async _setStackViewport(
viewport: Types.IStackViewport,
viewportData: StackViewportData,
viewportInfo: ViewportInfo,
presentations: Presentations
): void {
): Promise<void> {
const displaySetOptions = viewportInfo.getDisplaySetOptions();

const { imageIds, initialImageIndex, displaySetInstanceUID } = viewportData.data;
Expand Down Expand Up @@ -347,7 +354,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
}
}

viewport.setStack(imageIds, initialImageIndexToUse).then(() => {
return viewport.setStack(imageIds, initialImageIndexToUse).then(() => {
viewport.setProperties({ ...properties });
const camera = presentations.positionPresentation?.camera;
if (camera) {
Expand Down Expand Up @@ -654,38 +661,44 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
const viewport = this.getCornerstoneViewport(viewportId);
const viewportCamera = viewport.getCamera();

let displaySetPromise;

if (viewport instanceof VolumeViewport || viewport instanceof VolumeViewport3D) {
this._setVolumeViewport(viewport, viewportData, viewportInfo).then(() => {
displaySetPromise = this._setVolumeViewport(viewport, viewportData, viewportInfo).then(() => {
if (keepCamera) {
viewport.setCamera(viewportCamera);
viewport.render();
}
});

return;
}

if (viewport instanceof StackViewport) {
this._setStackViewport(viewport, viewportData, viewportInfo);
return;
displaySetPromise = this._setStackViewport(viewport, viewportData, viewportInfo);
}

displaySetPromise.then(() => {
this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, {
viewportData,
viewportId,
});
});
}

_setDisplaySets(
viewport: StackViewport | VolumeViewport,
viewportData: StackViewportData | VolumeViewportData,
viewportInfo: ViewportInfo,
presentations: Presentations = {}
): void {
): Promise<void> {
if (viewport instanceof StackViewport) {
this._setStackViewport(
return this._setStackViewport(
viewport,
viewportData as StackViewportData,
viewportInfo,
presentations
);
} else if (viewport instanceof VolumeViewport || viewport instanceof VolumeViewport3D) {
this._setVolumeViewport(
return this._setVolumeViewport(
viewport,
viewportData as VolumeViewportData,
viewportInfo,
Expand Down
11 changes: 0 additions & 11 deletions extensions/default/src/commandsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,6 @@ const commandsModule = ({
];
stateSyncService.store(stateSyncReduce);
// This is a default action applied
const { protocol } = hangingProtocolService.getActiveProtocol();
actions.toggleHpTools();

// try to use the same tool in the new hanging protocol stage
Expand All @@ -264,16 +263,6 @@ const commandsModule = ({
});
}
}

// Send the notification about updating the state
if (protocolId !== hpInfo.protocolId) {
// The old protocol callbacks are used for turning off things
// like crosshairs when moving to the new HP
commandsManager.run(oldProtocol.callbacks?.onProtocolExit);
// The new protocol callback is used for things like
// activating modes etc.
}
commandsManager.run(protocol.callbacks?.onProtocolEnter);
return true;
} catch (e) {
console.error(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ function checkHpsBestMatch(hps) {

describe('HangingProtocolService', () => {
const mockedFunction = jest.fn();
const commandsManager = {};
const commandsManager = {
run: mockedFunction,
};
const servicesManager = {
services: {
TestService: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,8 @@ export default class HangingProtocolService extends PubSubService {

throw new Error(error);
}

this._commandsManager.run(this.protocol?.callbacks?.onProtocolEnter);
}

protected matchActivation(
Expand Down Expand Up @@ -956,6 +958,12 @@ export default class HangingProtocolService extends PubSubService {
if (!this.protocol || this.protocol.id !== protocol.id) {
this.stageIndex = options?.stageIndex || 0;
this._originalProtocol = this._copyProtocol(protocol);

// before reassigning the protocol, we need to check if there is a callback
// on the old protocol that needs to be called
// Send the notification about updating the state
this._commandsManager.run(this.protocol?.callbacks?.onProtocolExit);

this.protocol = protocol;

const { imageLoadStrategy } = protocol;
Expand Down Expand Up @@ -1120,6 +1128,13 @@ export default class HangingProtocolService extends PubSubService {

const { columns: numCols, rows: numRows, layoutOptions = [] } = layoutProps;

if (this.protocol?.callbacks?.onViewportDataInitialized) {
this._commandsManager.runCommand('attachProtocolViewportDataListener', {
protocol: this.protocol,
stageIndex: this.stageIndex,
});
}

this._broadcastEvent(this.EVENTS.NEW_LAYOUT, {
layoutType,
numRows,
Expand All @@ -1143,9 +1158,6 @@ export default class HangingProtocolService extends PubSubService {
} {
let matchedViewports = 0;
stageModel.viewports.forEach(viewport => {
// Todo: we should probably assign a random viewportId if not defined
// below, but it feels odd since viewportGrid should handle this kind
// of thing
const viewportId = viewport.viewportOptions.viewportId;
const matchDetails = this._matchViewport(
viewport,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function _broadcastEvent(eventName, callbackProps) {
/** Export a PubSubService class to be used instead of the individual items */
export class PubSubService {
EVENTS: any;
subscribe: (eventName: string, callback: Function) => { unsubscribe: () => any; };
subscribe: (eventName: string, callback: Function) => { unsubscribe: () => any };
_broadcastEvent: (eventName: string, callbackProps: any) => void;
_unsubscribe: (eventName: string, listenerId: string) => void;
_isValidEvent: (eventName: string) => boolean;
Expand Down
6 changes: 4 additions & 2 deletions platform/core/src/types/HangingProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,17 @@ export type ProtocolStage = {
export type ProtocolNotifications = {
// This set of commands is executed after the protocol is exited and the new one applied
onProtocolExit?: Command[];

// This set of commands is executed after the protocol is entered and applied
onProtocolEnter?: Command[];

// This set of commands is executed before the layout change is started.
// If it returns false, the layout change will be aborted.
// The numRows and numCols is included in the command params, so it is possible
// to apply a specific hanging protocol
onLayoutChange?: Command[];
// This set of commands is executed after the initial viewport grid data is set
// and all viewport data includes a designated display set. This command
// will run on every stage's initial layout.
onViewportDataInitialized?: Command[];
};

/**
Expand Down
62 changes: 62 additions & 0 deletions platform/docs/docs/platform/extensions/modules/hpModule.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ viewportStructure: {
],
},
},

```


Expand Down Expand Up @@ -559,3 +560,64 @@ Additional series level criteria, such as modality rules must be included at the
},
],
```


## Callbacks


Hanging protocols in `OHIF-v3` provide the flexibility to define various callbacks that allow you to customize the behavior of your viewer when specific events occur during protocol execution. These callbacks are defined in the `ProtocolNotifications` type and can be added to your hanging protocol configuration.

Each callback is an array of commands or actions that are executed when the event occurs.

```js
[
{
commandName: 'showDownloadViewportModal',
commandOptions: {}
}
]
```


Here, we'll explain the available callbacks and their purposes:

### `onProtocolExit`

The `onProtocolExit` callback is executed after the protocol is exited and the new one is applied. This callback is useful for performing actions or executing commands when switching between hanging protocols.

### `onProtocolEnter`

The `onProtocolEnter` callback is executed after the protocol is entered and applied. You can use this callback to define actions or commands that should run when entering a specific hanging protocol.

### `onLayoutChange`

The `onLayoutChange` callback is executed before the layout change is started. You can use it to apply a specific hanging protocol based on the current layout or other criteria.

### `onViewportDataInitialized`

The `onViewportDataInitialized` callback is executed after the initial viewport grid data is set and all viewport data includes a designated display set. This callback runs during the initial layout setup for each stage. You can use it to perform actions or apply settings to the viewports at the start.

Here is an example of how you can add these callbacks to your hanging protocol configuration:

```javascript
const protocol = {
id: 'myProtocol',
name: 'My Protocol',
// rest of the protocol configuration
callbacks: {
onProtocolExit: [
// Array of commands or actions to execute on protocol exit
],
onProtocolEnter: [
// Array of commands or actions to execute on protocol enter
],
onLayoutChange: [
// Array of commands or actions to execute on layout change
],
onViewportDataInitialized: [
// Array of commands or actions to execute on viewport data initialization
],
},
// protocolMatchingRules
// the rest
};
Original file line number Diff line number Diff line change
Expand Up @@ -350,13 +350,13 @@ The below example modifies the included hanging protocol (extensions/tmtv/src/ge

```javascript
ptDisplaySet: {
...
seriesMatchingRules: [
{
attribute: 'sameAs',
sameAttribute: 'FrameOfReferenceUID',
sameDisplaySetId: 'ctDisplaySet',
required: true,
},
...
...
seriesMatchingRules: [
{
attribute: 'sameAs',
sameAttribute: 'FrameOfReferenceUID',
sameDisplaySetId: 'ctDisplaySet',
required: true,
},
...
```