feat(hp priors): Study comparison hanging protocol (#3579)
This commit is contained in:
parent
ecbe7c56e0
commit
53c16b928f
@ -5,7 +5,6 @@ const segProtocol: Types.HangingProtocol.Protocol = {
|
|||||||
// Don't store this hanging protocol as it applies to the currently active
|
// Don't store this hanging protocol as it applies to the currently active
|
||||||
// display set by default
|
// display set by default
|
||||||
// cacheId: null,
|
// cacheId: null,
|
||||||
hasUpdatedPriorsInformation: false,
|
|
||||||
name: 'Segmentations',
|
name: 'Segmentations',
|
||||||
// Just apply this one when specifically listed
|
// Just apply this one when specifically listed
|
||||||
protocolMatchingRules: [],
|
protocolMatchingRules: [],
|
||||||
|
|||||||
@ -5,7 +5,6 @@ const srProtocol: Types.HangingProtocol.Protocol = {
|
|||||||
// Don't store this hanging protocol as it applies to the currently active
|
// Don't store this hanging protocol as it applies to the currently active
|
||||||
// display set by default
|
// display set by default
|
||||||
// cacheId: null,
|
// cacheId: null,
|
||||||
hasUpdatedPriorsInformation: false,
|
|
||||||
name: 'SR Key Images',
|
name: 'SR Key Images',
|
||||||
// Just apply this one when specifically listed
|
// Just apply this one when specifically listed
|
||||||
protocolMatchingRules: [],
|
protocolMatchingRules: [],
|
||||||
|
|||||||
@ -4,9 +4,8 @@ const mpr: Types.HangingProtocol.Protocol = {
|
|||||||
id: 'mpr',
|
id: 'mpr',
|
||||||
name: 'Multi-Planar Reconstruction',
|
name: 'Multi-Planar Reconstruction',
|
||||||
locked: true,
|
locked: true,
|
||||||
hasUpdatedPriorsInformation: false,
|
|
||||||
createdDate: '2021-02-23',
|
createdDate: '2021-02-23',
|
||||||
modifiedDate: '2023-04-03',
|
modifiedDate: '2023-08-15',
|
||||||
availableTo: {},
|
availableTo: {},
|
||||||
editableBy: {},
|
editableBy: {},
|
||||||
// Unknown number of priors referenced - so just match any study
|
// Unknown number of priors referenced - so just match any study
|
||||||
@ -164,7 +163,6 @@ const mpr: Types.HangingProtocol.Protocol = {
|
|||||||
const mprAnd3DVolumeViewport = {
|
const mprAnd3DVolumeViewport = {
|
||||||
id: 'mprAnd3DVolumeViewport',
|
id: 'mprAnd3DVolumeViewport',
|
||||||
locked: true,
|
locked: true,
|
||||||
hasUpdatedPriorsInformation: false,
|
|
||||||
name: 'mpr',
|
name: 'mpr',
|
||||||
createdDate: '2023-03-15T10:29:44.894Z',
|
createdDate: '2023-03-15T10:29:44.894Z',
|
||||||
modifiedDate: '2023-03-15T10:29:44.894Z',
|
modifiedDate: '2023-03-15T10:29:44.894Z',
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import hpMNGrid from './hpMNGrid';
|
import hpMNGrid from './hpMNGrid';
|
||||||
|
import hpMNCompare from './hpCompare';
|
||||||
|
|
||||||
const defaultProtocol = {
|
const defaultProtocol = {
|
||||||
id: 'default',
|
id: 'default',
|
||||||
@ -6,7 +7,6 @@ const defaultProtocol = {
|
|||||||
// Don't store this hanging protocol as it applies to the currently active
|
// Don't store this hanging protocol as it applies to the currently active
|
||||||
// display set by default
|
// display set by default
|
||||||
// cacheId: null,
|
// cacheId: null,
|
||||||
hasUpdatedPriorsInformation: false,
|
|
||||||
name: 'Default',
|
name: 'Default',
|
||||||
createdDate: '2021-02-23T19:22:08.894Z',
|
createdDate: '2021-02-23T19:22:08.894Z',
|
||||||
modifiedDate: '2023-04-01',
|
modifiedDate: '2023-04-01',
|
||||||
@ -108,6 +108,11 @@ function getHangingProtocolModule() {
|
|||||||
name: hpMNGrid.id,
|
name: hpMNGrid.id,
|
||||||
protocol: hpMNGrid,
|
protocol: hpMNGrid,
|
||||||
},
|
},
|
||||||
|
// Create a MxN comparison hanging protocol available by default
|
||||||
|
{
|
||||||
|
name: hpMNCompare.id,
|
||||||
|
protocol: hpMNCompare,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
193
extensions/default/src/hpCompare.ts
Normal file
193
extensions/default/src/hpCompare.ts
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
import { Types } from '@ohif/core';
|
||||||
|
|
||||||
|
const defaultDisplaySetSelector = {
|
||||||
|
studyMatchingRules: [
|
||||||
|
{
|
||||||
|
// The priorInstance is a study counter that indicates what position this study is in
|
||||||
|
// and the value comes from the options parameter.
|
||||||
|
attribute: 'studyInstanceUIDsIndex',
|
||||||
|
from: 'options',
|
||||||
|
required: true,
|
||||||
|
constraint: {
|
||||||
|
equals: { value: 0 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
seriesMatchingRules: [
|
||||||
|
{
|
||||||
|
attribute: 'numImageFrames',
|
||||||
|
constraint: {
|
||||||
|
greaterThan: { value: 0 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// This display set will select the specified items by preference
|
||||||
|
// It has no affect if nothing is specified in the URL.
|
||||||
|
{
|
||||||
|
attribute: 'isDisplaySetFromUrl',
|
||||||
|
weight: 10,
|
||||||
|
constraint: {
|
||||||
|
equals: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const priorDisplaySetSelector = {
|
||||||
|
studyMatchingRules: [
|
||||||
|
{
|
||||||
|
// The priorInstance is a study counter that indicates what position this study is in
|
||||||
|
// and the value comes from the options parameter.
|
||||||
|
attribute: 'studyInstanceUIDsIndex',
|
||||||
|
from: 'options',
|
||||||
|
required: true,
|
||||||
|
constraint: {
|
||||||
|
equals: { value: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
seriesMatchingRules: [
|
||||||
|
{
|
||||||
|
attribute: 'numImageFrames',
|
||||||
|
constraint: {
|
||||||
|
greaterThan: { value: 0 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// This display set will select the specified items by preference
|
||||||
|
// It has no affect if nothing is specified in the URL.
|
||||||
|
{
|
||||||
|
attribute: 'isDisplaySetFromUrl',
|
||||||
|
weight: 10,
|
||||||
|
constraint: {
|
||||||
|
equals: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentDisplaySet = {
|
||||||
|
id: 'defaultDisplaySetId',
|
||||||
|
};
|
||||||
|
|
||||||
|
const priorDisplaySet = {
|
||||||
|
id: 'priorDisplaySetId',
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentViewport0 = {
|
||||||
|
viewportOptions: {
|
||||||
|
toolGroupId: 'default',
|
||||||
|
allowUnmatchedView: true,
|
||||||
|
},
|
||||||
|
displaySets: [currentDisplaySet],
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentViewport1 = {
|
||||||
|
...currentViewport0,
|
||||||
|
displaySets: [
|
||||||
|
{
|
||||||
|
...currentDisplaySet,
|
||||||
|
matchedDisplaySetsIndex: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const priorViewport0 = {
|
||||||
|
...currentViewport0,
|
||||||
|
displaySets: [priorDisplaySet],
|
||||||
|
};
|
||||||
|
|
||||||
|
const priorViewport1 = {
|
||||||
|
...priorViewport0,
|
||||||
|
displaySets: [
|
||||||
|
{
|
||||||
|
...priorDisplaySet,
|
||||||
|
matchedDisplaySetsIndex: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This hanging protocol can be activated on the primary mode by directly
|
||||||
|
* referencing it in a URL or by directly including it within a mode, e.g.:
|
||||||
|
* `&hangingProtocolId=@ohif/mnGrid` added to the viewer URL
|
||||||
|
* It is not included in the viewer mode by default.
|
||||||
|
*/
|
||||||
|
const hpMNCompare: Types.HangingProtocol.Protocol = {
|
||||||
|
id: '@ohif/hpCompare',
|
||||||
|
description: 'Compare two studies in various layouts',
|
||||||
|
name: 'Compare Two Studies',
|
||||||
|
numberOfPriorsReferenced: 1,
|
||||||
|
protocolMatchingRules: [
|
||||||
|
{
|
||||||
|
id: 'Two Studies',
|
||||||
|
weight: 1000,
|
||||||
|
attribute: 'StudyInstanceUID',
|
||||||
|
// The 'from' attribute says where to get the 'attribute' value from. In this case
|
||||||
|
// prior means the second study in the study list.
|
||||||
|
from: 'prior',
|
||||||
|
required: true,
|
||||||
|
constraint: {
|
||||||
|
notNull: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
toolGroupIds: ['default'],
|
||||||
|
displaySetSelectors: {
|
||||||
|
defaultDisplaySetId: defaultDisplaySetSelector,
|
||||||
|
priorDisplaySetId: priorDisplaySetSelector,
|
||||||
|
},
|
||||||
|
defaultViewport: {
|
||||||
|
viewportOptions: {
|
||||||
|
viewportType: 'stack',
|
||||||
|
toolGroupId: 'default',
|
||||||
|
allowUnmatchedView: true,
|
||||||
|
},
|
||||||
|
displaySets: [
|
||||||
|
{
|
||||||
|
id: 'defaultDisplaySetId',
|
||||||
|
matchedDisplaySetsIndex: -1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
stages: [
|
||||||
|
{
|
||||||
|
name: '2x2',
|
||||||
|
stageActivation: {
|
||||||
|
enabled: {
|
||||||
|
minViewportsMatched: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
viewportStructure: {
|
||||||
|
layoutType: 'grid',
|
||||||
|
properties: {
|
||||||
|
rows: 2,
|
||||||
|
columns: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
viewports: [
|
||||||
|
currentViewport0,
|
||||||
|
priorViewport0,
|
||||||
|
currentViewport1,
|
||||||
|
priorViewport1,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: '2x1',
|
||||||
|
stageActivation: {
|
||||||
|
enabled: {
|
||||||
|
minViewportsMatched: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
viewportStructure: {
|
||||||
|
layoutType: 'grid',
|
||||||
|
properties: {
|
||||||
|
rows: 1,
|
||||||
|
columns: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
viewports: [currentViewport0, priorViewport0],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default hpMNCompare;
|
||||||
@ -7,7 +7,6 @@ import { Types } from '@ohif/core';
|
|||||||
* It is not included in the viewer mode by default.
|
* It is not included in the viewer mode by default.
|
||||||
*/
|
*/
|
||||||
const hpMN: Types.HangingProtocol.Protocol = {
|
const hpMN: Types.HangingProtocol.Protocol = {
|
||||||
hasUpdatedPriorsInformation: false,
|
|
||||||
id: '@ohif/mnGrid',
|
id: '@ohif/mnGrid',
|
||||||
description: 'Has various hanging protocol grid layouts',
|
description: 'Has various hanging protocol grid layouts',
|
||||||
name: '2x2',
|
name: '2x2',
|
||||||
|
|||||||
@ -217,7 +217,6 @@ const stage4 = {
|
|||||||
const ptCT = {
|
const ptCT = {
|
||||||
id: '@ohif/extension-tmtv.hangingProtocolModule.ptCT',
|
id: '@ohif/extension-tmtv.hangingProtocolModule.ptCT',
|
||||||
locked: true,
|
locked: true,
|
||||||
hasUpdatedPriorsInformation: false,
|
|
||||||
name: 'Default',
|
name: 'Default',
|
||||||
createdDate: '2021-02-23T19:22:08.894Z',
|
createdDate: '2021-02-23T19:22:08.894Z',
|
||||||
modifiedDate: '2022-10-04T19:22:08.894Z',
|
modifiedDate: '2022-10-04T19:22:08.894Z',
|
||||||
|
|||||||
28
platform/app/cypress/integration/MultiStudy.spec.js
Normal file
28
platform/app/cypress/integration/MultiStudy.spec.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
describe('OHIF Multi Study', () => {
|
||||||
|
const beforeSetup = () => {
|
||||||
|
cy.checkStudyRouteInViewer(
|
||||||
|
'1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1,1.2.840.113619.2.5.1762583153.215519.978957063.78',
|
||||||
|
'&hangingProtocolId=@ohif/hpCompare'
|
||||||
|
);
|
||||||
|
cy.expectMinimumThumbnails(4);
|
||||||
|
cy.initCornerstoneToolsAliases();
|
||||||
|
cy.initCommonElementsAliases();
|
||||||
|
};
|
||||||
|
|
||||||
|
it('Should display 2 comparison up', () => {
|
||||||
|
beforeSetup();
|
||||||
|
|
||||||
|
cy.get('[data-cy="viewport-pane"]')
|
||||||
|
.its('length')
|
||||||
|
.should('be.eq', 4);
|
||||||
|
cy.get('[data-cy="studyDate"]').should(studyDate => {
|
||||||
|
expect(studyDate.length).to.be.eq(4);
|
||||||
|
expect(studyDate.text())
|
||||||
|
.to.contain('2014')
|
||||||
|
.contain('2001');
|
||||||
|
expect(studyDate.text().indexOf('2014')).to.be.lessThan(
|
||||||
|
studyDate.text().indexOf('2001')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -2,7 +2,6 @@ import HangingProtocolService from './HangingProtocolService';
|
|||||||
|
|
||||||
const testProtocol = {
|
const testProtocol = {
|
||||||
id: 'test',
|
id: 'test',
|
||||||
hasUpdatedPriorsInformation: false,
|
|
||||||
name: 'Default',
|
name: 'Default',
|
||||||
protocolMatchingRules: [
|
protocolMatchingRules: [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -161,6 +161,9 @@ export default class HangingProtocolService extends PubSubService {
|
|||||||
this.studies = [];
|
this.studies = [];
|
||||||
this.viewportMatchDetails = new Map();
|
this.viewportMatchDetails = new Map();
|
||||||
this.displaySetMatchDetails = new Map();
|
this.displaySetMatchDetails = new Map();
|
||||||
|
this.protocol = undefined;
|
||||||
|
this.stageIndex = undefined;
|
||||||
|
this.protocolEngine = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Leave the hanging protocol in the initialized state */
|
/** Leave the hanging protocol in the initialized state */
|
||||||
@ -908,7 +911,7 @@ export default class HangingProtocolService extends PubSubService {
|
|||||||
const stage = stages[i];
|
const stage = stages[i];
|
||||||
if (stage.id === stageId && stage.status !== 'disabled') {
|
if (stage.id === stageId && stage.status !== 'disabled') {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1221,7 +1224,7 @@ export default class HangingProtocolService extends PubSubService {
|
|||||||
for (const subMatch of match.matchingScores) {
|
for (const subMatch of match.matchingScores) {
|
||||||
if (subMatch.displaySetInstanceUID === displaySetUID) {
|
if (subMatch.displaySetInstanceUID === displaySetUID) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Reused viewport details ${id} with ds ${displaySetUID} not valid`
|
`Reused viewport details ${id} with ds ${displaySetUID} not valid`
|
||||||
@ -1421,25 +1424,26 @@ export default class HangingProtocolService extends PubSubService {
|
|||||||
seriesMatchingRules
|
seriesMatchingRules
|
||||||
);
|
);
|
||||||
const matchActiveOnly = this.protocol.numberOfPriorsReferenced === -1;
|
const matchActiveOnly = this.protocol.numberOfPriorsReferenced === -1;
|
||||||
this.studies.forEach(study => {
|
this.studies.forEach((study, studyInstanceUIDsIndex) => {
|
||||||
// Skip non-active if active only
|
// Skip non-active if active only
|
||||||
if (matchActiveOnly && this.activeStudy !== study) {
|
if (matchActiveOnly && this.activeStudy !== study) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const studyDisplaySets = this.displaySets.filter(
|
const studyDisplaySets = this.displaySets.filter(
|
||||||
it => it.StudyInstanceUID === study.StudyInstanceUID
|
it => it.StudyInstanceUID === study.StudyInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
const studyMatchDetails = this.protocolEngine.findMatch(
|
const studyMatchDetails = this.protocolEngine.findMatch(
|
||||||
study,
|
study,
|
||||||
studyMatchingRules,
|
studyMatchingRules,
|
||||||
{
|
{
|
||||||
studies: this.studies,
|
studies: this.studies,
|
||||||
displaySets: studyDisplaySets,
|
displaySets: studyDisplaySets,
|
||||||
allDisplaySets: this.displaySets,
|
allDisplaySets: this.displaySets,
|
||||||
displaySetMatchDetails: this.displaySetMatchDetails,
|
displaySetMatchDetails: this.displaySetMatchDetails,
|
||||||
}
|
studyInstanceUIDsIndex,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Prevent bestMatch from being updated if the matchDetails' required attribute check has failed
|
// Prevent bestMatch from being updated if the matchDetails' required attribute check has failed
|
||||||
@ -1448,10 +1452,10 @@ export default class HangingProtocolService extends PubSubService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.debug(
|
this.debug(
|
||||||
'study',
|
'study',
|
||||||
study.StudyInstanceUID,
|
study.StudyInstanceUID,
|
||||||
'display sets #',
|
'display sets #',
|
||||||
studyDisplaySets.length
|
studyDisplaySets.length
|
||||||
);
|
);
|
||||||
studyDisplaySets.forEach(displaySet => {
|
studyDisplaySets.forEach(displaySet => {
|
||||||
const {
|
const {
|
||||||
@ -1460,15 +1464,15 @@ export default class HangingProtocolService extends PubSubService {
|
|||||||
displaySetInstanceUID,
|
displaySetInstanceUID,
|
||||||
} = displaySet;
|
} = displaySet;
|
||||||
const seriesMatchDetails = this.protocolEngine.findMatch(
|
const seriesMatchDetails = this.protocolEngine.findMatch(
|
||||||
displaySet,
|
displaySet,
|
||||||
seriesMatchingRules,
|
seriesMatchingRules,
|
||||||
// Todo: why we have images here since the matching type does not have it
|
// Todo: why we have images here since the matching type does not have it
|
||||||
{
|
{
|
||||||
studies: this.studies,
|
studies: this.studies,
|
||||||
instance: displaySet.images?.[0],
|
instance: displaySet.images?.[0],
|
||||||
displaySetMatchDetails: this.displaySetMatchDetails,
|
displaySetMatchDetails: this.displaySetMatchDetails,
|
||||||
displaySets: studyDisplaySets,
|
displaySets: studyDisplaySets,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Prevent bestMatch from being updated if the matchDetails' required attribute check has failed
|
// Prevent bestMatch from being updated if the matchDetails' required attribute check has failed
|
||||||
|
|||||||
@ -270,7 +270,6 @@ export type Protocol = {
|
|||||||
stages: ProtocolStage[];
|
stages: ProtocolStage[];
|
||||||
// Optional
|
// Optional
|
||||||
locked?: boolean;
|
locked?: boolean;
|
||||||
hasUpdatedPriorsInformation?: boolean;
|
|
||||||
name?: string;
|
name?: string;
|
||||||
createdDate?: string;
|
createdDate?: string;
|
||||||
modifiedDate?: string;
|
modifiedDate?: string;
|
||||||
@ -284,7 +283,9 @@ export type Protocol = {
|
|||||||
/* The number of priors required for this hanging protocol.
|
/* The number of priors required for this hanging protocol.
|
||||||
* -1 means that NO priors are referenced, and thus this HP matches
|
* -1 means that NO priors are referenced, and thus this HP matches
|
||||||
* only the active study, whereas 0 means that an unknown number of
|
* only the active study, whereas 0 means that an unknown number of
|
||||||
* priors is matched.
|
* priors is matched. Positive values mean at least that many priors are
|
||||||
|
* required.
|
||||||
|
* Replaces hasUpdatedPriors
|
||||||
*/
|
*/
|
||||||
numberOfPriorsReferenced?: number;
|
numberOfPriorsReferenced?: number;
|
||||||
syncDataForViewports?: boolean;
|
syncDataForViewports?: boolean;
|
||||||
|
|||||||
@ -36,7 +36,6 @@ Here is an example protocol which if used will hang a 1x3 layout with the first
|
|||||||
const oneByThreeProtocol = {
|
const oneByThreeProtocol = {
|
||||||
id: 'oneByThreeProtocol',
|
id: 'oneByThreeProtocol',
|
||||||
locked: true,
|
locked: true,
|
||||||
hasUpdatedPriorsInformation: false,
|
|
||||||
name: 'Default',
|
name: 'Default',
|
||||||
createdDate: '2021-02-23T19:22:08.894Z',
|
createdDate: '2021-02-23T19:22:08.894Z',
|
||||||
modifiedDate: '2022-10-04T19:22:08.894Z',
|
modifiedDate: '2022-10-04T19:22:08.894Z',
|
||||||
@ -241,6 +240,9 @@ A list of criteria for the protocol along with the provided points for ranking.
|
|||||||
"StudyDescription", "ModalitiesInStudy", "NumberOfStudyRelatedSeries", "NumberOfSeriesRelatedInstances"
|
"StudyDescription", "ModalitiesInStudy", "NumberOfStudyRelatedSeries", "NumberOfSeriesRelatedInstances"
|
||||||
In addition to these tags, you can also use a custom attribute that you have registered before.
|
In addition to these tags, you can also use a custom attribute that you have registered before.
|
||||||
We will learn more about this later.
|
We will learn more about this later.
|
||||||
|
- `from`: Indicates the source of the attribute. This allows getting values
|
||||||
|
from other objects such as the `prior` instance object instead of from the
|
||||||
|
current one.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -279,6 +281,20 @@ A list of criteria for the protocol along with the provided points for ranking.
|
|||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `from` attribute
|
||||||
|
The from attribute allows getting the attribute to test from some other object
|
||||||
|
such as the prior study, the list of studies overall or another module provided
|
||||||
|
value. Some of the possible attributes are:
|
||||||
|
|
||||||
|
* `prior`: To get the value from the prior study.
|
||||||
|
* `activeStudy`: To match the active study
|
||||||
|
* `studies`: To match the list of studies to display
|
||||||
|
* `displaySets`: The display sets for the current study
|
||||||
|
* `allDisplaySets`: Alll available display sets
|
||||||
|
* `instance`: An instance from the current display set being tested
|
||||||
|
* `options`: Gets the options object itself, eg if you want a simple top level
|
||||||
|
value.
|
||||||
|
|
||||||
### displaySetSelectors
|
### displaySetSelectors
|
||||||
Defines the display sets that the protocol will use for arrangement.
|
Defines the display sets that the protocol will use for arrangement.
|
||||||
|
|
||||||
@ -475,22 +491,70 @@ HangingProtocolService.addCustomAttribute(
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Matching on Prior Study with UID
|
## Matching on Prior Study with UID
|
||||||
|
|
||||||
Often it is desired to match a new study to a prior study (e.g., follow up on
|
Often it is desired to match a new study to a prior study (e.g., follow up on
|
||||||
a surgery). Since the hanging protocols run on displaySets we need to have a
|
a surgery). Since the hanging protocols run on displaySets we need to have a
|
||||||
way to let OHIF knows that it needs to load the prior study as well. This can
|
way to let OHIF knows that it needs to load the prior study as well. This can
|
||||||
be done by specifying both StudyInstanceUIDs in the URL. Below we are
|
be done by specifying both StudyInstanceUIDs in the URL. The additional studies
|
||||||
running OHIF with two studies
|
are then accessible to the hanging protocol. Below we are
|
||||||
|
running OHIF with two studies, and a comparison hanging protocol available by
|
||||||
|
default.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
http://localhost:3000/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5&StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095722.1
|
http://localhost:3000/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5&StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095722.1&hangingprotocolId=@ohif/hpCompare
|
||||||
```
|
```
|
||||||
|
|
||||||
Now you have access to both studies and you can use matchingRules to match
|
The `&hangingProtocolId` option forces the specific hanging protocol to be
|
||||||
displaySets.
|
applied, but the mode can also add the hanging protocols to the default set,
|
||||||
|
and then the best matching hanging protocol will be applied by the run method.
|
||||||
|
|
||||||
|
To match any other studies, it is required to enable the prior matching rules
|
||||||
|
capability using:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Indicate number of priors used - 0 means any number, -1 means none.
|
||||||
|
numberOfPriorsReferenced: 1,
|
||||||
|
```
|
||||||
|
|
||||||
Our roadmap includes enabling matching on prior studies without the UID (e.g., baseline, most recent and index).
|
The matching rule that allows the hanging protocol to be runnable is:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
protocolMatchingRules: [
|
||||||
|
{
|
||||||
|
id: 'Two Studies',
|
||||||
|
weight: 1000,
|
||||||
|
// This will generate 1.3.6.1.4.1.25403.345050719074.3824.20170125095722.1
|
||||||
|
// since that is study instance UID in the prior from instance.
|
||||||
|
attribute: 'StudyInstanceUID',
|
||||||
|
// The 'from' attribute says where to get the 'attribute' value from. In this case
|
||||||
|
// prior means the second study in the study list.
|
||||||
|
from: 'prior',
|
||||||
|
required: true,
|
||||||
|
constraint: {
|
||||||
|
notNull: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
The display set selector selecting the specific study to display is included
|
||||||
|
in the studyMatchingRules. Note that this rule will cause ONLY the second study
|
||||||
|
to be matched, so it won't attempt to match anything in other studies.
|
||||||
|
Additional series level criteria, such as modality rules must be included at the
|
||||||
|
`seriesMatchingRules`.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
studyMatchingRules: [
|
||||||
|
{
|
||||||
|
// The priorInstance is a study counter that indicates what position this study is in
|
||||||
|
// and the value comes from the options parameter.
|
||||||
|
attribute: 'studyInstanceUIDsIndex',
|
||||||
|
from: 'options',
|
||||||
|
required: true,
|
||||||
|
constraint: {
|
||||||
|
equals: { value: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|||||||
@ -133,7 +133,11 @@ const ViewportActionBar = ({
|
|||||||
<span className="ml-1 text-aqua-pale text-large">{label}</span>
|
<span className="ml-1 text-aqua-pale text-large">{label}</span>
|
||||||
)}
|
)}
|
||||||
<div className={separatorClasses}></div>
|
<div className={separatorClasses}></div>
|
||||||
<span ref={studyDateElemRef} className={studyDateClasses()}>
|
<span
|
||||||
|
data-cy="studyDate"
|
||||||
|
ref={studyDateElemRef}
|
||||||
|
className={studyDateClasses()}
|
||||||
|
>
|
||||||
{studyDate}
|
{studyDate}
|
||||||
</span>
|
</span>
|
||||||
{showSeriesDesc && (
|
{showSeriesDesc && (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user