Render text properly, render arrow + freetext. Need to move textboxes appropriately.
This commit is contained in:
parent
d956c3008c
commit
e3f2e26e3b
@ -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'
|
||||
@ -134,49 +139,6 @@ function _getDisplaySetsFromSeries(
|
||||
return [displaySet];
|
||||
}
|
||||
|
||||
function _isRehydratable(displaySet, MeasurementService) {
|
||||
const mappings = MeasurementService.getSourceMappings(
|
||||
'CornerstoneTools',
|
||||
'4'
|
||||
);
|
||||
|
||||
if (!mappings || !mappings.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const mappingDefinitions = mappings.map(m => m.definition);
|
||||
const { measurements } = displaySet;
|
||||
|
||||
const adapterKeys = Object.keys(cornerstoneAdapters).filter(
|
||||
adapterKey =>
|
||||
typeof cornerstoneAdapters[adapterKey]
|
||||
.isValidCornerstoneTrackingIdentifier === 'function'
|
||||
);
|
||||
|
||||
const adapters = [];
|
||||
|
||||
adapterKeys.forEach(key => {
|
||||
if (mappingDefinitions.includes(key)) {
|
||||
// Must have both a dcmjs adapter and a MeasurementService
|
||||
// Definition in order to be a candidate for import.
|
||||
adapters.push(cornerstoneAdapters[key]);
|
||||
}
|
||||
});
|
||||
|
||||
for (let i = 0; i < measurements.length; i++) {
|
||||
const TrackingIdentifier = measurements[i].TrackingIdentifier;
|
||||
const hydratable = adapters.some(adapter =>
|
||||
adapter.isValidCornerstoneTrackingIdentifier(TrackingIdentifier)
|
||||
);
|
||||
|
||||
if (hydratable) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function _checkIfCanAddMeasurementsToDisplaySet(
|
||||
srDisplaySet,
|
||||
newDisplaySet,
|
||||
@ -459,6 +421,12 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
CodeNameCodeSequenceValues.TrackingIdentifier
|
||||
);
|
||||
|
||||
const Findings = mergedContentSequence.filter(
|
||||
item =>
|
||||
item.ConceptNameCodeSequence.CodeValue ===
|
||||
CodeNameCodeSequenceValues.Finding
|
||||
);
|
||||
|
||||
const measurement = {
|
||||
loaded: false,
|
||||
labels: [],
|
||||
@ -467,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,
|
||||
|
||||
@ -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,6 +11,7 @@ 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');
|
||||
@ -60,6 +62,12 @@ export default class DICOMSRDisplayTool extends BaseTool {
|
||||
trackingUniqueIdentifiers.includes(td.TrackingUniqueIdentifier)
|
||||
);
|
||||
|
||||
// First: Render annotations with textboxes moved to closest side
|
||||
// TODO: Render all annotations and get their locations.
|
||||
// TODO: Find suitable places for textboxes that aren't covered.
|
||||
|
||||
let shouldRepositionTextBoxes = false;
|
||||
|
||||
for (let i = 0; i < filteredToolData.length; i++) {
|
||||
const data = filteredToolData[i];
|
||||
const { renderableData, labels } = data;
|
||||
@ -80,8 +88,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
|
||||
@ -110,36 +120,53 @@ export default class DICOMSRDisplayTool extends BaseTool {
|
||||
const { element } = eventData;
|
||||
const context = getNewContext(eventData.canvasContext.canvas);
|
||||
|
||||
debugger;
|
||||
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);
|
||||
|
||||
debugger;
|
||||
|
||||
function textBoxAnchorPoints() {
|
||||
return [{ x: 1000, y: 1000 }];
|
||||
return data.handles.textBox.anchorPoints;
|
||||
}
|
||||
|
||||
draw(context, context => {
|
||||
drawLinkedTextBox(
|
||||
context,
|
||||
element,
|
||||
{
|
||||
active: false,
|
||||
hasMoved: false,
|
||||
movesIndependently: false,
|
||||
drawnIndependently: true,
|
||||
allowedOutsideImage: true,
|
||||
hasBoundingBox: true,
|
||||
x: 256,
|
||||
y: 256,
|
||||
},
|
||||
data.handles.textBox,
|
||||
text,
|
||||
null,
|
||||
data.handles,
|
||||
textBoxAnchorPoints,
|
||||
color,
|
||||
lineWidth,
|
||||
10,
|
||||
0,
|
||||
true
|
||||
);
|
||||
});
|
||||
@ -157,7 +184,7 @@ export default class DICOMSRDisplayTool extends BaseTool {
|
||||
});
|
||||
}
|
||||
|
||||
renderPointOrMultipoint(renderableData, eventData, options) {
|
||||
renderMultipoint(renderableData, eventData, options) {
|
||||
const context = getNewContext(eventData.canvasContext.canvas);
|
||||
|
||||
renderableData.forEach(points => {
|
||||
@ -167,6 +194,46 @@ export default class DICOMSRDisplayTool extends BaseTool {
|
||||
});
|
||||
}
|
||||
|
||||
renderPoint(renderableData, eventData, options) {
|
||||
// Render single point as an arrow.
|
||||
debugger;
|
||||
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;
|
||||
|
||||
debugger;
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
debugger;
|
||||
|
||||
drawArrow(
|
||||
context,
|
||||
handleEndCanvas,
|
||||
handleStartCanvas,
|
||||
color,
|
||||
lineWidth,
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
renderCircle(renderableData, eventData, options) {
|
||||
const { element } = eventData;
|
||||
|
||||
@ -220,6 +287,7 @@ const SHORT_HAND_MAP = {
|
||||
'Long Axis': 'L ',
|
||||
AREA: 'Area ',
|
||||
Length: '',
|
||||
CORNERSTONEFREETEXT: '',
|
||||
};
|
||||
|
||||
function _labelToShorthand(label) {
|
||||
@ -231,3 +299,67 @@ function _labelToShorthand(label) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
21
yarn.lock
21
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"
|
||||
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user