feat(HangingProtocol): add protocol generator back (#3128)
This commit is contained in:
1 parent
f7c886878d
commit
514ea3ccea
5 files changed
+139
-37
No files matched your search
@@ -79,6 +79,14 @@ const testProtocol = {
|
|||||||
numberOfPriorsReferenced: -1,
|
numberOfPriorsReferenced: -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function testProtocolGenerator({ servicesManager }) {
|
||||||
|
servicesManager.services.TestService.toCall();
|
||||||
|
|
||||||
|
return {
|
||||||
|
protocol: testProtocol,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const studyMatch = {
|
const studyMatch = {
|
||||||
StudyInstanceUID: 'studyMatch',
|
StudyInstanceUID: 'studyMatch',
|
||||||
StudyDescription: 'A PETCT study type',
|
StudyDescription: 'A PETCT study type',
|
||||||
@@ -107,41 +115,80 @@ const displaySet3 = {
|
|||||||
|
|
||||||
const studyMatchDisplaySets = [displaySet3, displaySet2, displaySet1];
|
const studyMatchDisplaySets = [displaySet3, displaySet2, displaySet1];
|
||||||
|
|
||||||
|
function checkHpsBestMatch(hps) {
|
||||||
|
hps.run({ studies: [studyMatch], displaySets: studyMatchDisplaySets });
|
||||||
|
const { hpAlreadyApplied, viewportMatchDetails } = hps.getMatchDetails();
|
||||||
|
expect(hpAlreadyApplied).toMatchObject(new Map([[0, false]]));
|
||||||
|
expect(viewportMatchDetails.size).toBe(1);
|
||||||
|
expect(viewportMatchDetails.get(0)).toMatchObject({
|
||||||
|
viewportOptions: {
|
||||||
|
viewportId: 'ctAXIAL',
|
||||||
|
viewportType: 'volume',
|
||||||
|
orientation: 'axial',
|
||||||
|
toolGroupId: 'ctToolGroup',
|
||||||
|
},
|
||||||
|
// Matches ds1 because it matches 2 rules, a required and an optional
|
||||||
|
// ds2 fails to match required and ds3 fails to match an optional.
|
||||||
|
displaySetsInfo: [
|
||||||
|
{
|
||||||
|
SeriesInstanceUID: 'ds1',
|
||||||
|
displaySetInstanceUID: 'displaySet1',
|
||||||
|
displaySetOptions: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe('HangingProtocolService', () => {
|
describe('HangingProtocolService', () => {
|
||||||
|
const mockedFunction = jest.fn();
|
||||||
const commandsManager = {};
|
const commandsManager = {};
|
||||||
const hps = new HangingProtocolServiceClass(commandsManager);
|
const servicesManager = {
|
||||||
|
services: {
|
||||||
|
TestService: {
|
||||||
|
toCall: mockedFunction,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const hps = new HangingProtocolServiceClass(commandsManager, servicesManager);
|
||||||
let initialScaling;
|
let initialScaling;
|
||||||
|
|
||||||
beforeAll(() => {
|
afterEach(() => {
|
||||||
hps.addProtocol(testProtocol.id, testProtocol);
|
mockedFunction.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has one protocol', () => {
|
describe('with a static protocol', () => {
|
||||||
expect(hps.getProtocols().length).toBe(1);
|
beforeAll(() => {
|
||||||
|
hps.addProtocol(testProtocol.id, testProtocol);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has one protocol', () => {
|
||||||
|
expect(hps.getProtocols().length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('run', () => {
|
||||||
|
it('matches best image match', () => {
|
||||||
|
checkHpsBestMatch(hps);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('run', () => {
|
describe('with protocol generator', () => {
|
||||||
it('matches best image match', () => {
|
beforeAll(() => {
|
||||||
hps.run({ studies: [studyMatch], displaySets: studyMatchDisplaySets });
|
hps.addProtocol(testProtocol.id, testProtocolGenerator);
|
||||||
const { hpAlreadyApplied, viewportMatchDetails } = hps.getMatchDetails();
|
});
|
||||||
expect(hpAlreadyApplied).toMatchObject(new Map([[0, false]]));
|
|
||||||
expect(viewportMatchDetails.size).toBe(1);
|
it('has one protocol', () => {
|
||||||
expect(viewportMatchDetails.get(0)).toMatchObject({
|
expect(hps.getProtocols().length).toBe(1);
|
||||||
viewportOptions: {
|
});
|
||||||
viewportId: 'ctAXIAL',
|
|
||||||
viewportType: 'volume',
|
describe('run', () => {
|
||||||
orientation: 'axial',
|
it('matches best image match', () => {
|
||||||
toolGroupId: 'ctToolGroup',
|
checkHpsBestMatch(hps);
|
||||||
},
|
});
|
||||||
// Matches ds1 because it matches 2 rules, a required and an optional
|
|
||||||
// ds2 fails to match required and ds3 fails to match an optional.
|
it('uses services manager', () => {
|
||||||
displaySetsInfo: [
|
hps.run({ studies: [studyMatch], displaySets: studyMatchDisplaySets });
|
||||||
{
|
expect(mockedFunction).toHaveBeenCalledTimes(1);
|
||||||
SeriesInstanceUID: 'ds1',
|
|
||||||
displaySetInstanceUID: 'displaySet1',
|
|
||||||
displaySetOptions: {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const EVENTS = {
|
|||||||
'event::hanging_protocol_applied_for_viewport',
|
'event::hanging_protocol_applied_for_viewport',
|
||||||
};
|
};
|
||||||
|
|
||||||
type Protocol = HangingProtocol.Protocol;
|
type Protocol = HangingProtocol.Protocol | HangingProtocol.ProtocolGenerator;
|
||||||
|
|
||||||
class HangingProtocolService {
|
class HangingProtocolService {
|
||||||
studies: StudyMetadata[];
|
studies: StudyMetadata[];
|
||||||
@@ -180,7 +180,21 @@ class HangingProtocolService {
|
|||||||
public getProtocolById(id: string): HangingProtocol.Protocol {
|
public getProtocolById(id: string): HangingProtocol.Protocol {
|
||||||
const protocol = this.protocols.get(id);
|
const protocol = this.protocols.get(id);
|
||||||
|
|
||||||
return protocol;
|
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 this._validateProtocol(protocol);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -434,6 +448,23 @@ class HangingProtocolService {
|
|||||||
return protocol;
|
return protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getProtocolFromGenerator(
|
||||||
|
protocolGenerator: HangingProtocol.ProtocolGenerator
|
||||||
|
): {
|
||||||
|
protocol: HangingProtocol.Protocol;
|
||||||
|
} {
|
||||||
|
const { protocol } = protocolGenerator({
|
||||||
|
servicesManager: this._servicesManager,
|
||||||
|
commandsManager: this._commandsManager,
|
||||||
|
});
|
||||||
|
|
||||||
|
const validatedProtocol = this._validateProtocol(protocol);
|
||||||
|
|
||||||
|
return {
|
||||||
|
protocol: validatedProtocol,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
getViewportsRequireUpdate(viewportIndex, displaySetInstanceUID) {
|
getViewportsRequireUpdate(viewportIndex, displaySetInstanceUID) {
|
||||||
const newDisplaySetInstanceUID = displaySetInstanceUID;
|
const newDisplaySetInstanceUID = displaySetInstanceUID;
|
||||||
const protocol = this.protocol;
|
const protocol = this.protocol;
|
||||||
|
|||||||
@@ -151,6 +151,9 @@ type Protocol = {
|
|||||||
syncDataForViewports?: boolean;
|
syncDataForViewports?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ProtocolGenerator = ({ servicesManager: any, commandsManager: any }) => {
|
||||||
|
protocol: Protocol;
|
||||||
|
};
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
SetProtocolOptions,
|
SetProtocolOptions,
|
||||||
@@ -171,6 +174,6 @@ export type {
|
|||||||
DisplaySetInfo,
|
DisplaySetInfo,
|
||||||
GlobalProtocolOptions,
|
GlobalProtocolOptions,
|
||||||
ViewportSpecificProtocolOptions,
|
ViewportSpecificProtocolOptions,
|
||||||
DisplaySetAndViewportOptions
|
DisplaySetAndViewportOptions,
|
||||||
|
ProtocolGenerator,
|
||||||
};
|
};
|
||||||
@@ -78,7 +78,20 @@ 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,
|
'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'.
|
and can be set as the last module listed containing 'default'.
|
||||||
|
|
||||||
See the typescript definitions for more details on the structure.
|
A hanging protocol can also be defined with a generator.
|
||||||
|
A generator is a function we can write this way:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function protocolGenerator({ servicesManager, commandsManager }) {
|
||||||
|
// Some computations using services and commands ...
|
||||||
|
|
||||||
|
return {
|
||||||
|
protocol: generatedProtocol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See the typescript definitions for more details on the structure of protocols.
|
||||||
|
|
||||||
## Custom Attribute
|
## Custom Attribute
|
||||||
In some situations, you might want to match based on a custom attribute and not the DICOM tags. For instance,
|
In some situations, you might want to match based on a custom attribute and not the DICOM tags. For instance,
|
||||||
|
|||||||
@@ -2405,6 +2405,14 @@
|
|||||||
detect-gpu "^4.0.45"
|
detect-gpu "^4.0.45"
|
||||||
lodash.clonedeep "4.5.0"
|
lodash.clonedeep "4.5.0"
|
||||||
|
|
||||||
|
"@cornerstonejs/core@^0.25.1":
|
||||||
|
version "0.25.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-0.25.1.tgz#7e5c70858018d99417b61965ac606754542f77ad"
|
||||||
|
integrity sha512-/Ve9qwGyRRK1uUCrefh9cVG3OIaaS9LQoTUk9YMUbsoWxd6JitXh31XVHjlvNtBYGMRVdz+uGPS2zSxq1vi4aA==
|
||||||
|
dependencies:
|
||||||
|
detect-gpu "^4.0.45"
|
||||||
|
lodash.clonedeep "4.5.0"
|
||||||
|
|
||||||
"@cornerstonejs/streaming-image-volume-loader@^0.8.2":
|
"@cornerstonejs/streaming-image-volume-loader@^0.8.2":
|
||||||
version "0.8.2"
|
version "0.8.2"
|
||||||
resolved "https://registry.npmjs.org/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-0.8.2.tgz#058a591501e7fe7ebbe195695c92d047a288290f"
|
resolved "https://registry.npmjs.org/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-0.8.2.tgz#058a591501e7fe7ebbe195695c92d047a288290f"
|
||||||
@@ -2413,12 +2421,12 @@
|
|||||||
"@cornerstonejs/core" "^0.25.0"
|
"@cornerstonejs/core" "^0.25.0"
|
||||||
cornerstone-wado-image-loader "^4.7.0"
|
cornerstone-wado-image-loader "^4.7.0"
|
||||||
|
|
||||||
"@cornerstonejs/tools@^0.34.0":
|
"@cornerstonejs/tools@^0.34.2":
|
||||||
version "0.34.0"
|
version "0.34.2"
|
||||||
resolved "https://registry.npmjs.org/@cornerstonejs/tools/-/tools-0.34.0.tgz#d164f575b4870cea2f3300aaf88722a5f47e61f4"
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-0.34.2.tgz#645c97583066a872fd35188153cd21b26625473e"
|
||||||
integrity sha512-J+pcLsAobcX3oHuDOmaWvXLfofBCa7wivuKjkuEajssgm+pUHp+hAt+LAZz0fy1m2ZGOFUZn6TQjGbQg/nrYEQ==
|
integrity sha512-rbdQuSXGJ++ARS6eUAkBZi0tjmUKVtad0QPk0SZZXlDC2QAQRyIfq7n7oF2AaIR/0ngarTekUzuIy2nKiaOT+Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@cornerstonejs/core" "^0.25.0"
|
"@cornerstonejs/core" "^0.25.1"
|
||||||
lodash.clonedeep "4.5.0"
|
lodash.clonedeep "4.5.0"
|
||||||
lodash.get "^4.4.2"
|
lodash.get "^4.4.2"
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user