feat: explicit layout apply via HangingProtocolService (#2936)
* feat: add more types to HP service * change hp to have apply protocol * feat: mpr initial work * apply protocol by id * feat: mode should be able to apply protocol directly * fix: wrong inheritance for viewports drag and drop * apply review comments
This commit is contained in:
1 parent
fb9744a817
commit
5f480ff746
28 files changed
+705
-226
No files matched your search
@@ -43,12 +43,10 @@ export default class ExtensionManager {
|
||||
const {
|
||||
MeasurementService,
|
||||
ViewportGridService,
|
||||
HangingProtocolService,
|
||||
} = _servicesManager.services;
|
||||
|
||||
MeasurementService.clearMeasurements();
|
||||
ViewportGridService.reset();
|
||||
HangingProtocolService.reset();
|
||||
|
||||
registeredExtensionIds.forEach(extensionId => {
|
||||
const onModeEnter = _extensionLifeCycleHooks.onModeEnter[extensionId];
|
||||
@@ -74,12 +72,10 @@ export default class ExtensionManager {
|
||||
const {
|
||||
MeasurementService,
|
||||
ViewportGridService,
|
||||
HangingProtocolService,
|
||||
} = _servicesManager.services;
|
||||
|
||||
MeasurementService.clearMeasurements();
|
||||
ViewportGridService.reset();
|
||||
HangingProtocolService.reset();
|
||||
|
||||
registeredExtensionIds.forEach(extensionId => {
|
||||
const onModeExit = _extensionLifeCycleHooks.onModeExit[extensionId];
|
||||
@@ -196,6 +192,8 @@ export default class ExtensionManager {
|
||||
dataSources
|
||||
);
|
||||
break;
|
||||
case MODULE_TYPES.HANGING_PROTOCOL:
|
||||
this._initHangingProtocolsModule(extensionModule, extensionId);
|
||||
case MODULE_TYPES.TOOLBAR:
|
||||
case MODULE_TYPES.VIEWPORT:
|
||||
case MODULE_TYPES.PANEL:
|
||||
@@ -203,7 +201,6 @@ export default class ExtensionManager {
|
||||
case MODULE_TYPES.CONTEXT:
|
||||
case MODULE_TYPES.LAYOUT_TEMPLATE:
|
||||
case MODULE_TYPES.UTILITY:
|
||||
case MODULE_TYPES.HANGING_PROTOCOL:
|
||||
// Default for most extension points,
|
||||
// Just adds each entry ready for consumption by mode.
|
||||
extensionModule.forEach(element => {
|
||||
@@ -287,6 +284,13 @@ export default class ExtensionManager {
|
||||
}
|
||||
};
|
||||
|
||||
_initHangingProtocolsModule = (extensionModule, extensionId) => {
|
||||
const { HangingProtocolService } = this._servicesManager.services;
|
||||
extensionModule.forEach(({ id, protocol }) => {
|
||||
HangingProtocolService.addProtocol(id, protocol);
|
||||
});
|
||||
};
|
||||
|
||||
_initDataSourcesModule(extensionModule, extensionId, dataSources = []) {
|
||||
const { UserAuthenticationService } = this._servicesManager.services;
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ describe('ExtensionManager.js', () => {
|
||||
services: {
|
||||
// Required for DataSource Module initiation
|
||||
UserAuthenticationService: jest.fn(),
|
||||
HangingProtocolService: {
|
||||
addProtocol: jest.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
appConfig = {
|
||||
|
||||
@@ -2,6 +2,7 @@ interface IDisplaySet {
|
||||
displaySetInstanceUID: string;
|
||||
StudyInstanceUID: string;
|
||||
SeriesInstanceUID?: string;
|
||||
SeriesNumber?: string;
|
||||
}
|
||||
|
||||
export default IDisplaySet;
|
||||
@@ -127,7 +127,7 @@ describe('HangingProtocolService', () => {
|
||||
let initialScaling;
|
||||
|
||||
beforeAll(() => {
|
||||
hps.addProtocols([testProtocol]);
|
||||
hps.addProtocol(testProtocol.id, testProtocol);
|
||||
});
|
||||
|
||||
it('has one protocol', () => {
|
||||
@@ -137,11 +137,7 @@ describe('HangingProtocolService', () => {
|
||||
describe('run', () => {
|
||||
it('matches best image match', () => {
|
||||
hps.run({ studies: [studyMatch], displaySets: studyMatchDisplaySets });
|
||||
const {
|
||||
hpAlreadyApplied,
|
||||
viewportMatchDetails,
|
||||
displaySetMatchDetails,
|
||||
} = hps.getMatchDetails();
|
||||
const { hpAlreadyApplied, viewportMatchDetails } = hps.getMatchDetails();
|
||||
expect(hpAlreadyApplied).toMatchObject([false]);
|
||||
expect(viewportMatchDetails.length).toBe(1);
|
||||
expect(viewportMatchDetails[0]).toMatchObject({
|
||||
|
||||
@@ -3,6 +3,7 @@ import sortBy from '../../utils/sortBy';
|
||||
import ProtocolEngine from './ProtocolEngine';
|
||||
import StudyMetadata from '../../types/StudyMetadata';
|
||||
import IDisplaySet from '../DisplaySetService/IDisplaySet';
|
||||
import { HangingProtocol } from '../../types';
|
||||
|
||||
const EVENTS = {
|
||||
STAGE_CHANGE: 'event::hanging_protocol_stage_change',
|
||||
@@ -12,51 +13,24 @@ const EVENTS = {
|
||||
'event::hanging_protocol_custom_image_load_performed',
|
||||
};
|
||||
|
||||
type ViewportOptions = {
|
||||
orientation: string;
|
||||
toolGroupId: string;
|
||||
viewportId: string;
|
||||
viewportType: string;
|
||||
initialImageOptions: Record<string, unknown>;
|
||||
syncGroups: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type ViewportMatchDetails = {
|
||||
viewportOptions: ViewportOptions;
|
||||
displaySetsInfo: {
|
||||
SeriesInstanceUID: string;
|
||||
displaySetInstanceUID: string;
|
||||
displaySetOptions: Record<string, unknown>;
|
||||
};
|
||||
};
|
||||
|
||||
type DisplaySetMatchDetails = {
|
||||
SeriesInstanceUID: string;
|
||||
StudyInstanceUID: string;
|
||||
displaySetInstanceUID: string;
|
||||
matchDetails: any;
|
||||
matchingScore: number;
|
||||
sortingInfo: any;
|
||||
};
|
||||
|
||||
type HangingProtocolMatchDetails = {
|
||||
displaySetMatchDetails: Map<string, DisplaySetMatchDetails>;
|
||||
viewportMatchDetails: ViewportMatchDetails[];
|
||||
hpAlreadyApplied: boolean[];
|
||||
};
|
||||
type Protocol = HangingProtocol.Protocol | HangingProtocol.ProtocolGenerator;
|
||||
|
||||
class HangingProtocolService {
|
||||
studies: StudyMetadata[];
|
||||
protocols: Record<string, unknown>[];
|
||||
protocol: Record<string, unknown>;
|
||||
// stores all the protocols (object or function that returns an object) in a map
|
||||
protocols: Map<string, Protocol>;
|
||||
// the current protocol that is being applied to the viewports in object format
|
||||
protocol: HangingProtocol.Protocol;
|
||||
stage: number;
|
||||
_commandsManager: Record<string, unknown>;
|
||||
_servicesManager: Record<string, unknown>;
|
||||
protocolEngine: ProtocolEngine;
|
||||
hpAlreadyApplied: boolean[] = [];
|
||||
customViewportSettings = [];
|
||||
displaySets: IDisplaySet[] = [];
|
||||
activeStudy: Record<string, unknown>;
|
||||
debugLogging: false;
|
||||
EVENTS: { [key: string]: string };
|
||||
|
||||
customAttributeRetrievalCallbacks = {
|
||||
NumberOfStudyRelatedSeries: {
|
||||
@@ -89,17 +63,21 @@ class HangingProtocolService {
|
||||
* DisplaySetId is the id defined in the hangingProtocol object itself
|
||||
* and match is an object that contains information about
|
||||
*/
|
||||
displaySetMatchDetails: Map<string, DisplaySetMatchDetails> = new Map();
|
||||
displaySetMatchDetails: Map<
|
||||
string,
|
||||
HangingProtocol.DisplaySetMatchDetails
|
||||
> = new Map();
|
||||
|
||||
/**
|
||||
* An array that contains for each viewport (viewportIndex) specified in the
|
||||
* hanging protocol, an object of the form
|
||||
*/
|
||||
viewportMatchDetails = [] as ViewportMatchDetails[];
|
||||
viewportMatchDetails = [] as HangingProtocol.ViewportMatchDetails[];
|
||||
|
||||
constructor(commandsManager) {
|
||||
constructor(commandsManager, servicesManager) {
|
||||
this._commandsManager = commandsManager;
|
||||
this.protocols = [];
|
||||
this._servicesManager = servicesManager;
|
||||
this.protocols = new Map();
|
||||
this.protocolEngine = undefined;
|
||||
this.protocol = undefined;
|
||||
this.stage = undefined;
|
||||
@@ -116,13 +94,17 @@ class HangingProtocolService {
|
||||
|
||||
public reset() {
|
||||
this.studies = [];
|
||||
this.protocols = [];
|
||||
this.protocols = new Map();
|
||||
this.hpAlreadyApplied = [];
|
||||
this.viewportMatchDetails = [];
|
||||
// this.ProtocolEngine.reset()
|
||||
}
|
||||
|
||||
public getMatchDetails(): HangingProtocolMatchDetails {
|
||||
public getDefaultProtocol(): HangingProtocol.Protocol {
|
||||
return this.getProtocolById('default');
|
||||
}
|
||||
|
||||
public getMatchDetails(): HangingProtocol.HangingProtocolMatchDetails {
|
||||
return {
|
||||
viewportMatchDetails: this.viewportMatchDetails,
|
||||
displaySetMatchDetails: this.displaySetMatchDetails,
|
||||
@@ -130,16 +112,76 @@ class HangingProtocolService {
|
||||
};
|
||||
}
|
||||
|
||||
public getProtocols() {
|
||||
return this.protocols;
|
||||
/**
|
||||
* It loops over the protocols map object, and checks whether the protocol
|
||||
* is a function, if so, it executes it and returns the result as a protocol object
|
||||
* otherwise it returns the protocol object itself
|
||||
*
|
||||
* @returns all the hanging protocol registered in the HangingProtocolService
|
||||
*/
|
||||
public getProtocols(): HangingProtocol.Protocol[] {
|
||||
// this.protocols is a map of protocols with the protocol id as the key
|
||||
// and the protocol or a function that returns a protocol as the value
|
||||
const protocols = [];
|
||||
// @ts-ignore
|
||||
for (const protocolId of this.protocols.keys()) {
|
||||
const protocol = this.getProtocolById(protocolId);
|
||||
if (protocol) {
|
||||
protocols.push(protocol);
|
||||
}
|
||||
}
|
||||
|
||||
return protocols;
|
||||
}
|
||||
|
||||
public addProtocols(protocols) {
|
||||
protocols.forEach(protocol => {
|
||||
if (this.protocols.indexOf(protocol) === -1) {
|
||||
this.protocols.push(this._validateProtocol(protocol));
|
||||
/**
|
||||
* Returns the protocol with the given id, it will get the protocol from the
|
||||
* protocols map object and if it is a function, it will execute it and return
|
||||
* the result as a protocol object
|
||||
*
|
||||
* @param protocolId - the id of the protocol
|
||||
* @returns protocol - the protocol with the given id
|
||||
*/
|
||||
public getProtocolById(id: string): HangingProtocol.Protocol {
|
||||
const protocol = this.protocols.get(id);
|
||||
|
||||
if (protocol instanceof Function) {
|
||||
try {
|
||||
const { protocol: generatedProtocol } = this._getProtocolFromGenerator(
|
||||
protocol
|
||||
);
|
||||
|
||||
return generatedProtocol;
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`Error while executing protocol generator for protocol ${id}: ${error}`
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* It adds a protocol to the protocols map object. If a protocol with the given
|
||||
* id already exists, warn the user and overwrite it.
|
||||
*
|
||||
* @param {string} protocolId - The id of the protocol.
|
||||
* @param {Protocol} protocol - Protocol - This is the protocol that you want to
|
||||
* add to the protocol manager.
|
||||
*/
|
||||
public addProtocol(protocolId: string, protocol: Protocol): void {
|
||||
if (this.protocols.has(protocolId)) {
|
||||
console.warn(
|
||||
`A protocol with id ${protocolId} already exists. It will be overwritten.`
|
||||
);
|
||||
}
|
||||
|
||||
if (!(protocol instanceof Function)) {
|
||||
protocol = this._validateProtocol(protocol as HangingProtocol.Protocol);
|
||||
}
|
||||
|
||||
this.protocols.set(protocolId, protocol);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,28 +198,28 @@ class HangingProtocolService {
|
||||
* @param protocol is a specific protocol to apply.
|
||||
* @returns
|
||||
*/
|
||||
public run({ studies, displaySets, activeStudy }, protocol) {
|
||||
public run({ studies, displaySets, activeStudy }, protocolId) {
|
||||
this.studies = [...studies];
|
||||
this.displaySets = displaySets;
|
||||
this.activeStudy = activeStudy || studies[0];
|
||||
|
||||
this.protocolEngine = new ProtocolEngine(
|
||||
this.protocols,
|
||||
this.getProtocols(),
|
||||
this.customAttributeRetrievalCallbacks
|
||||
);
|
||||
|
||||
// if there is no pre-defined protocol
|
||||
if (!protocol || protocol.id === undefined) {
|
||||
const matchedProtocol = this.protocolEngine.run({
|
||||
studies: this.studies,
|
||||
activeStudy,
|
||||
displaySets,
|
||||
});
|
||||
this._setProtocol(matchedProtocol);
|
||||
if (protocolId) {
|
||||
const protocol = this.getProtocolById(protocolId);
|
||||
this._setProtocol(protocol);
|
||||
return;
|
||||
}
|
||||
|
||||
this._setProtocol(protocol);
|
||||
const matchedProtocol = this.protocolEngine.run({
|
||||
studies: this.studies,
|
||||
activeStudy,
|
||||
displaySets,
|
||||
});
|
||||
this._setProtocol(matchedProtocol);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,7 +227,7 @@ class HangingProtocolService {
|
||||
* and its callback has been added to the HangingProtocolService
|
||||
* @returns {boolean} true
|
||||
*/
|
||||
public hasCustomImageLoadStrategy() {
|
||||
public hasCustomImageLoadStrategy(): boolean {
|
||||
return (
|
||||
this.activeImageLoadStrategyName !== null &&
|
||||
this.registeredImageLoadStrategies[
|
||||
@@ -194,7 +236,7 @@ class HangingProtocolService {
|
||||
);
|
||||
}
|
||||
|
||||
public getCustomImageLoadPerformed() {
|
||||
public getCustomImageLoadPerformed(): boolean {
|
||||
return this.customImageLoadPerformed;
|
||||
}
|
||||
|
||||
@@ -203,13 +245,13 @@ class HangingProtocolService {
|
||||
* @param {string} name strategy name
|
||||
* @param {Function} callback image loader callback
|
||||
*/
|
||||
public registerImageLoadStrategy(name, callback) {
|
||||
public registerImageLoadStrategy(name, callback): void {
|
||||
if (callback instanceof Function && name) {
|
||||
this.registeredImageLoadStrategies[name] = callback;
|
||||
}
|
||||
}
|
||||
|
||||
public setHangingProtocolAppliedForViewport(i) {
|
||||
public setHangingProtocolAppliedForViewport(i): void {
|
||||
this.hpAlreadyApplied[i] = true;
|
||||
}
|
||||
|
||||
@@ -222,7 +264,12 @@ class HangingProtocolService {
|
||||
* @param callback The function used to calculate the attribute value from the other attributes at its level (e.g. study/series/image)
|
||||
* @param options to add to the "this" object for the custom attribute retriever
|
||||
*/
|
||||
public addCustomAttribute(attributeId, attributeName, callback, options) {
|
||||
public addCustomAttribute(
|
||||
attributeId: string,
|
||||
attributeName: string,
|
||||
callback: (metadata: any) => any,
|
||||
options: Record<string, any> = {}
|
||||
): void {
|
||||
this.customAttributeRetrievalCallbacks[attributeId] = {
|
||||
...options,
|
||||
id: attributeId,
|
||||
@@ -234,7 +281,7 @@ class HangingProtocolService {
|
||||
/**
|
||||
* Switches to the next protocol stage in the display set sequence
|
||||
*/
|
||||
public nextProtocolStage() {
|
||||
public nextProtocolStage(): void {
|
||||
console.log('ProtocolEngine::nextProtocolStage');
|
||||
|
||||
if (!this._setCurrentProtocolStage(1)) {
|
||||
@@ -245,7 +292,7 @@ class HangingProtocolService {
|
||||
/**
|
||||
* Switches to the previous protocol stage in the display set sequence
|
||||
*/
|
||||
public previousProtocolStage() {
|
||||
public previousProtocolStage(): void {
|
||||
console.log('ProtocolEngine::previousProtocolStage');
|
||||
|
||||
if (!this._setCurrentProtocolStage(-1)) {
|
||||
@@ -257,7 +304,7 @@ class HangingProtocolService {
|
||||
* Executes the callback function for the custom loading strategy for the images
|
||||
* if no strategy is set, the default strategy is used
|
||||
*/
|
||||
runImageLoadStrategy(data) {
|
||||
runImageLoadStrategy(data): void {
|
||||
const loader = this.registeredImageLoadStrategies[
|
||||
this.activeImageLoadStrategyName
|
||||
];
|
||||
@@ -277,8 +324,14 @@ class HangingProtocolService {
|
||||
this._broadcastChange(this.EVENTS.CUSTOM_IMAGE_LOAD_PERFORMED, loadedData);
|
||||
}
|
||||
|
||||
_validateProtocol(protocol) {
|
||||
_validateProtocol(
|
||||
protocol: HangingProtocol.Protocol
|
||||
): HangingProtocol.Protocol {
|
||||
protocol.id = protocol.id || protocol.name;
|
||||
const defaultViewportOptions = {
|
||||
toolGroupId: 'default',
|
||||
viewportType: 'stack',
|
||||
};
|
||||
// Automatically compute some number of attributes if they
|
||||
// aren't present. Makes defining new HPs easier.
|
||||
protocol.name = protocol.name || protocol.id;
|
||||
@@ -292,13 +345,14 @@ class HangingProtocolService {
|
||||
|
||||
for (let i = 0; i < rows * columns; i++) {
|
||||
stage.viewports.push({
|
||||
viewportOptions: {},
|
||||
viewportOptions: defaultViewportOptions,
|
||||
displaySets: [],
|
||||
});
|
||||
}
|
||||
} else {
|
||||
stage.viewports.forEach(viewport => {
|
||||
viewport.viewportOptions = viewport.viewportOptions || {};
|
||||
viewport.viewportOptions =
|
||||
viewport.viewportOptions || defaultViewportOptions;
|
||||
if (!viewport.displaySets) {
|
||||
viewport.displaySets = [];
|
||||
} else {
|
||||
@@ -313,9 +367,53 @@ class HangingProtocolService {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
_setProtocol(protocol) {
|
||||
// TODO: Add proper Protocol class to validate the protocols
|
||||
// which are entered manually
|
||||
/**
|
||||
* It applied the protocol to the current studies and display sets based on the
|
||||
* protocolId that is provided.
|
||||
* @param protocolId - name of the protocol to be set
|
||||
* @param protocol - protocol object (optional), if not provided, the protocol
|
||||
* will be retrieved from the list of protocols by its name
|
||||
* @param matchingDisplaySets - predefined display sets to be used for the protocol
|
||||
*/
|
||||
public setProtocol(
|
||||
protocolId: string,
|
||||
protocol?: HangingProtocol.Protocol,
|
||||
matchingDisplaySets?: Record<string, HangingProtocol.DisplaySetMatchDetails>
|
||||
): void {
|
||||
if (!protocol) {
|
||||
const foundProtocol = this.protocols.get(protocolId);
|
||||
|
||||
if (!foundProtocol) {
|
||||
console.warn(
|
||||
`HangingProtocolService::setProtocol - protocol ${protocolId} not found`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (foundProtocol instanceof Function) {
|
||||
try {
|
||||
({ protocol, matchingDisplaySets } = this._getProtocolFromGenerator(
|
||||
foundProtocol
|
||||
));
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`HangingProtocolService::setProtocol - protocol ${protocolId} failed to execute`,
|
||||
error
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
protocol = foundProtocol;
|
||||
}
|
||||
}
|
||||
|
||||
this._setProtocol(protocol, matchingDisplaySets);
|
||||
}
|
||||
|
||||
private _setProtocol(
|
||||
protocol: HangingProtocol.Protocol,
|
||||
matchingDisplaySets?: Record<string, HangingProtocol.DisplaySetMatchDetails>
|
||||
): void {
|
||||
this.stage = 0;
|
||||
this.protocol = protocol;
|
||||
const { imageLoadStrategy } = protocol;
|
||||
@@ -328,7 +426,7 @@ class HangingProtocolService {
|
||||
this.activeImageLoadStrategyName = imageLoadStrategy;
|
||||
}
|
||||
}
|
||||
this._updateViewports();
|
||||
this._updateViewports(matchingDisplaySets);
|
||||
|
||||
this._broadcastChange(this.EVENTS.PROTOCOL_CHANGED, {
|
||||
viewportMatchDetails: this.viewportMatchDetails,
|
||||
@@ -362,19 +460,50 @@ class HangingProtocolService {
|
||||
return this.protocol.stages[this.stage];
|
||||
}
|
||||
|
||||
private _getProtocolFromGenerator(
|
||||
protocolGenerator: HangingProtocol.ProtocolGenerator
|
||||
): {
|
||||
protocol: HangingProtocol.Protocol;
|
||||
matchingDisplaySets: Record<string, HangingProtocol.DisplaySetMatchDetails>;
|
||||
} {
|
||||
const { protocol, matchingDisplaySets } = protocolGenerator({
|
||||
servicesManager: this._servicesManager,
|
||||
commandsManager: this._commandsManager,
|
||||
});
|
||||
|
||||
const validatedProtocol = this._validateProtocol(protocol);
|
||||
|
||||
return {
|
||||
protocol: validatedProtocol,
|
||||
matchingDisplaySets,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the viewports with the selected protocol stage.
|
||||
*/
|
||||
_updateViewports() {
|
||||
_updateViewports(
|
||||
matchingDisplaySets?: Record<string, HangingProtocol.DisplaySetMatchDetails>
|
||||
): void {
|
||||
// Make sure we have an active protocol with a non-empty array of display sets
|
||||
if (!this._getNumProtocolStages()) {
|
||||
console.log('No protocol stages - nothing to display');
|
||||
return;
|
||||
}
|
||||
|
||||
// each time we are updating the viewports, we need to reset the
|
||||
// matching applied
|
||||
this.hpAlreadyApplied = [];
|
||||
|
||||
// reset displaySetMatchDetails
|
||||
this.displaySetMatchDetails = new Map();
|
||||
|
||||
if (matchingDisplaySets) {
|
||||
this.displaySetMatchDetails = new Map(
|
||||
Object.entries(matchingDisplaySets)
|
||||
);
|
||||
}
|
||||
|
||||
// Retrieve the current stage
|
||||
const stageModel = this._getCurrentStageModel();
|
||||
|
||||
@@ -392,7 +521,7 @@ class HangingProtocolService {
|
||||
}
|
||||
|
||||
this.customImageLoadPerformed = false;
|
||||
const { type: layoutType } = stageModel.viewportStructure;
|
||||
const { layoutType } = stageModel.viewportStructure;
|
||||
|
||||
// Retrieve the properties associated with the current display set's viewport structure template
|
||||
// If no such layout properties exist, stop here.
|
||||
@@ -412,14 +541,19 @@ class HangingProtocolService {
|
||||
});
|
||||
|
||||
// Matching the displaySets
|
||||
for (const displaySet of stageModel.displaySets) {
|
||||
// skip matching if already matched
|
||||
if (this.displaySetMatchDetails.has(displaySet.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
stageModel.displaySets.forEach(displaySet => {
|
||||
const { bestMatch, matchingScores } = this._matchImages(displaySet);
|
||||
this.displaySetMatchDetails.set(displaySet.id, bestMatch);
|
||||
|
||||
if (bestMatch) {
|
||||
bestMatch.matchingScores = matchingScores;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Loop through each viewport
|
||||
stageModel.viewports.forEach((viewport, viewportIndex) => {
|
||||
@@ -430,6 +564,8 @@ class HangingProtocolService {
|
||||
// but it is a info to locate the displaySet from the displaySetService
|
||||
const displaySetsInfo = [];
|
||||
viewport.displaySets.forEach(
|
||||
// Todo: why do we have displaySetIndex here? It is not used in the protocol
|
||||
// definition
|
||||
({ id, displaySetIndex = 0, options: displaySetOptions }) => {
|
||||
const viewportDisplaySetMain = this.displaySetMatchDetails.get(id);
|
||||
// Use the display set index to allow getting the "next" match, eg
|
||||
@@ -445,7 +581,7 @@ class HangingProtocolService {
|
||||
displaySetInstanceUID,
|
||||
} = viewportDisplaySet;
|
||||
|
||||
const displaySetInfo = {
|
||||
const displaySetInfo: HangingProtocol.DisplaySetInfo = {
|
||||
SeriesInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
displaySetOptions,
|
||||
@@ -524,6 +660,7 @@ class HangingProtocolService {
|
||||
const seriesMatchDetails = this.protocolEngine.findMatch(
|
||||
displaySet,
|
||||
seriesMatchingRules,
|
||||
// Todo: why we have images here since the matching type does not have it
|
||||
{ studies: this.studies, instance: displaySet.images?.[0] }
|
||||
);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import HangingProtocolService from './HangingProtocolService';
|
||||
|
||||
export default {
|
||||
name: 'HangingProtocolService',
|
||||
create: ({ configuration = {}, commandsManager }) => {
|
||||
return new HangingProtocolService(commandsManager);
|
||||
create: ({ configuration = {}, commandsManager, servicesManager }) => {
|
||||
return new HangingProtocolService(commandsManager, servicesManager);
|
||||
},
|
||||
};
|
||||
@@ -37,6 +37,7 @@ export default class ServicesManager {
|
||||
this.services[service.name] = service.create({
|
||||
configuration,
|
||||
commandsManager: this._commandsManager,
|
||||
servicesManager: this,
|
||||
});
|
||||
} else {
|
||||
log.warn(`Service create factory function not defined. Exiting early.`);
|
||||
|
||||
@@ -4,10 +4,15 @@ import log from '../log.js';
|
||||
jest.mock('./../log.js');
|
||||
|
||||
describe('ServicesManager.js', () => {
|
||||
let servicesManager;
|
||||
let servicesManager, commandsManager;
|
||||
|
||||
beforeEach(() => {
|
||||
servicesManager = new ServicesManager();
|
||||
commandsManager = {
|
||||
createContext: jest.fn(),
|
||||
getContext: jest.fn(),
|
||||
registerCommand: jest.fn(),
|
||||
};
|
||||
servicesManager = new ServicesManager(commandsManager);
|
||||
log.warn.mockClear();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
@@ -90,9 +95,9 @@ describe('ServicesManager.js', () => {
|
||||
|
||||
servicesManager.registerService(fakeService, configuration);
|
||||
|
||||
expect(fakeService.create.mock.calls[0][0]).toEqual({
|
||||
configuration,
|
||||
});
|
||||
expect(fakeService.create.mock.calls[0][0].configuration.config).toBe(
|
||||
configuration.config
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,140 @@
|
||||
type DisplaySetInfo = {
|
||||
SeriesInstanceUID: string;
|
||||
displaySetInstanceUID: string;
|
||||
displaySetOptions: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type ViewportMatchDetails = {
|
||||
viewportOptions: ViewportOptions;
|
||||
displaySetsInfo: DisplaySetInfo[];
|
||||
};
|
||||
|
||||
type DisplaySetMatchDetails = {
|
||||
SeriesInstanceUID: string;
|
||||
StudyInstanceUID: string;
|
||||
displaySetInstanceUID: string;
|
||||
matchDetails?: any;
|
||||
matchingScores?: any[];
|
||||
sortingInfo?: any;
|
||||
};
|
||||
|
||||
type HangingProtocolMatchDetails = {
|
||||
displaySetMatchDetails: Map<string, DisplaySetMatchDetails>;
|
||||
viewportMatchDetails: ViewportMatchDetails[];
|
||||
hpAlreadyApplied: boolean[];
|
||||
};
|
||||
|
||||
type MatchingRule = {
|
||||
id: string;
|
||||
weight: number;
|
||||
attribute: string;
|
||||
constraint: Record<string, unknown>;
|
||||
required: boolean;
|
||||
};
|
||||
|
||||
type ViewportLayoutOptions = {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
type ViewportStructure = {
|
||||
layoutType: string;
|
||||
properties: {
|
||||
rows: number;
|
||||
columns: number;
|
||||
layoutOptions: ViewportLayoutOptions[];
|
||||
};
|
||||
};
|
||||
|
||||
type DisplaySet = {
|
||||
id: string;
|
||||
imageMatchingRules: MatchingRule[];
|
||||
seriesMatchingRules: MatchingRule[];
|
||||
studyMatchingRules: MatchingRule[];
|
||||
};
|
||||
|
||||
type SyncGroup = {
|
||||
type: string;
|
||||
id: string;
|
||||
source?: boolean
|
||||
target?: boolean
|
||||
}
|
||||
|
||||
type initialImageOptions = {
|
||||
index?: number;
|
||||
preset? : string; // todo: type more
|
||||
}
|
||||
|
||||
type ViewportOptions = {
|
||||
toolGroupId: string;
|
||||
viewportType: string;
|
||||
id?: string;
|
||||
orientation?: string;
|
||||
viewportId?: string;
|
||||
initialImageOptions?: initialImageOptions;
|
||||
syncGroups?: SyncGroup[];
|
||||
customViewportProps? : Record<string, unknown>;
|
||||
};
|
||||
|
||||
type DisplaySetOptions = {
|
||||
id: string;
|
||||
options?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type Viewport = {
|
||||
viewportOptions: ViewportOptions;
|
||||
displaySets: DisplaySetOptions[];
|
||||
};
|
||||
|
||||
type ProtocolStage = {
|
||||
id: string;
|
||||
name: string;
|
||||
viewportStructure: ViewportStructure;
|
||||
displaySets: DisplaySet[];
|
||||
viewports: Viewport[];
|
||||
createdDate?: string;
|
||||
};
|
||||
|
||||
type Protocol = {
|
||||
// Mandatory
|
||||
id: string;
|
||||
stages: ProtocolStage[];
|
||||
// Optional
|
||||
locked?: boolean;
|
||||
hasUpdatedPriorsInformation?: boolean;
|
||||
name?: string;
|
||||
createdDate?: string;
|
||||
modifiedDate?: string;
|
||||
availableTo?: Record<string, unknown>;
|
||||
editableBy?: Record<string, unknown>;
|
||||
toolGroupIds?: string[];
|
||||
imageLoadStrategy?: string; // Todo: this should be types specifically
|
||||
protocolMatchingRules?: MatchingRule[];
|
||||
numberOfPriorsReferenced?: number;
|
||||
};
|
||||
|
||||
type ProtocolGenerator = ({servicesManager: any, commandsManager: any}) => {
|
||||
protocol: Protocol;
|
||||
matchingDisplaySets: any;
|
||||
};
|
||||
|
||||
export type {
|
||||
ProtocolGenerator,
|
||||
ViewportOptions,
|
||||
ViewportMatchDetails,
|
||||
DisplaySetMatchDetails,
|
||||
HangingProtocolMatchDetails,
|
||||
Protocol,
|
||||
ProtocolStage,
|
||||
Viewport,
|
||||
DisplaySet,
|
||||
ViewportStructure,
|
||||
ViewportLayoutOptions,
|
||||
DisplaySetOptions,
|
||||
MatchingRule,
|
||||
SyncGroup,
|
||||
initialImageOptions,
|
||||
DisplaySetInfo
|
||||
};
|
||||
@@ -6,4 +6,12 @@ import {
|
||||
|
||||
import Consumer from './Consumer';
|
||||
|
||||
export { StudyMetadata, SeriesMetadata, InstanceMetadata, Consumer };
|
||||
import * as HangingProtocol from './HangingProtocol';
|
||||
|
||||
export type {
|
||||
HangingProtocol,
|
||||
StudyMetadata,
|
||||
SeriesMetadata,
|
||||
InstanceMetadata,
|
||||
Consumer,
|
||||
};
|
||||
Reference in new issue
Block a user