Generate render tool.
This commit is contained in:
parent
7bd041a02c
commit
f4470c9625
7
extensions/dicom-sr/src/constants/scoordTypes.js
Normal file
7
extensions/dicom-sr/src/constants/scoordTypes.js
Normal file
@ -0,0 +1,7 @@
|
||||
export default {
|
||||
POINT: 'POINT',
|
||||
MULTIPOINT: 'MULTIPOINT',
|
||||
POLYLINE: 'POLYLINE',
|
||||
CIRCLE: 'CIRCLE',
|
||||
ELLIPSE: 'ELLIPSE',
|
||||
};
|
||||
5
extensions/dicom-sr/src/constants/toolNames.js
Normal file
5
extensions/dicom-sr/src/constants/toolNames.js
Normal file
@ -0,0 +1,5 @@
|
||||
const TOOL_NAMES = {
|
||||
DICOM_SR_DISPLAY_TOOL: 'DICOMSRDisplayTool',
|
||||
};
|
||||
|
||||
export default TOOL_NAMES;
|
||||
@ -1,5 +1,8 @@
|
||||
import id from './id';
|
||||
import { utils } from '@ohif/core';
|
||||
import { utils, classes } from '@ohif/core';
|
||||
import addMeasurement from './utils/addMeasurement.js';
|
||||
|
||||
const { ImageSet } = classes;
|
||||
|
||||
const sopClassHandlerName = 'dicom-sr';
|
||||
|
||||
@ -9,8 +12,6 @@ const sopClassUids = [
|
||||
'1.2.840.10008.5.1.4.1.1.88.33', //COMPREHENSIVE_SR:
|
||||
];
|
||||
|
||||
const scoordTypes = ['POINT', 'MULTIPOINT', 'POLYLINE', 'CIRCLE', 'ELLIPSE'];
|
||||
|
||||
const CodeNameCodeSequenceValues = {
|
||||
ImagingMeasurementReport: '126000',
|
||||
ImageLibrary: '111028',
|
||||
@ -20,6 +21,10 @@ const CodeNameCodeSequenceValues = {
|
||||
TrackingUniqueIdentifier: '112040',
|
||||
};
|
||||
|
||||
const RELATIONSHIP_TYPE = {
|
||||
INFERRED_FROM: 'INFERRED FROM',
|
||||
};
|
||||
|
||||
/**
|
||||
* Basic SOPClassHandler:
|
||||
* - For all Image types that are stackable, create
|
||||
@ -29,16 +34,23 @@ const CodeNameCodeSequenceValues = {
|
||||
* @param {SeriesMetadata} series The series metadata object from which the display sets will be created
|
||||
* @returns {Array} The list of display sets created for the given series object
|
||||
*/
|
||||
function getDisplaySetsFromSeries(instances) {
|
||||
function _getDisplaySetsFromSeries(
|
||||
instances,
|
||||
servicesManager,
|
||||
extensionManager
|
||||
) {
|
||||
// If the series has no instances, stop here
|
||||
if (!instances || !instances.length) {
|
||||
throw new Error('No instances were provided');
|
||||
}
|
||||
|
||||
const { DisplaySetService } = servicesManager.services;
|
||||
const dataSources = extensionManager.getDataSources();
|
||||
const dataSource = dataSources[0];
|
||||
|
||||
const instance = instances[0];
|
||||
|
||||
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = instance;
|
||||
|
||||
const { ConceptNameCodeSequence, ContentSequence } = instance;
|
||||
|
||||
if (
|
||||
@ -51,10 +63,6 @@ function getDisplaySetsFromSeries(instances) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const referencedImages = _getReferencedImagesList(ContentSequence);
|
||||
|
||||
const measurements = _getMeasurements(ContentSequence, SOPInstanceUID);
|
||||
|
||||
const displaySet = {
|
||||
plugin: id,
|
||||
Modality: 'SR',
|
||||
@ -63,13 +71,141 @@ function getDisplaySetsFromSeries(instances) {
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
SOPClassHandlerId: `${id}.sopClassHandlerModule.${sopClassHandlerName}`,
|
||||
referencedImages,
|
||||
referencedImages: _getReferencedImagesList(ContentSequence),
|
||||
measurements: _getMeasurements(ContentSequence),
|
||||
};
|
||||
|
||||
console.log(DisplaySetService);
|
||||
|
||||
// Check currently added displaySets and add measurements if the sources exist.
|
||||
DisplaySetService.activeDisplaySets.forEach(activeDisplaySet => {
|
||||
_checkIfCanAddMeasurementsToDisplaySet(
|
||||
displaySet,
|
||||
activeDisplaySet,
|
||||
dataSource
|
||||
);
|
||||
});
|
||||
|
||||
// Subscribe to new displaySets as the source may come in after.
|
||||
DisplaySetService.subscribe(
|
||||
DisplaySetService.EVENTS.DISPLAY_SETS_ADDED,
|
||||
newDisplaySets => {
|
||||
// If there are still some measurements that have not yet been loaded into cornerstone,
|
||||
// See if we can load them onto any of the new displaySets.
|
||||
newDisplaySets.forEach(newDisplaySet => {
|
||||
_checkIfCanAddMeasurementsToDisplaySet(
|
||||
displaySet,
|
||||
newDisplaySet,
|
||||
dataSource
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return [displaySet];
|
||||
}
|
||||
|
||||
function getSopClassHandlerModule() {
|
||||
function _checkIfCanAddMeasurementsToDisplaySet(
|
||||
srDisplaySet,
|
||||
newDisplaySet,
|
||||
dataSource
|
||||
) {
|
||||
let unloadedMeasurements = srDisplaySet.measurements.filter(
|
||||
measurement => measurement.loaded === false
|
||||
);
|
||||
|
||||
if (unloadedMeasurements.length === 0) {
|
||||
// All already loaded!
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newDisplaySet instanceof ImageSet) {
|
||||
// This also filters out _this_ displaySet, as it is not an ImageSet.
|
||||
return;
|
||||
}
|
||||
|
||||
const { sopClassUids, images } = newDisplaySet;
|
||||
|
||||
// Check if any have the newDisplaySet is the correct SOPClass.
|
||||
unloadedMeasurements = unloadedMeasurements.filter(measurement =>
|
||||
measurement.coords.some(coord =>
|
||||
sopClassUids.includes(coord.ReferencedSOPSequence.ReferencedSOPClassUID)
|
||||
)
|
||||
);
|
||||
|
||||
if (unloadedMeasurements.length === 0) {
|
||||
// New displaySet isn't the correct SOPClass, so can't contain the referenced images.
|
||||
return;
|
||||
}
|
||||
|
||||
const SOPInstanceUIDs = [];
|
||||
|
||||
unloadedMeasurements.forEach(measurement => {
|
||||
const { coords } = measurement;
|
||||
|
||||
coords.forEach(coord => {
|
||||
const SOPInstanceUID =
|
||||
coord.ReferencedSOPSequence.ReferencedSOPInstanceUID;
|
||||
|
||||
if (!SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
||||
SOPInstanceUIDs.push(SOPInstanceUID);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const imageIdsForDisplaySet = dataSource.getImageIdsForDisplaySet(
|
||||
newDisplaySet
|
||||
);
|
||||
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
if (!unloadedMeasurements.length) {
|
||||
// All measurements loaded.
|
||||
break;
|
||||
}
|
||||
|
||||
const image = images[i];
|
||||
const { SOPInstanceUID } = image;
|
||||
if (SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
||||
const imageId = imageIdsForDisplaySet[i];
|
||||
|
||||
for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
|
||||
const measurement = unloadedMeasurements[j];
|
||||
if (_measurmentReferencesSOPInstanceUID(measurement, SOPInstanceUID)) {
|
||||
addMeasurement(
|
||||
measurement,
|
||||
imageId,
|
||||
newDisplaySet.displaySetInstanceUID
|
||||
);
|
||||
|
||||
unloadedMeasurements.splice(j, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _measurmentReferencesSOPInstanceUID(measurement, SOPInstanceUID) {
|
||||
const { coords } = measurement;
|
||||
|
||||
for (let j = 0; j < coords.length; j++) {
|
||||
const coord = coords[j];
|
||||
const { ReferencedSOPInstanceUID } = coord.ReferencedSOPSequence;
|
||||
|
||||
if (ReferencedSOPInstanceUID === SOPInstanceUID) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||
const getDisplaySetsFromSeries = instances => {
|
||||
return _getDisplaySetsFromSeries(
|
||||
instances,
|
||||
servicesManager,
|
||||
extensionManager
|
||||
);
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
name: sopClassHandlerName,
|
||||
@ -79,10 +215,7 @@ function getSopClassHandlerModule() {
|
||||
];
|
||||
}
|
||||
|
||||
function _getMeasurements(
|
||||
ImagingMeasurementReportContentSequence,
|
||||
SOPInstanceUID
|
||||
) {
|
||||
function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
||||
const ImagingMeasurements = ImagingMeasurementReportContentSequence.find(
|
||||
item =>
|
||||
item.ConceptNameCodeSequence.CodeValue ===
|
||||
@ -101,7 +234,7 @@ function _getMeasurements(
|
||||
MeasurementGroups
|
||||
);
|
||||
|
||||
let allMeasurements = [];
|
||||
let measurements = [];
|
||||
|
||||
Object.keys(mergedContentSequencesByTrackingUniqueIdentifiers).forEach(
|
||||
trackingUniqueIdentifier => {
|
||||
@ -110,13 +243,15 @@ function _getMeasurements(
|
||||
trackingUniqueIdentifier
|
||||
];
|
||||
|
||||
const measurements = _processMeasurement(mergedContentSequence);
|
||||
const measurement = _processMeasurement(mergedContentSequence);
|
||||
|
||||
if (measurements) {
|
||||
allMeasurements = allMeasurements.concat(measurements);
|
||||
if (measurement) {
|
||||
measurements.push(measurement);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return measurements;
|
||||
}
|
||||
|
||||
function _getMergedContentSequencesByTrackingUniqueIdentifiers(
|
||||
@ -172,8 +307,6 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(
|
||||
}
|
||||
|
||||
function _processMeasurement(mergedContentSequence) {
|
||||
debugger;
|
||||
|
||||
if (
|
||||
mergedContentSequence.some(
|
||||
group => group.ValueType === 'SCOORD' || group.ValueType === 'SCOORD3D'
|
||||
@ -182,34 +315,134 @@ function _processMeasurement(mergedContentSequence) {
|
||||
return _processTID1410Measurement(mergedContentSequence);
|
||||
}
|
||||
|
||||
return _processNonGeometricallyDefinedMeasurement(mergedContentSequence);
|
||||
}
|
||||
|
||||
function _processTID1410Measurement(mergedContentSequence) {
|
||||
// Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level,
|
||||
// And non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D
|
||||
// TODO -> Look at RelationshipType => Contains means
|
||||
|
||||
const graphicItem = mergedContentSequence.find(
|
||||
group => group.ValueType === 'SCOORD'
|
||||
);
|
||||
|
||||
if (!graphicItem) {
|
||||
console.warn(
|
||||
`graphic ValueType ${graphicItem.ValueType} not currently supported, skipping annotation.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const NUMContentItems = mergedContentSequence.filter(
|
||||
group => group.ValueType === 'NUM'
|
||||
);
|
||||
|
||||
const measurements = [];
|
||||
const measurement = {
|
||||
loaded: false,
|
||||
labels: [],
|
||||
coords: [_getCoordsFromSCOORDOrSCOORD3D(graphicItem)],
|
||||
};
|
||||
|
||||
NUMContentItems.forEach(item => {
|
||||
debugger;
|
||||
const { ConceptNameCodeSequence, MeasuredValueSequence } = item;
|
||||
|
||||
const { ContentSequence, MeasuredValueSequence } = item;
|
||||
|
||||
// GET -> REPRESENTATION, REFERNECED IMAGE, measurement value
|
||||
if (MeasuredValueSequence) {
|
||||
measurement.labels.push(
|
||||
_getLabelFromMeasuredValueSequence(
|
||||
ConceptNameCodeSequence,
|
||||
MeasuredValueSequence
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level,
|
||||
// And non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D
|
||||
|
||||
// TODO -> Look at RelationshipType => Contains means
|
||||
|
||||
return [];
|
||||
return measurement;
|
||||
}
|
||||
|
||||
function _processTID1410Measurement(mergedContentSequence) {
|
||||
// TODO
|
||||
console.error('TODO => Process TID1410Measurement');
|
||||
debugger;
|
||||
function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
const NUMContentItems = mergedContentSequence.filter(
|
||||
group => group.ValueType === 'NUM'
|
||||
);
|
||||
|
||||
return [ß];
|
||||
const measurement = {
|
||||
loaded: false,
|
||||
labels: [],
|
||||
coords: [],
|
||||
};
|
||||
|
||||
NUMContentItems.forEach(item => {
|
||||
const {
|
||||
ConceptNameCodeSequence,
|
||||
ContentSequence,
|
||||
MeasuredValueSequence,
|
||||
} = item;
|
||||
|
||||
const { ValueType } = ContentSequence;
|
||||
|
||||
if (!ValueType === 'SCOORD') {
|
||||
console.warn(
|
||||
`Graphic ${ValueType} not currently supported, skipping annotation.`
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const coords = _getCoordsFromSCOORDOrSCOORD3D(ContentSequence);
|
||||
|
||||
if (coords) {
|
||||
measurement.coords.push(coords);
|
||||
}
|
||||
|
||||
if (MeasuredValueSequence) {
|
||||
measurement.labels.push(
|
||||
_getLabelFromMeasuredValueSequence(
|
||||
ConceptNameCodeSequence,
|
||||
MeasuredValueSequence
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return measurement;
|
||||
}
|
||||
|
||||
function _getCoordsFromSCOORDOrSCOORD3D(item) {
|
||||
const { ValueType, RelationshipType, GraphicType, GraphicData } = item;
|
||||
|
||||
if (RelationshipType !== RELATIONSHIP_TYPE.INFERRED_FROM) {
|
||||
console.warn(
|
||||
`Relationshiptype === ${RelationshipType}. Cannot deal with NON TID-1400 SCOORD group with RelationshipType !== "INFERRED FROM."`
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const coords = { ValueType, GraphicType, GraphicData };
|
||||
|
||||
// ContentSequence has length of 1 as RelationshipType === 'INFERRED FROM'
|
||||
if (ValueType === 'SCOORD') {
|
||||
const { ReferencedSOPSequence } = item.ContentSequence;
|
||||
|
||||
coords.ReferencedSOPSequence = ReferencedSOPSequence;
|
||||
} else if (ValueType === 'SCOORD3D') {
|
||||
const { ReferencedFrameOfReferenceSequence } = item.ContentSequence;
|
||||
|
||||
coords.ReferencedFrameOfReferenceSequence = ReferencedFrameOfReferenceSequence;
|
||||
}
|
||||
|
||||
return coords;
|
||||
}
|
||||
|
||||
function _getLabelFromMeasuredValueSequence(
|
||||
ConceptNameCodeSequence,
|
||||
MeasuredValueSequence
|
||||
) {
|
||||
const { CodeMeaning } = ConceptNameCodeSequence;
|
||||
const { NumericValue, MeasurementUnitsCodeSequence } = MeasuredValueSequence;
|
||||
const { CodeValue } = MeasurementUnitsCodeSequence;
|
||||
|
||||
return { label: CodeMeaning, value: `${NumericValue} ${CodeValue}` }; // E.g. Long Axis: 31.0 mm
|
||||
}
|
||||
|
||||
function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
||||
import id from './id.js';
|
||||
import init from './init';
|
||||
|
||||
const Component = React.lazy(() => {
|
||||
return import('./OHIFCornerstoneSRViewport');
|
||||
@ -23,6 +24,10 @@ export default {
|
||||
*/
|
||||
id,
|
||||
|
||||
preRegistration({ servicesManager, configuration = {} }) {
|
||||
init({ servicesManager, configuration });
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
|
||||
21
extensions/dicom-sr/src/init.js
Normal file
21
extensions/dicom-sr/src/init.js
Normal file
@ -0,0 +1,21 @@
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool';
|
||||
|
||||
import TOOL_NAMES from './constants/toolNames';
|
||||
|
||||
const defaultConfig = {
|
||||
TOOL_NAMES: {
|
||||
DICOM_SR_DISPLAY_TOOL: 'DICOMSRDisplayTool',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {object} configuration
|
||||
*/
|
||||
export default function init({ configuration = {} }) {
|
||||
const conifg = Object.assign({}, defaultConfig, configuration);
|
||||
|
||||
TOOL_NAMES.DICOM_SR_DISPLAY_TOOL = conifg.TOOL_NAMES.DICOM_SR_DISPLAY_TOOL;
|
||||
|
||||
cornerstoneTools.addTool(DICOMSRDisplayTool);
|
||||
}
|
||||
123
extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js
Normal file
123
extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js
Normal file
@ -0,0 +1,123 @@
|
||||
import { importInternal, getToolState, toolColors } from 'cornerstone-tools';
|
||||
|
||||
import TOOL_NAMES from '../constants/toolNames';
|
||||
import SCOORD_TYPES from '../constants/scoordTypes';
|
||||
|
||||
// Cornerstone 3rd party dev kit imports
|
||||
const draw = importInternal('drawing/draw');
|
||||
const drawJoinedLines = importInternal('drawing/drawJoinedLines');
|
||||
const drawCircle = importInternal('drawing/drawCircle');
|
||||
const drawEllipse = importInternal('drawing/drawEllipse');
|
||||
const getNewContext = importInternal('drawing/getNewContext');
|
||||
const BaseTool = importInternal('base/BaseTool');
|
||||
|
||||
/**
|
||||
* @class DICOMSRDisplayTool - Renders DICOMSR data in a read only manner (i.e. as an overlay).
|
||||
* @extends cornerstoneTools.BaseTool
|
||||
*/
|
||||
export default class DICOMSRDisplayTool extends BaseTool {
|
||||
constructor(props = {}) {
|
||||
const defaultProps = {
|
||||
mixins: ['enabledOrDisabledBinaryTool'],
|
||||
name: TOOL_NAMES.DICOM_SR_DISPLAY_TOOL,
|
||||
};
|
||||
|
||||
const initialProps = Object.assign(defaultProps, props);
|
||||
|
||||
super(initialProps);
|
||||
}
|
||||
|
||||
renderToolData(evt) {
|
||||
const eventData = evt.detail;
|
||||
const { element } = eventData;
|
||||
|
||||
const toolState = getToolState(element, this.name);
|
||||
|
||||
if (!toolState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {
|
||||
color: toolColors.setToolColor(),
|
||||
lineWidth: 2,
|
||||
handleRadius: 6,
|
||||
};
|
||||
|
||||
for (let i = 0; i < toolState.data.length; i++) {
|
||||
const data = toolState.data[i];
|
||||
|
||||
Object.keys(data).forEach(GraphicType => {
|
||||
const renderableData = data[GraphicType];
|
||||
|
||||
switch (GraphicType) {
|
||||
case SCOORD_TYPES.POINT:
|
||||
case SCOORD_TYPES.MULTIPOINT:
|
||||
renderPointOrMultipoint(renderableData, eventData, options);
|
||||
break;
|
||||
case SCOORD_TYPES.POLYLINE:
|
||||
renderPolyLine(renderableData, eventData, options);
|
||||
break;
|
||||
case SCOORD_TYPES.CIRCLE:
|
||||
renderCircle(renderableData, eventData, options);
|
||||
break;
|
||||
case SCOORD_TYPES.ELLIPSE:
|
||||
renderEllipse(renderableData, eventData, options);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderPolyLine(renderableData, eventData, options) {
|
||||
const { element } = eventData;
|
||||
const context = getNewContext(eventData.canvasContext.canvas);
|
||||
|
||||
renderableData.forEach(points => {
|
||||
draw(context, context => {
|
||||
drawJoinedLines(context, element, points[0], points, options);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
renderPointOrMultipoint(renderableData, eventData, options) {
|
||||
const context = getNewContext(eventData.canvasContext.canvas);
|
||||
|
||||
renderableData.forEach(points => {
|
||||
draw(context, context => {
|
||||
drawHandles(context, eventData, points, options);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
renderCircle(renderableData, eventData, options) {
|
||||
const { element } = eventData;
|
||||
|
||||
const context = getNewContext(eventData.canvasContext.canvas);
|
||||
|
||||
renderableData.forEach(circle => {
|
||||
const { center, radius } = circle;
|
||||
|
||||
drawCircle(context, element, center, radius, options);
|
||||
});
|
||||
}
|
||||
|
||||
renderEllipse(renderableData, eventData, options) {
|
||||
const { element } = eventData;
|
||||
|
||||
const context = getNewContext(eventData.canvasContext.canvas);
|
||||
|
||||
renderableData.forEach(ellipse => {
|
||||
const { corner1, corner2 } = ellipse;
|
||||
|
||||
drawEllipse(
|
||||
context,
|
||||
element,
|
||||
corner1,
|
||||
corner2,
|
||||
options,
|
||||
'pixel',
|
||||
0 // TODO -> Work our the initial rotation and add it here so we render appropriately rotated ellipses.
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
135
extensions/dicom-sr/src/utils/addMeasurement.js
Normal file
135
extensions/dicom-sr/src/utils/addMeasurement.js
Normal file
@ -0,0 +1,135 @@
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import TOOL_NAMES from '../constants/toolNames';
|
||||
import SCOORD_TYPES from '../constants/scoordTypes';
|
||||
import { cornerstone } from '../../../../platform/core/src';
|
||||
|
||||
const globalImageIdSpecificToolStateManager =
|
||||
cornerstoneTools.globalImageIdSpecificToolStateManager;
|
||||
|
||||
export default function addMeasurement(
|
||||
measurement,
|
||||
imageId,
|
||||
displaySetInstanceUID
|
||||
) {
|
||||
console.log('== ADD MEASUREMENT TO CST ==');
|
||||
console.log(measurement);
|
||||
console.log(imageId);
|
||||
console.log('============================');
|
||||
|
||||
// TODO -> GET TRACKING UID AND ADD IT TO TOOL DATA AND ALSO THE MEASUREMENT ENTRY.
|
||||
// TODO -> Render rotated ellipse .
|
||||
|
||||
debugger;
|
||||
|
||||
const toolName = TOOL_NAMES.DICOM_SR_DISPLAY_TOOL;
|
||||
|
||||
const measurementData = {};
|
||||
|
||||
measurement.coords.forEach(coord => {
|
||||
const { GraphicType, GraphicData } = coord;
|
||||
|
||||
if (measurementData[GraphicType] === undefined) {
|
||||
measurementData[GraphicType] = [];
|
||||
}
|
||||
|
||||
const renderableData = _getRenderableData(GraphicType, GraphicData);
|
||||
|
||||
measurementData[GraphicType].push(renderableData);
|
||||
});
|
||||
|
||||
const toolState = globalImageIdSpecificToolStateManager.saveToolState();
|
||||
|
||||
if (toolState[imageId] === undefined) {
|
||||
toolState[imageId] = {};
|
||||
}
|
||||
|
||||
const imageIdToolState = toolState[imageId];
|
||||
|
||||
// If we don't have tool state for this type of tool, add an empty object
|
||||
if (imageIdToolState[toolName] === undefined) {
|
||||
imageIdToolState[toolName] = {
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
|
||||
const toolData = imageIdToolState[toolName];
|
||||
|
||||
toolData.data.push(measurementData);
|
||||
|
||||
measurement.loaded = true;
|
||||
measurement.imageId = imageId;
|
||||
measurement.displaySetInstanceUID = displaySetInstanceUID;
|
||||
debugger;
|
||||
}
|
||||
|
||||
function _getRenderableData(GraphicType, GraphicData) {
|
||||
let renderableData;
|
||||
|
||||
switch (GraphicType) {
|
||||
case SCOORD_TYPES.POINT:
|
||||
case SCOORD_TYPES.MULTIPOINT:
|
||||
case SCOORD_TYPES.POLYLINE:
|
||||
renderableData = [];
|
||||
|
||||
for (let i = 0; i < GraphicData.length; i += 2) {
|
||||
renderableData.push({ x: GraphicData[i], y: GraphicData[i + 1] });
|
||||
}
|
||||
break;
|
||||
case SCOORD_TYPES.CIRCLE:
|
||||
const center = { x: GraphicData[0], y: GraphicData[1] };
|
||||
const onPerimeter = { x: GraphicData[2], y: GraphicData[3] };
|
||||
|
||||
const radius = cornerstoneMath.point.distance(center, onPerimeter);
|
||||
|
||||
renderableData = {
|
||||
center,
|
||||
radius,
|
||||
};
|
||||
break;
|
||||
case SCOORD_TYPES.ELLIPSE:
|
||||
console.warn('ROTATED ELLIPSE NOT YET SUPPORTED!');
|
||||
|
||||
const majorAxis = [
|
||||
{ x: GraphicData[0], y: GraphicData[1] },
|
||||
{ x: GraphicData[2], y: GraphicData[3] },
|
||||
];
|
||||
const minorAxis = [
|
||||
{ x: GraphicData[4], y: GraphicData[5] },
|
||||
{ x: GraphicData[6], y: GraphicData[7] },
|
||||
];
|
||||
|
||||
// Calculate two opposite corners of box defined by two axes.
|
||||
|
||||
const minorAxisLength = cornerstoneMath.point.distance(
|
||||
minorAxis[0],
|
||||
minorAxis[1]
|
||||
);
|
||||
|
||||
const minorAxisDirection = {
|
||||
x: (minorAxis[1].x - minorAxis[0].x) / minorAxisLength,
|
||||
y: (minorAxis[1].y - minorAxis[0].y) / minorAxisLength,
|
||||
};
|
||||
|
||||
const halfMinorAxisLength = minorAxisLength / 2;
|
||||
|
||||
// First end point of major axis + half minor axis vector
|
||||
const corner1 = {
|
||||
x: majorAxis[0].x + minorAxisDirection.x * halfMinorAxisLength,
|
||||
y: majorAxis[0].y + minorAxisDirection.y * halfMinorAxisLength,
|
||||
};
|
||||
|
||||
// Second end point of major axis - half of minor axis vector
|
||||
const corner2 = {
|
||||
x: majorAxis[1].x - minorAxisDirection.x * halfMinorAxisLength,
|
||||
y: majorAxis[1].y - minorAxisDirection.y * halfMinorAxisLength,
|
||||
};
|
||||
|
||||
renderableData = {
|
||||
corner1,
|
||||
corner2,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
return renderableData;
|
||||
}
|
||||
@ -17,9 +17,12 @@ export default class ExtensionManager {
|
||||
this.modules[moduleType] = [];
|
||||
});
|
||||
this.dataSourceMap = {};
|
||||
this.defaultDataSourceName = appConfig.defaultDataSourceName;
|
||||
this.activeDataSource = undefined;
|
||||
}
|
||||
|
||||
console.log('modules map');
|
||||
console.log(this.modulesMap);
|
||||
setActiveDataSource(dataSourceName) {
|
||||
this.activeDataSource = dataSourceName;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -137,6 +140,10 @@ export default class ExtensionManager {
|
||||
};
|
||||
|
||||
getDataSources = dataSourceName => {
|
||||
if (dataSourceName === undefined) {
|
||||
dataSourceName = this.activeDataSource;
|
||||
}
|
||||
|
||||
// Note: this currently uses the data source name, which feels weird...
|
||||
return this.dataSourceMap[dataSourceName];
|
||||
};
|
||||
|
||||
@ -23,4 +23,5 @@ window.config = {
|
||||
},
|
||||
},
|
||||
],
|
||||
defaultDataSourceName: 'dicomweb',
|
||||
};
|
||||
|
||||
@ -13,6 +13,8 @@ import {
|
||||
// redux as reduxOHIF,
|
||||
} from '@ohif/core';
|
||||
|
||||
// TODO -> this feels bad.
|
||||
|
||||
/**
|
||||
* @param {object|func} appConfigOrFunc - application configuration, or a function that returns application configuration
|
||||
* @param {object[]} defaultExtensions - array of extension objects
|
||||
|
||||
@ -27,12 +27,15 @@ export default function ModeRoute({
|
||||
: [StudyInstanceUIDs];
|
||||
|
||||
const { extensions, sopClassHandlers } = mode;
|
||||
// TODO:
|
||||
// - Check query/params for specific dataSource
|
||||
// - If provided, query for that dataSource instance
|
||||
// - If not provided, select default datasource
|
||||
// - Update `extensionManager` to have a method to retrieve the default source
|
||||
const dataSources = extensionManager.getDataSources(dataSourceName);
|
||||
|
||||
if (dataSourceName === undefined) {
|
||||
dataSourceName = extensionManager.defaultDataSourceName;
|
||||
}
|
||||
|
||||
extensionManager.setActiveDataSource(dataSourceName);
|
||||
|
||||
const dataSources = extensionManager.getDataSources();
|
||||
|
||||
const dataSource = dataSources[0];
|
||||
const route = mode.routes[0];
|
||||
|
||||
|
||||
@ -68,6 +68,30 @@ export default function buildModeRoutes(
|
||||
exact: true,
|
||||
});
|
||||
});
|
||||
|
||||
const defaultDataSourceName = extensionManager.defaultDataSourceName;
|
||||
|
||||
// Add default DataSource route.
|
||||
const path = `/${mode.id}`;
|
||||
|
||||
// TODO move up.
|
||||
const component = ({ location }) => (
|
||||
<ViewModelProvider>
|
||||
<ModeRoute
|
||||
location={location}
|
||||
mode={mode}
|
||||
dataSourceName={defaultDataSourceName}
|
||||
extensionManager={extensionManager}
|
||||
servicesManager={servicesManager}
|
||||
/>
|
||||
</ViewModelProvider>
|
||||
);
|
||||
|
||||
routes.push({
|
||||
path,
|
||||
component,
|
||||
exact: true,
|
||||
});
|
||||
});
|
||||
|
||||
return routes;
|
||||
|
||||
@ -29,8 +29,7 @@ const createRoutes = (
|
||||
const routes =
|
||||
buildModeRoutes(modes, dataSources, extensionManager, servicesManager) ||
|
||||
[];
|
||||
// TODO: Shouldn't need to guard input routes with an empty array?
|
||||
const allRoutes = [...(routes || []), ...bakedInRoutes];
|
||||
const allRoutes = [...routes, ...bakedInRoutes];
|
||||
|
||||
console.log(
|
||||
'Creating Routes: ',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user