feat(hp): Add displayArea option for Hanging protocols and example with Mamo(#3808)
This commit is contained in:
1 parent
cb30b97610
commit
18ac08ed86
28 files changed
+771
-27
No files matched your search
@@ -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,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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: {
|
||||
File renamed without changes.
@@ -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}`;
|
||||
};
|
||||
@@ -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]) => {
|
||||
|
||||
Reference in new issue
Block a user