diff --git a/extensions/dicom-html/package.json b/extensions/dicom-html/package.json
index eca2b16a3..a56b3fd5f 100644
--- a/extensions/dicom-html/package.json
+++ b/extensions/dicom-html/package.json
@@ -28,7 +28,7 @@
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
- "dcmjs": "0.14.0",
+ "dcmjs": "0.14.1",
"prop-types": "^15.6.2",
"react": "^16.11.0",
"react-dom": "^16.11.0"
diff --git a/extensions/dicom-rt/package.json b/extensions/dicom-rt/package.json
index 323ea25d8..30cc11a22 100644
--- a/extensions/dicom-rt/package.json
+++ b/extensions/dicom-rt/package.json
@@ -31,7 +31,7 @@
"@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.16.1",
- "dcmjs": "0.14.0",
+ "dcmjs": "0.14.1",
"prop-types": "^15.6.2",
"react": "^16.8.6",
"react-dom": "^16.8.6"
diff --git a/extensions/dicom-segmentation/package.json b/extensions/dicom-segmentation/package.json
index 9306becf7..b09a4d289 100644
--- a/extensions/dicom-segmentation/package.json
+++ b/extensions/dicom-segmentation/package.json
@@ -31,7 +31,7 @@
"@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.16.1",
- "dcmjs": "0.14.0",
+ "dcmjs": "0.14.1",
"prop-types": "^15.6.2",
"react": "^16.8.6",
"react-dom": "^16.8.6"
diff --git a/extensions/dicom-sr/package.json b/extensions/dicom-sr/package.json
index 731b7d39c..99e378bd9 100644
--- a/extensions/dicom-sr/package.json
+++ b/extensions/dicom-sr/package.json
@@ -35,7 +35,7 @@
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.16.1",
"cornerstone-wado-image-loader": "^3.1.2",
- "dcmjs": "0.14.0",
+ "dcmjs": "0.14.1",
"dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
diff --git a/extensions/dicom-sr/src/getSopClassHandlerModule.js b/extensions/dicom-sr/src/getSopClassHandlerModule.js
index 265209160..e0d79765b 100644
--- a/extensions/dicom-sr/src/getSopClassHandlerModule.js
+++ b/extensions/dicom-sr/src/getSopClassHandlerModule.js
@@ -26,12 +26,16 @@ const CodeNameCodeSequenceValues = {
ImageLibraryGroup: '126200',
TrackingUniqueIdentifier: '112040',
TrackingIdentifier: '112039',
+ Finding: '121071',
};
const RELATIONSHIP_TYPE = {
INFERRED_FROM: 'INFERRED FROM',
};
+const CORNERSTONE_CODING_SCHEME_DESIGNATOR = 'CST4';
+const CORNERSTONE_FREETEXT_CODE_VALUE = 'CORNERSTONEFREETEXT';
+
/**
* Basic SOPClassHandler:
* - For all Image types that are stackable, create
@@ -68,8 +72,9 @@ function _getDisplaySetsFromSeries(
const { ConceptNameCodeSequence, ContentSequence } = instance;
if (
+ !ConceptNameCodeSequence ||
ConceptNameCodeSequence.CodeValue !==
- CodeNameCodeSequenceValues.ImagingMeasurementReport
+ CodeNameCodeSequenceValues.ImagingMeasurementReport
) {
console.warn(
'Only support Imaging Measurement Report SRs (TID1500) for now'
@@ -416,6 +421,12 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
CodeNameCodeSequenceValues.TrackingIdentifier
);
+ const Findings = mergedContentSequence.filter(
+ item =>
+ item.ConceptNameCodeSequence.CodeValue ===
+ CodeNameCodeSequenceValues.Finding
+ );
+
const measurement = {
loaded: false,
labels: [],
@@ -424,6 +435,23 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
TrackingIdentifier: TrackingIdentifierContentItem.TextValue,
};
+ if (Findings.length) {
+ // TODO -> Pull in labels when we have them, just free text for now.
+ const cornerstoneFreeTextFinding = Findings.find(
+ Finding =>
+ Finding.ConceptCodeSequence.CodingSchemeDesignator ===
+ CORNERSTONE_CODING_SCHEME_DESIGNATOR &&
+ Finding.ConceptCodeSequence.CodeValue ===
+ CORNERSTONE_FREETEXT_CODE_VALUE
+ );
+ if (cornerstoneFreeTextFinding) {
+ measurement.labels.push({
+ label: CORNERSTONE_FREETEXT_CODE_VALUE,
+ value: cornerstoneFreeTextFinding.ConceptCodeSequence.CodeMeaning,
+ });
+ }
+ }
+
NUMContentItems.forEach(item => {
const {
ConceptNameCodeSequence,
@@ -495,7 +523,16 @@ function _getLabelFromMeasuredValueSequence(
const { NumericValue, MeasurementUnitsCodeSequence } = MeasuredValueSequence;
const { CodeValue } = MeasurementUnitsCodeSequence;
- return { label: CodeMeaning, value: `${NumericValue} ${CodeValue}` }; // E.g. Long Axis: 31.0 mm
+ debugger;
+
+ const formatedNumericValue = NumericValue
+ ? Number(NumericValue).toFixed(2)
+ : '';
+
+ return {
+ label: CodeMeaning,
+ value: `${formatedNumericValue} ${CodeValue}`,
+ }; // E.g. Long Axis: 31.0 mm
}
function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
diff --git a/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js b/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js
index f0dc64b4e..4efca058e 100644
--- a/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js
+++ b/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js
@@ -1,4 +1,5 @@
import { importInternal, getToolState, toolColors } from 'cornerstone-tools';
+import { pixelToCanvas } from 'cornerstone-core';
import TOOL_NAMES from '../constants/toolNames';
import SCOORD_TYPES from '../constants/scoordTypes';
@@ -10,8 +11,10 @@ const drawJoinedLines = importInternal('drawing/drawJoinedLines');
const drawCircle = importInternal('drawing/drawCircle');
const drawEllipse = importInternal('drawing/drawEllipse');
const drawHandles = importInternal('drawing/drawHandles');
+const drawArrow = importInternal('drawing/drawArrow');
const getNewContext = importInternal('drawing/getNewContext');
const BaseTool = importInternal('base/BaseTool');
+const drawLinkedTextBox = importInternal('drawing/drawLinkedTextBox');
/**
* @class DICOMSRDisplayTool - Renders DICOMSR data in a read only manner (i.e. as an overlay).
@@ -59,18 +62,20 @@ export default class DICOMSRDisplayTool extends BaseTool {
trackingUniqueIdentifiers.includes(td.TrackingUniqueIdentifier)
);
+ let shouldRepositionTextBoxes = false;
+
for (let i = 0; i < filteredToolData.length; i++) {
const data = filteredToolData[i];
- const { renderableData } = data;
+ const { renderableData, labels } = data;
const color =
data.TrackingUniqueIdentifier === activeTrackingUniqueIdentifier
? toolColors.getActiveColor()
: toolColors.getToolColor();
-
+ const lineWidth = 2;
const options = {
color,
- lineWidth: 2,
+ lineWidth,
handleRadius: 6,
};
@@ -79,8 +84,10 @@ export default class DICOMSRDisplayTool extends BaseTool {
switch (GraphicType) {
case SCOORD_TYPES.POINT:
+ this.renderPoint(renderableDataForGraphicType, eventData, options);
+ break;
case SCOORD_TYPES.MULTIPOINT:
- this.renderPointOrMultipoint(
+ this.renderMultipoint(
renderableDataForGraphicType,
eventData,
options
@@ -105,9 +112,84 @@ export default class DICOMSRDisplayTool extends BaseTool {
break;
}
});
+
+ const { element } = eventData;
+ const context = getNewContext(eventData.canvasContext.canvas);
+
+ if (!data.handles || !data.handles.textBox) {
+ const textBox = {
+ active: false,
+ hasMoved: true,
+ movesIndependently: false,
+ drawnIndependently: true,
+ allowedOutsideImage: true,
+ hasBoundingBox: true,
+ };
+
+ const anchorPoints = _getTextBoxAnchorPointsForRenderableData(
+ renderableData,
+ eventData
+ );
+ textBox.anchorPoints = anchorPoints;
+
+ const bottomRight = {
+ x: Math.max(...anchorPoints.map(point => point.x)),
+ y: Math.max(...anchorPoints.map(point => point.y)),
+ };
+
+ textBox.x = bottomRight.x;
+ textBox.y = bottomRight.y;
+
+ data.handles = {};
+ data.handles.textBox = textBox;
+
+ shouldRepositionTextBoxes = true;
+ }
+
+ const text = _getTextBoxLinesFromLabels(labels);
+
+ function textBoxAnchorPoints() {
+ return data.handles.textBox.anchorPoints;
+ }
+
+ draw(context, context => {
+ drawLinkedTextBox(
+ context,
+ element,
+ data.handles.textBox,
+ text,
+ data.handles,
+ textBoxAnchorPoints,
+ color,
+ lineWidth,
+ 0,
+ true
+ );
+ });
}
+
+ // TOOD -> text boxes may overlap with other annotations at the moment.
+ // To be fixed after we get requirements.
+ // if (shouldRepositionTextBoxes) {
+ // this.repositionTextBox(filteredToolData, eventData);
+ // }
}
+ // repositionTextBox(toolData, eventData) {
+ // const toolBoundingBoxes = [];
+
+ // for (let i = 0; i < toolData.length; i++) {
+ // const toolDataI = toolData[i];
+
+ // const { textBox } = toolDataI.handles;
+ // const { anchorPoints } = textBox;
+
+ // const boundingBox = _getBoundingBoxFromAnchorPoints(anchorPoints);
+ // // Get the textbox bounding locations.
+ // // Get the tool extents.
+ // }
+ // }
+
renderPolyLine(renderableData, eventData, options) {
const { element } = eventData;
const context = getNewContext(eventData.canvasContext.canvas);
@@ -119,7 +201,7 @@ export default class DICOMSRDisplayTool extends BaseTool {
});
}
- renderPointOrMultipoint(renderableData, eventData, options) {
+ renderMultipoint(renderableData, eventData, options) {
const context = getNewContext(eventData.canvasContext.canvas);
renderableData.forEach(points => {
@@ -129,6 +211,41 @@ export default class DICOMSRDisplayTool extends BaseTool {
});
}
+ renderPoint(renderableData, eventData, options) {
+ // Render single point as an arrow.
+ const { element, image } = eventData;
+ const { rows, columns } = image;
+ const context = getNewContext(eventData.canvasContext.canvas);
+
+ const { color, lineWidth } = options;
+
+ // Find a suitable length for the image size.
+
+ const xOffset = columns / 10;
+ const yOffset = rows / 10;
+
+ renderableData.forEach(points => {
+ const point = points[0]; // The SCOORD type is POINT so the array length is 1.
+ draw(context, context => {
+ // Draw the arrow
+ const handleStartCanvas = pixelToCanvas(element, point);
+ const handleEndCanvas = pixelToCanvas(element, {
+ x: point.x + xOffset,
+ y: point.y + yOffset,
+ });
+
+ drawArrow(
+ context,
+ handleEndCanvas,
+ handleStartCanvas,
+ color,
+ lineWidth,
+ false
+ );
+ });
+ });
+ }
+
renderCircle(renderableData, eventData, options) {
const { element } = eventData;
@@ -161,3 +278,123 @@ export default class DICOMSRDisplayTool extends BaseTool {
});
}
}
+
+function _getTextBoxLinesFromLabels(labels) {
+ // TODO -> max 2 for now, need a generic solution for this!
+
+ const labelLength = Math.min(labels.length, 2);
+
+ const lines = [];
+
+ for (let i = 0; i < labelLength; i++) {
+ const labelEntry = labels[i];
+ lines.push(`${_labelToShorthand(labelEntry.label)}${labelEntry.value}`);
+ }
+
+ return lines;
+}
+
+const SHORT_HAND_MAP = {
+ 'Short Axis': 'S ',
+ 'Long Axis': 'L ',
+ AREA: 'Area ',
+ Length: '',
+ CORNERSTONEFREETEXT: '',
+};
+
+function _labelToShorthand(label) {
+ const shortHand = SHORT_HAND_MAP[label];
+
+ if (shortHand !== undefined) {
+ return shortHand;
+ }
+
+ return label;
+}
+
+function _getTextBoxAnchorPointsForRenderableData(renderableData, eventData) {
+ let anchorPoints = [];
+
+ Object.keys(renderableData).forEach(GraphicType => {
+ const renderableDataForGraphicType = renderableData[GraphicType];
+
+ switch (GraphicType) {
+ case SCOORD_TYPES.POINT:
+ renderableDataForGraphicType.forEach(points => {
+ anchorPoints = [...anchorPoints, ...points];
+
+ // Add other arrow point based on image size.
+ const { image } = eventData;
+ const { rows, columns } = image;
+
+ const xOffset = columns / 10;
+ const yOffset = rows / 10;
+ const point = points[0];
+
+ anchorPoints.push({ x: point.x + xOffset, y: point.y + yOffset });
+ });
+
+ break;
+ case SCOORD_TYPES.MULTIPOINT:
+ case SCOORD_TYPES.POLYLINE:
+ renderableDataForGraphicType.forEach(points => {
+ anchorPoints = [...anchorPoints, ...points];
+ });
+ break;
+ case SCOORD_TYPES.CIRCLE:
+ renderableDataForGraphicType.forEach(circle => {
+ const { center, radius } = circle;
+
+ anchorPoints.push({ x: center.x + radius, y: center.y });
+ anchorPoints.push({ x: center.x - radius, y: center.y });
+ anchorPoints.push({ x: center.x, y: center.y + radius });
+ anchorPoints.push({ x: center.x, y: center.y - radius });
+ });
+
+ break;
+ case SCOORD_TYPES.ELLIPSE:
+ renderableDataForGraphicType.forEach(ellipse => {
+ const { corner1, corner2 } = ellipse;
+
+ const halfWidth = Math.abs(corner1.x - corner2.x) / 2;
+ const halfHeight = Math.abs(corner1.y - corner2.y) / 2;
+
+ const center = {
+ x: (corner1.x + corner2.x) / 2,
+ y: (corner1.y + corner2.y) / 2,
+ };
+
+ anchorPoints.push({ x: center.x + halfWidth, y: center.y });
+ anchorPoints.push({ x: center.x - halfWidth, y: center.y });
+ anchorPoints.push({ x: center.x, y: center.y + halfHeight });
+ anchorPoints.push({ x: center.x, y: center.y - halfHeight });
+ });
+ break;
+ }
+ });
+
+ return anchorPoints;
+}
+
+function _getBoundingBoxFromAnchorPoints(anchorPoints) {
+ let minX = Infinity;
+ let maxX = -Infinity;
+ let minY = Infinity;
+ let maxY = -Infinity;
+
+ anchorPoints.forEach(point => {
+ const { x, y } = point;
+
+ if (x > maxX) {
+ maxX = x;
+ } else if (x < minX) {
+ minX = x;
+ }
+
+ if (y > maxX) {
+ maxY = y;
+ } else if (y < minY) {
+ minY = y;
+ }
+ });
+}
diff --git a/extensions/dicom-sr/src/utils/addMeasurement.js b/extensions/dicom-sr/src/utils/addMeasurement.js
index f3d28c317..b7ea21440 100644
--- a/extensions/dicom-sr/src/utils/addMeasurement.js
+++ b/extensions/dicom-sr/src/utils/addMeasurement.js
@@ -14,11 +14,6 @@ export default function addMeasurement(
imageId,
displaySetInstanceUID
) {
- console.log('== ADD MEASUREMENT TO CST ==');
- console.log(measurement);
- console.log(imageId);
- console.log('============================');
-
// TODO -> Render rotated ellipse .
const toolName = TOOL_NAMES.DICOM_SR_DISPLAY_TOOL;
@@ -26,6 +21,7 @@ export default function addMeasurement(
const measurementData = {
TrackingUniqueIdentifier: measurement.TrackingUniqueIdentifier,
renderableData: {},
+ labels: measurement.labels,
};
measurement.coords.forEach(coord => {
diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json
index 1ed3dbb5b..6b39b6eae 100644
--- a/extensions/measurement-tracking/package.json
+++ b/extensions/measurement-tracking/package.json
@@ -29,7 +29,7 @@
"peerDependencies": {
"@ohif/core": "^0.50.0",
"cornerstone-tools": "4.16.1",
- "dcmjs": "0.14.0",
+ "dcmjs": "0.14.1",
"prop-types": "^15.6.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js
index 71c59eaf3..a69e92835 100644
--- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js
+++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js
@@ -65,6 +65,7 @@ const machineConfiguration = {
cond: 'isNewSeries',
},
],
+
UNTRACK_SERIES: [
{
target: 'tracking',
diff --git a/extensions/vtk/package.json b/extensions/vtk/package.json
index 3e33aa678..d5e309444 100644
--- a/extensions/vtk/package.json
+++ b/extensions/vtk/package.json
@@ -33,7 +33,7 @@
"@ohif/ui": "^2.0.0",
"cornerstone-core": "^2.3.0",
"cornerstone-wado-image-loader": "^3.1.2",
- "dcmjs": "0.14.0",
+ "dcmjs": "0.14.1",
"dicom-parser": "^1.8.3",
"i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1",
diff --git a/platform/core/package.json b/platform/core/package.json
index 3a16b5b95..239876138 100644
--- a/platform/core/package.json
+++ b/platform/core/package.json
@@ -38,7 +38,7 @@
"dependencies": {
"@babel/runtime": "7.7.6",
"ajv": "^6.10.0",
- "dcmjs": "0.14.0",
+ "dcmjs": "0.14.1",
"dicomweb-client": "^0.6.0",
"immer": "6.0.2",
"isomorphic-base64": "^1.0.2",
diff --git a/platform/viewer/package.json b/platform/viewer/package.json
index 629e5e304..c79f11e3a 100644
--- a/platform/viewer/package.json
+++ b/platform/viewer/package.json
@@ -69,7 +69,7 @@
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.16.1",
"cornerstone-wado-image-loader": "^3.1.2",
- "dcmjs": "0.14.0",
+ "dcmjs": "0.14.1",
"dicom-parser": "^1.8.3",
"dicomweb-client": "^0.4.4",
"dotenv-webpack": "^1.7.0",
diff --git a/yarn.lock b/yarn.lock
index ee59610f2..19aeaf498 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2679,6 +2679,17 @@
dependencies:
"@types/node" ">= 8"
+"@ohif/extension-cornerstone@^2.4.0":
+ version "2.8.2"
+ resolved "https://registry.yarnpkg.com/@ohif/extension-cornerstone/-/extension-cornerstone-2.8.2.tgz#b8d95610b91eb43c05f84adcdd699183918a57ff"
+ integrity sha512-fIkPPQdDCkoVHCYTUFB4UhmdL9VJRyYawa6FxXiKXjj9OcUsrkXj+BORYgzcEkLWTLmrB+66qtIqpQ7FyXlzJQ==
+ dependencies:
+ "@babel/runtime" "^7.5.5"
+ classnames "^2.2.6"
+ lodash.merge "^4.6.2"
+ lodash.throttle "^4.1.1"
+ react-cornerstone-viewport "2.3.9"
+
"@ohif/extension-lesion-tracker@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@ohif/extension-lesion-tracker/-/extension-lesion-tracker-0.2.0.tgz#37fda345204041539051750fad17551846fba40f"
@@ -7353,10 +7364,10 @@ dateformat@^3.0.0:
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
-dcmjs@0.14.0:
- version "0.14.0"
- resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.14.0.tgz#0dc6cb2d15ddcff759bc9002f2a9704537735d1c"
- integrity sha512-VL/Ibxe5RDsc5j5SEv3aEqdlKuBXz81/bBuW59Or0cos9vgK3XnVl3rr0ct6DWXJGK8vGIUMBOguyd/NRvlN0w==
+dcmjs@0.14.1:
+ version "0.14.1"
+ resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.14.1.tgz#4a4f18d1e5332d38429e2d77340a6ad0052ec71e"
+ integrity sha512-GGGQt4zjX7Fp0rLO5rcIQgmxZdLsEwBSZ1T74iXeJG5W32P1yiodJfBW900tNZCKj3zWamO3f5jDC1Xkv81fdg==
dependencies:
"@babel/polyfill" "^7.8.3"
"@babel/runtime" "^7.8.4"
@@ -17889,6 +17900,16 @@ react-cornerstone-viewport@2.3.8:
prop-types "^15.7.2"
react-resize-detector "^4.2.1"
+react-cornerstone-viewport@2.3.9:
+ version "2.3.9"
+ resolved "https://registry.yarnpkg.com/react-cornerstone-viewport/-/react-cornerstone-viewport-2.3.9.tgz#f9761da8e536f0a217137c6ca1a983f5882249f9"
+ integrity sha512-qrhq8CbX/jq6b93cQjV2qC/mhHOvFBpxzxcHlvHzEQZt/rmRMcaYxCqjpNaNbUmmBu61wNkxesUVsggkPTTcqg==
+ dependencies:
+ classnames "^2.2.6"
+ date-fns "^2.2.1"
+ prop-types "^15.7.2"
+ react-resize-detector "^4.2.1"
+
react-dates@21.2.1:
version "21.2.1"
resolved "https://registry.yarnpkg.com/react-dates/-/react-dates-21.2.1.tgz#a979ed6876326ccfbf754a019bc95458cc061ad8"