feat(hp): Add displayArea option for Hanging protocols and example with Mamo(#3808)
This commit is contained in:
parent
cb30b97610
commit
18ac08ed86
@ -51,7 +51,6 @@ provided by the <a href="https://ohif.org/">Open Health Imaging Foundation (OHIF
|
||||
| <img src="https://github.com/OHIF/Viewers/blob/master/platform/docs/docs/assets/img/demo-video.webp?raw=true" alt="VIDEO" width="350"/> | Video | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=2.25.96975534054447904995905761963464388233) |
|
||||
| <img src="https://github.com/OHIF/Viewers/blob/master/platform/docs/docs/assets/img/microscopy.webp?raw=true" alt="microscopy" width="350"/> | Slide Microscopy | [Demo](https://viewer.ohif.org/microscopy?StudyInstanceUIDs=2.25.141277760791347900862109212450152067508) |
|
||||
|
||||
|
||||
## About
|
||||
|
||||
The OHIF Viewer can retrieve
|
||||
|
||||
@ -32,6 +32,7 @@ module.exports = {
|
||||
'@babel/transform-destructuring',
|
||||
'@babel/plugin-transform-runtime',
|
||||
'@babel/plugin-transform-typescript',
|
||||
'@babel/plugin-transform-class-static-block',
|
||||
],
|
||||
},
|
||||
production: {
|
||||
|
||||
@ -573,6 +573,8 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
initialImageIndexToUse = this._getInitialImageIndexForViewport(viewportInfo, imageIds) || 0;
|
||||
}
|
||||
|
||||
const { rotation, flipHorizontal, displayArea } = viewportInfo.getViewportOptions();
|
||||
|
||||
const properties = { ...presentations.lutPresentation?.properties };
|
||||
if (!presentations.lutPresentation?.properties) {
|
||||
const { voi, voiInverted, colormap } = displaySetOptions[0];
|
||||
@ -598,6 +600,15 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
return viewport.setStack(imageIds, initialImageIndexToUse).then(() => {
|
||||
viewport.setProperties({ ...properties });
|
||||
this.setPresentations(viewport.id, presentations);
|
||||
if (displayArea) {
|
||||
viewport.setDisplayArea(displayArea);
|
||||
}
|
||||
if (rotation) {
|
||||
viewport.setProperties({ rotation });
|
||||
}
|
||||
if (flipHorizontal) {
|
||||
viewport.setCamera({ flipHorizontal: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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: {
|
||||
201
extensions/default/src/hangingprotocols/hpMammo.ts
Normal file
201
extensions/default/src/hangingprotocols/hpMammo.ts
Normal file
@ -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;
|
||||
131
extensions/default/src/hangingprotocols/hpScale.ts
Normal file
131
extensions/default/src/hangingprotocols/hpScale.ts
Normal file
@ -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;
|
||||
15
extensions/default/src/hangingprotocols/index.ts
Normal file
15
extensions/default/src/hangingprotocols/index.ts
Normal file
@ -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,
|
||||
};
|
||||
9
extensions/default/src/hangingprotocols/laterality.ts
Normal file
9
extensions/default/src/hangingprotocols/laterality.ts
Normal file
@ -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);
|
||||
}
|
||||
11
extensions/default/src/hangingprotocols/viewCode.ts
Normal file
11
extensions/default/src/hangingprotocols/viewCode.ts
Normal file
@ -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]) => {
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
"i18next": "^17.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^5.50.0",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@
|
||||
"i18next": "^17.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^5.50.0",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@
|
||||
"i18next": "^17.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^5.50.0",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
"dotenv": "^14.1.0",
|
||||
"eslint": "^5.0.1",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"webpack": "^5.50.0",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-merge": "^5.7.3"
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
"i18next": "^17.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^5.50.0",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ const PUBLIC_URL = process.env.PUBLIC_URL || '/';
|
||||
const APP_CONFIG = process.env.APP_CONFIG || 'config/default.js';
|
||||
const PROXY_TARGET = process.env.PROXY_TARGET;
|
||||
const PROXY_DOMAIN = process.env.PROXY_DOMAIN;
|
||||
const OHIF_PORT = Number(process.env.OHIF_PORT || 3000);
|
||||
const ENTRY_TARGET = process.env.ENTRY_TARGET || `${SRC_DIR}/index.js`;
|
||||
const Dotenv = require('dotenv-webpack');
|
||||
const writePluginImportFile = require('./writePluginImportsFile.js');
|
||||
@ -143,7 +144,7 @@ module.exports = (env, argv) => {
|
||||
// http2: true,
|
||||
// https: true,
|
||||
open: true,
|
||||
port: 3000,
|
||||
port: OHIF_PORT,
|
||||
client: {
|
||||
overlay: { errors: true, warnings: false },
|
||||
},
|
||||
|
||||
86
platform/app/cypress/integration/ImageConsistency.spec.js
Normal file
86
platform/app/cypress/integration/ImageConsistency.spec.js
Normal file
@ -0,0 +1,86 @@
|
||||
import { utilities } from '@cornerstonejs/core';
|
||||
|
||||
/**
|
||||
* Add tests to ensure image consistency and quality
|
||||
*/
|
||||
|
||||
const testPixel = (dx, dy, expectedPixel) => {
|
||||
cy.get('.cornerstone-canvas').then(v => {
|
||||
const canvas = v[0];
|
||||
cy.log(
|
||||
'testPixel canvas',
|
||||
dx,
|
||||
dy,
|
||||
expectedPixel,
|
||||
canvas.width,
|
||||
canvas.height,
|
||||
canvas.style.width,
|
||||
canvas.style.height
|
||||
);
|
||||
const ctx = canvas.getContext('2d');
|
||||
cy.window()
|
||||
.its('cornerstone')
|
||||
.then(cornerstone => {
|
||||
const { viewport } = cornerstone.getEnabledElements()[0];
|
||||
const imageData = viewport.getImageData();
|
||||
// cy.log("imageData", imageData);
|
||||
const origin = viewport.worldToCanvas(imageData.origin);
|
||||
const orX = origin[0] * devicePixelRatio;
|
||||
const orY = origin[1] * devicePixelRatio;
|
||||
const x = Math.round(orX + dx);
|
||||
const y = Math.round(orY + dy);
|
||||
cy.log('testPixel origin x,y point x,y', orX, orY, x, y);
|
||||
// cy.log('world origin', imageData.origin);
|
||||
// cy.log('focal', viewport.getCamera().focalPoint,
|
||||
// viewport.worldToCanvas(viewport.getCamera().focalPoint));
|
||||
const pixelData = ctx.getImageData(x, y, 1, 1);
|
||||
|
||||
expect(pixelData.data[0]).closeTo(expectedPixel, 1);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('CS3D Image Consistency and Quality', () => {
|
||||
const setupStudySeries = (studyUID, seriesUID) => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
studyUID,
|
||||
`&seriesInstanceUID=${seriesUID}&hangingProtocolId=@ohif/hpScale`
|
||||
);
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.initCommonElementsAliases();
|
||||
};
|
||||
|
||||
it('TG18 Resolution Test Displayed 1:1', () => {
|
||||
setupStudySeries(
|
||||
'2.16.124.113543.6004.101.103.20021117.061159.1',
|
||||
'2.16.124.113543.6004.101.103.20021117.061159.1.004'
|
||||
);
|
||||
testPixel(1018, 1028, 255);
|
||||
// Horizontal and vertical delta from this should not be contaminated
|
||||
// by values from center
|
||||
testPixel(1019, 1028, 0);
|
||||
testPixel(1018, 1029, 0);
|
||||
testPixel(1017, 1028, 0);
|
||||
testPixel(1018, 1027, 0);
|
||||
});
|
||||
|
||||
// Missing test data - todo
|
||||
it.skip('8 bit image displayable', () => {
|
||||
setupStudySeries('1.3.46.670589.17.1.7.1.1.7', '1.3.46.670589.17.1.7.2.1.7');
|
||||
|
||||
// Compare with dcm2jpg generated values or by manually computing WL values
|
||||
testPixel(258, 257, 171);
|
||||
testPixel(259, 257, 166);
|
||||
});
|
||||
|
||||
it.skip('12 bit image displayable and zoom with pixel spacing', () => {
|
||||
setupStudySeries(
|
||||
'1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1',
|
||||
'1.3.6.1.4.1.25403.345050719074.3824.20170125113608.5'
|
||||
);
|
||||
|
||||
// Compare with dcm2jpg generated values or by manually computing WL values
|
||||
testPixel(258, 277, 120);
|
||||
testPixel(259, 277, 122);
|
||||
});
|
||||
});
|
||||
@ -111,6 +111,34 @@ window.config = {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
|
||||
sourceName: 'dicomweb2',
|
||||
configuration: {
|
||||
friendlyName: 'AWS S3 Static wado secondary server',
|
||||
name: 'aws',
|
||||
wadoUriRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb',
|
||||
qidoRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb',
|
||||
wadoRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb',
|
||||
qidoSupportsIncludeField: false,
|
||||
supportsReject: false,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
enableStudyLazyLoad: true,
|
||||
supportsFuzzyMatching: false,
|
||||
supportsWildcard: true,
|
||||
staticWado: true,
|
||||
singlepart: 'bulkdata,video',
|
||||
// whether the data source should use retrieveBulkData to grab metadata,
|
||||
// and in case of relative path, what would it be relative to, options
|
||||
// are in the series level or study level (some servers like series some study)
|
||||
bulkDataURI: {
|
||||
enabled: true,
|
||||
relativeResolution: 'studies',
|
||||
},
|
||||
omitQuotationForMultipartRequest: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
friendlyName: 'StaticWado default data',
|
||||
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
|
||||
|
||||
@ -24,6 +24,18 @@ export type DisplaySetAndViewportOptions = {
|
||||
displaySetOptions: DisplaySetOptions;
|
||||
};
|
||||
|
||||
export type DisplayArea = {
|
||||
type?: 'SCALE' | 'FIT';
|
||||
scale?: number;
|
||||
interpolationType?: any;
|
||||
imageArea?: [number, number]; // areaX, areaY
|
||||
imageCanvasPoint?: {
|
||||
imagePoint: [number, number]; // imageX, imageY
|
||||
canvasPoint?: [number, number]; // canvasX, canvasY
|
||||
};
|
||||
storeAsInitialCamera?: boolean;
|
||||
};
|
||||
|
||||
export type SetProtocolOptions = {
|
||||
/** Used to provide a mapping of what keys are provided for which viewport.
|
||||
* For example, a Chest XRay might use have the display set selector id of
|
||||
@ -155,6 +167,7 @@ export type ViewportOptions = {
|
||||
id?: string;
|
||||
orientation?: CustomOption<string>;
|
||||
viewportId?: string;
|
||||
displayArea?: DisplayArea;
|
||||
initialImageOptions?: CustomOption<initialImageOptions>;
|
||||
syncGroups?: CustomOption<SyncGroup>[];
|
||||
customViewportProps?: Record<string, unknown>;
|
||||
|
||||
@ -293,6 +293,7 @@ alternative data source (or even specify different default hotkeys).
|
||||
| `APP_CONFIG` | Which [configuration file][config-file] to copy to output as `app-config.js` | `config/default.js` |
|
||||
| `PROXY_TARGET` | When developing, proxy requests that match this pattern to `PROXY_DOMAIN` | `undefined` |
|
||||
| `PROXY_DOMAIN` | When developing, proxy requests from `PROXY_TARGET` to `PROXY_DOMAIN` | `undefined` |
|
||||
| `OHIF_PORT` | The port to run the webpack server on for PWA builds. | `3000` |
|
||||
|
||||
You can also create a new config file and specify its path relative to the build
|
||||
output's root by setting the `APP_CONFIG` environment variable. You can set the
|
||||
|
||||
@ -281,21 +281,22 @@ 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:
|
||||
### `from` attribute (optional)
|
||||
The `from` attribute allows you to retrieve the attribute to test from another object, such as the previous study, the overall list of studies, or another provided value from a module.
|
||||
|
||||
* `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.
|
||||
The values provided by OHIF which you can use are:
|
||||
|
||||
### displaySetSelectors
|
||||
- `activeStudy`: to use the metadata of the active study to match
|
||||
- `studies`: to use the metadata of the list of studies (all studies) to match
|
||||
- `allDisplaySets`: all available display sets
|
||||
- `displaySets`: if the selector has matched a study, these are the display sets for that study
|
||||
- `prior`: the metadata of the first study in the list of studies that is not the active study
|
||||
- `options`: during matching, we also provide an options object with the following information that you can use as the `from` value:
|
||||
- `studyInstanceUIDsIndex`: the index of the study in the list of studies
|
||||
- `instance`: the metadata of the instance being matched, which is exactly the displaySet.instance metadata.
|
||||
|
||||
|
||||
### displaySetSelectors (mandatory)
|
||||
Defines the display sets that the protocol will use for arrangement.
|
||||
|
||||
```js
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
"react-window": "^1.8.9",
|
||||
"react-with-direction": "^1.3.1",
|
||||
"swiper": "^8.4.2",
|
||||
"webpack": "^5.81.0"
|
||||
"webpack": "^5.89.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.2",
|
||||
|
||||
@ -22557,7 +22557,7 @@ webpack-virtual-modules@^0.6.1:
|
||||
resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz#ac6fdb9c5adb8caecd82ec241c9631b7a3681b6f"
|
||||
integrity sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==
|
||||
|
||||
webpack@5, webpack@^5.50.0, webpack@^5.73.0, webpack@^5.81.0, webpack@^5.90.3:
|
||||
webpack@5, webpack@^5.50.0, webpack@^5.73.0, webpack@^5.89.0, webpack@^5.90.3:
|
||||
version "5.91.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.91.0.tgz#ffa92c1c618d18c878f06892bbdc3373c71a01d9"
|
||||
integrity sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==
|
||||
|
||||
Loading…
Reference in New Issue
Block a user