feat(hp): Add displayArea option for Hanging protocols and example with Mamo(#3808)

This commit is contained in:
Bill Wallace authored and GitHub committed 2024-05-29 11:31:09 -04:00
1 parent cb30b97610
commit 18ac08ed86
28 files changed
+771 -27

No files matched your search

+1 -1
View File
@@ -41,7 +41,7 @@
"react-dom": "^18.3.1",
"react-i18next": "^12.2.2",
"react-window": "^1.8.9",
"webpack": "^5.50.0",
"webpack": "^5.89.0",
"webpack-merge": "^5.7.3"
},
"dependencies": {
@@ -1,5 +1,7 @@
import hpMNGrid from './hpMNGrid';
import hpMNCompare from './hpCompare';
import hpMNGrid from './hangingprotocols/hpMNGrid';
import hpMNCompare from './hangingprotocols/hpCompare';
import hpMammography from './hangingprotocols/hpMammo';
import hpScale from './hangingprotocols/hpScale';
const defaultProtocol = {
id: 'default',
@@ -114,6 +116,14 @@ function getHangingProtocolModule() {
name: hpMNCompare.id,
protocol: hpMNCompare,
},
{
name: hpMammography.id,
protocol: hpMammography,
},
{
name: hpScale.id,
protocol: hpScale,
},
];
}
@@ -120,9 +120,9 @@ const hpMNCompare: Types.HangingProtocol.Protocol = {
{
id: 'Two Studies',
weight: 1000,
// is there a second study or in another work the attribute
// studyInstanceUIDsIndex that we get from prior should not be null
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: {
@@ -0,0 +1,201 @@
import {
RCC,
RMLO,
LCC,
LMLO,
RCCPrior,
LCCPrior,
RMLOPrior,
LMLOPrior,
} from './mammoDisplaySetSelector';
const rightDisplayArea = {
storeAsInitialCamera: true,
imageArea: [0.8, 0.8],
imageCanvasPoint: {
imagePoint: [0, 0.5],
canvasPoint: [0, 0.5],
},
};
const leftDisplayArea = {
storeAsInitialCamera: true,
imageArea: [0.8, 0.8],
imageCanvasPoint: {
imagePoint: [1, 0.5],
canvasPoint: [1, 0.5],
},
};
const hpMammography = {
id: '@ohif/hpMammo',
hasUpdatedPriorsInformation: false,
name: 'Mammography Breast Screening',
protocolMatchingRules: [
{
id: 'Mammography',
weight: 150,
attribute: 'ModalitiesInStudy',
constraint: {
contains: 'MG',
},
required: true,
},
{
id: 'numberOfImages',
attribute: 'numberOfDisplaySetsWithImages',
constraint: {
greaterThan: 2,
},
required: true,
},
],
toolGroupIds: ['default'],
displaySetSelectors: {
RCC,
LCC,
RMLO,
LMLO,
RCCPrior,
LCCPrior,
RMLOPrior,
LMLOPrior,
},
stages: [
{
name: 'CC/MLO',
viewportStructure: {
type: 'grid',
layoutType: 'grid',
properties: {
rows: 2,
columns: 2,
},
},
viewports: [
{
viewportOptions: {
toolGroupId: 'default',
displayArea: leftDisplayArea,
// flipHorizontal: true,
// rotation: 180,
allowUnmatchedView: true,
},
displaySets: [
{
id: 'RCC',
},
],
},
{
viewportOptions: {
toolGroupId: 'default',
// flipHorizontal: true,
displayArea: rightDisplayArea,
allowUnmatchedView: true,
},
displaySets: [
{
id: 'LCC',
},
],
},
{
viewportOptions: {
toolGroupId: 'default',
displayArea: leftDisplayArea,
// rotation: 180,
// flipHorizontal: true,
allowUnmatchedView: true,
},
displaySets: [
{
id: 'RMLO',
},
],
},
{
viewportOptions: {
toolGroupId: 'default',
displayArea: rightDisplayArea,
// flipHorizontal: true,
allowUnmatchedView: true,
},
displaySets: [
{
id: 'LMLO',
},
],
},
],
},
// Compare CC current/prior top/bottom
{
name: 'CC compare',
viewportStructure: {
type: 'grid',
layoutType: 'grid',
properties: {
rows: 2,
columns: 2,
},
},
viewports: [
{
viewportOptions: {
toolGroupId: 'default',
displayArea: leftDisplayArea,
flipHorizontal: true,
rotation: 180,
},
displaySets: [
{
id: 'RCC',
},
],
},
{
viewportOptions: {
toolGroupId: 'default',
flipHorizontal: true,
displayArea: rightDisplayArea,
},
displaySets: [
{
id: 'LCC',
},
],
},
{
viewportOptions: {
toolGroupId: 'default',
displayArea: leftDisplayArea,
flipHorizontal: true,
},
displaySets: [
{
id: 'RCCPrior',
},
],
},
{
viewportOptions: {
toolGroupId: 'default',
displayArea: rightDisplayArea,
},
displaySets: [
{
id: 'LCCPrior',
},
],
},
],
},
],
// Indicates it is prior aware, but will work with no priors
numberOfPriorsReferenced: 0,
};
export default hpMammography;
@@ -0,0 +1,131 @@
import { Types } from '@ohif/core';
const displayAreaScale1: Types.HangingProtocol.DisplayArea = {
type: 'SCALE',
scale: 1,
storeAsInitialCamera: true,
};
const displayAreaScale15: Types.HangingProtocol.DisplayArea = { ...displayAreaScale1, scale: 15 };
/**
* 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 hpScale: Types.HangingProtocol.Protocol = {
id: '@ohif/hpScale',
description: 'Has various hanging protocol grid layouts',
name: 'Scale Images',
protocolMatchingRules: [
{
id: 'OneOrMoreSeries',
weight: 25,
attribute: 'numberOfDisplaySetsWithImages',
constraint: {
greaterThan: 0,
},
},
],
toolGroupIds: ['default'],
displaySetSelectors: {
defaultDisplaySetId: {
seriesMatchingRules: [
{
attribute: 'numImageFrames',
constraint: {
greaterThan: { value: 0 },
},
required: true,
},
// 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,
},
},
],
},
},
defaultViewport: {
viewportOptions: {
viewportType: 'stack',
toolGroupId: 'default',
displayArea: displayAreaScale1,
allowUnmatchedView: true,
},
displaySets: [
{
id: 'defaultDisplaySetId',
matchedDisplaySetsIndex: -1,
},
],
},
stages: [
// A 1x1 stage - should be automatically activated if there is only 1 viewable instance
{
name: 'Scale 1:1',
stageActivation: {
enabled: {
minViewportsMatched: 1,
},
},
viewportStructure: {
layoutType: 'grid',
properties: {
rows: 1,
columns: 1,
},
},
viewports: [
{
viewportOptions: {
toolGroupId: 'default',
allowUnmatchedView: true,
displayArea: displayAreaScale1,
},
displaySets: [
{
id: 'defaultDisplaySetId',
},
],
},
],
},
{
name: 'Scale 1:15',
stageActivation: {
enabled: {
minViewportsMatched: 1,
},
},
viewportStructure: {
layoutType: 'grid',
properties: {
rows: 1,
columns: 1,
},
},
viewports: [
{
viewportOptions: {
toolGroupId: 'default',
allowUnmatchedView: true,
displayArea: displayAreaScale15,
},
displaySets: [
{
id: 'defaultDisplaySetId',
},
],
},
],
},
],
numberOfPriorsReferenced: -1,
};
export default hpScale;
@@ -0,0 +1,15 @@
import viewCodeAttribute from './viewCode';
import lateralityAttribute from './laterality';
import registerHangingProtocolAttributes from './registerHangingProtocolAttributes';
import hpMammography from './hpMammo';
import hpMNGrid from './hpMNGrid';
import hpCompare from './hpCompare';
export {
viewCodeAttribute,
lateralityAttribute,
hpMammography as hpMammo,
hpMNGrid,
hpCompare,
registerHangingProtocolAttributes,
};
@@ -0,0 +1,9 @@
export default displaySet => {
const frameAnatomy =
displaySet?.images?.[0]?.SharedFunctionalGroupsSequence?.[0]?.FrameAnatomySequence?.[0];
if (!frameAnatomy) {
return undefined;
}
const laterality = frameAnatomy?.FrameLaterality;
return laterality;
};
@@ -0,0 +1,214 @@
const priorStudyMatchingRules = [
{
// 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 },
},
},
];
const currentStudyMatchingRules = [
{
// 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 },
},
},
];
const LCCSeriesMatchingRules = [
{
weight: 10,
attribute: 'ViewCode',
constraint: {
contains: 'SCT:399162004',
},
},
{
weight: 5,
attribute: 'PatientOrientation',
constraint: {
contains: 'L',
},
},
{
weight: 20,
attribute: 'SeriesDescription',
constraint: {
contains: 'L CC',
},
},
];
const RCCSeriesMatchingRules = [
{
weight: 10,
attribute: 'ViewCode',
constraint: {
contains: 'SCT:399162004',
},
},
{
weight: 5,
attribute: 'PatientOrientation',
constraint: {
equals: ['P', 'L'],
},
},
{
attribute: 'PatientOrientation',
constraint: {
doesNotEqual: ['A', 'R'],
},
required: true,
},
{
weight: 20,
attribute: 'SeriesDescription',
constraint: {
contains: 'CC',
},
},
];
const LMLOSeriesMatchingRules = [
{
weight: 10,
attribute: 'ViewCode',
constraint: {
contains: 'SCT:399368009',
},
},
{
weight: 0,
attribute: 'ViewCode',
constraint: {
doesNotEqual: 'SCT:399162004',
},
required: true,
},
{
weight: 5,
attribute: 'PatientOrientation',
constraint: {
equals: ['A', 'R'],
},
},
{
weight: 20,
attribute: 'SeriesDescription',
constraint: {
contains: 'L MLO',
},
},
];
const RMLOSeriesMatchingRules = [
{
weight: 10,
attribute: 'ViewCode',
constraint: {
contains: 'SCT:399368009',
},
},
{
attribute: 'ViewCode',
constraint: {
doesNotEqual: 'SCT:399162004',
},
required: true,
},
{
attribute: 'PatientOrientation',
constraint: {
doesNotContain: ['P', 'FL'],
},
required: true,
},
{
weight: 5,
attribute: 'PatientOrientation',
constraint: {
equals: ['P', 'L'],
},
},
{
weight: 5,
attribute: 'PatientOrientation',
constraint: {
equals: ['A', 'FR'],
},
},
{
weight: 20,
attribute: 'SeriesDescription',
constraint: {
contains: 'R MLO',
},
},
{
attribute: 'SeriesDescription',
required: true,
constraint: {
doesNotContain: 'CC',
},
},
{
attribute: 'SeriesDescription',
required: true,
constraint: {
doesNotEqual: 'L MLO',
},
required: true,
},
];
const RCC = {
seriesMatchingRules: RCCSeriesMatchingRules,
studyMatchingRules: currentStudyMatchingRules,
};
const RCCPrior = {
seriesMatchingRules: RCCSeriesMatchingRules,
studyMatchingRules: priorStudyMatchingRules,
};
const LCC = {
seriesMatchingRules: LCCSeriesMatchingRules,
studyMatchingRules: currentStudyMatchingRules,
};
const LCCPrior = {
seriesMatchingRules: LCCSeriesMatchingRules,
studyMatchingRules: priorStudyMatchingRules,
};
const RMLO = {
seriesMatchingRules: RMLOSeriesMatchingRules,
studyMatchingRules: currentStudyMatchingRules,
};
const RMLOPrior = {
seriesMatchingRules: RMLOSeriesMatchingRules,
studyMatchingRules: priorStudyMatchingRules,
};
const LMLO = {
seriesMatchingRules: LMLOSeriesMatchingRules,
studyMatchingRules: currentStudyMatchingRules,
};
const LMLOPrior = {
seriesMatchingRules: LMLOSeriesMatchingRules,
studyMatchingRules: priorStudyMatchingRules,
};
export { RCC, LCC, RMLO, LMLO, RCCPrior, LCCPrior, RMLOPrior, LMLOPrior };
@@ -0,0 +1,8 @@
import viewCode from './viewCode';
import laterality from './laterality';
export default function registerHangingProtocolAttributes({ servicesManager }) {
const { hangingProtocolService } = servicesManager.services;
hangingProtocolService.addCustomAttribute('ViewCode', 'View Code Designator:Value', viewCode);
hangingProtocolService.addCustomAttribute('Laterality', 'Laterality of object', laterality);
}
@@ -0,0 +1,11 @@
export default displaySet => {
const ViewCodeSequence = displaySet?.images[0]?.ViewCodeSequence[0];
if (!ViewCodeSequence) {
return undefined;
}
const { CodingSchemeDesignator, CodeValue } = ViewCodeSequence;
if (!CodingSchemeDesignator || !CodeValue) {
return undefined;
}
return `${CodingSchemeDesignator}:${CodeValue}`;
};
+4
View File
@@ -2,6 +2,7 @@ import { DicomMetadataStore, classes } from '@ohif/core';
import { calculateSUVScalingFactors } from '@cornerstonejs/calculate-suv';
import getPTImageIdInstanceMetadata from './getPTImageIdInstanceMetadata';
import { registerHangingProtocolAttributes } from './hangingprotocols';
const metadataProvider = classes.MetadataProvider;
@@ -60,6 +61,9 @@ export default function init({
// afterwards.
stateSyncService.register('viewportsByPosition', { clearOnModeExit: true });
// Adds extra custom attributes for use by hanging protocols
registerHangingProtocolAttributes({ servicesManager });
// Function to process and subscribe to events for a given set of commands and listeners
const subscribeToEvents = listeners => {
Object.entries(listeners).forEach(([event, commands]) => {