From 5879aedcd0255fe0a56ac4941e4c64ae5ae99765 Mon Sep 17 00:00:00 2001 From: MarcoBologna91 Date: Thu, 28 Oct 2021 18:04:58 +0200 Subject: [PATCH 01/10] Enabled writing of location and description in DICOM SR file --- src/adapters/Cornerstone/MeasurementReport.js | 7 +++ src/utilities/TID300/TID300Measurement.js | 46 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/adapters/Cornerstone/MeasurementReport.js b/src/adapters/Cornerstone/MeasurementReport.js index d20afc60..91aef1a1 100644 --- a/src/adapters/Cornerstone/MeasurementReport.js +++ b/src/adapters/Cornerstone/MeasurementReport.js @@ -17,6 +17,13 @@ function getTID300ContentItem( const TID300Measurement = new toolClass.TID300Representation(args); + if (tool.location) { + TID300Measurement.props.location = tool.location; + } + if (tool.description) { + TID300Measurement.props.description = tool.description; + } + return TID300Measurement; } diff --git a/src/utilities/TID300/TID300Measurement.js b/src/utilities/TID300/TID300Measurement.js index ae4ef1ff..b4dcdd4c 100644 --- a/src/utilities/TID300/TID300Measurement.js +++ b/src/utilities/TID300/TID300Measurement.js @@ -11,6 +11,8 @@ export default class TID300Measurement { ...this.getTrackingGroups(), ...this.getFindingGroup(), ...this.getFindingSiteGroups(), + ...this.getLocation(), + ...this.getDescription(), ...contentSequenceEntries ]; } @@ -94,4 +96,48 @@ export default class TID300Measurement { }; }); } + + getLocation() { + let location = this.props.location; + + console.log(this.props); + + if (!location) { + return []; + } + + return [ + { + RelationshipType: "HAS OBS CONTEXT", + ValueType: "TEXT", + ConceptNameCodeSequence: { + CodeValue: "112041", + CodingSchemeDesignator: "DCM", + CodeMeaning: "Location" + }, + TextValue: location + } + ]; + } + + getDescription() { + let description = this.props.description; + + if (!description) { + return []; + } + + return [ + { + RelationshipType: "HAS OBS CONTEXT", + ValueType: "TEXT", + ConceptNameCodeSequence: { + CodeValue: "112042", + CodingSchemeDesignator: "DCM", + CodeMeaning: "Description" + }, + TextValue: description + } + ]; + } } From 4d3566345c78313a205e125ec8111aa85f925beb Mon Sep 17 00:00:00 2001 From: MarcoBologna91 Date: Thu, 28 Oct 2021 23:53:18 +0200 Subject: [PATCH 02/10] Change in codeValue for location and description tags --- src/utilities/TID300/TID300Measurement.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/utilities/TID300/TID300Measurement.js b/src/utilities/TID300/TID300Measurement.js index b4dcdd4c..90e81a81 100644 --- a/src/utilities/TID300/TID300Measurement.js +++ b/src/utilities/TID300/TID300Measurement.js @@ -100,8 +100,6 @@ export default class TID300Measurement { getLocation() { let location = this.props.location; - console.log(this.props); - if (!location) { return []; } @@ -111,9 +109,9 @@ export default class TID300Measurement { RelationshipType: "HAS OBS CONTEXT", ValueType: "TEXT", ConceptNameCodeSequence: { - CodeValue: "112041", + CodeValue: "121226", CodingSchemeDesignator: "DCM", - CodeMeaning: "Location" + CodeMeaning: "Approximate spatial location" }, TextValue: location } @@ -132,9 +130,9 @@ export default class TID300Measurement { RelationshipType: "HAS OBS CONTEXT", ValueType: "TEXT", ConceptNameCodeSequence: { - CodeValue: "112042", + CodeValue: "111033", CodingSchemeDesignator: "DCM", - CodeMeaning: "Description" + CodeMeaning: "Impression description" }, TextValue: description } From ada1c936559beccc387d0ddab8834cc48da7e084 Mon Sep 17 00:00:00 2001 From: MarcoBologna91 Date: Thu, 28 Oct 2021 23:57:37 +0200 Subject: [PATCH 03/10] Enabled reading of location and description from DICOM SR --- src/adapters/Cornerstone/ArrowAnnotate.js | 48 +++------ src/adapters/Cornerstone/Bidirectional.js | 64 +++-------- src/adapters/Cornerstone/EllipticalRoi.js | 65 ++++-------- src/adapters/Cornerstone/GenericTool.js | 124 ++++++++++++++++++++++ src/adapters/Cornerstone/Length.js | 67 ++++-------- 5 files changed, 193 insertions(+), 175 deletions(-) create mode 100644 src/adapters/Cornerstone/GenericTool.js diff --git a/src/adapters/Cornerstone/ArrowAnnotate.js b/src/adapters/Cornerstone/ArrowAnnotate.js index bccb7084..3580c2a6 100644 --- a/src/adapters/Cornerstone/ArrowAnnotate.js +++ b/src/adapters/Cornerstone/ArrowAnnotate.js @@ -2,49 +2,30 @@ import MeasurementReport from "./MeasurementReport.js"; import TID300Point from "../../utilities/TID300/Point.js"; import CORNERSTONE_4_TAG from "./cornerstone4Tag"; import { toArray } from "../helpers.js"; +import GenericTool from "./GenericTool.js"; const ARROW_ANNOTATE = "ArrowAnnotate"; const FINDING = "121071"; -const FINDING_SITE = "G-C0E3"; const CORNERSTONEFREETEXT = "CORNERSTONEFREETEXT"; -class ArrowAnnotate { - constructor() {} - +class ArrowAnnotate extends GenericTool { // TODO: this function is required for all Cornerstone Tool Adapters, since it is called by MeasurementReport. static getMeasurementData(MeasurementGroup) { - const { ContentSequence } = MeasurementGroup; + const toolState = super.getMeasurementData(MeasurementGroup); - const NUMGroup = toArray(ContentSequence).find( - group => group.ValueType === "NUM" - ); + const { ContentSequence } = MeasurementGroup; - const SCOORDGroup = toArray(NUMGroup.ContentSequence).find( - group => group.ValueType === "SCOORD" - ); + const SCOORDGroup = this.getScoordContent(ContentSequence); const findingGroup = toArray(ContentSequence).find( group => group.ConceptNameCodeSequence.CodeValue === FINDING ); - const findingSiteGroups = toArray(ContentSequence).filter( - group => group.ConceptNameCodeSequence.CodeValue === FINDING_SITE - ); - const text = findingGroup.ConceptCodeSequence.CodeMeaning; const { GraphicData } = SCOORDGroup; - const { ReferencedSOPSequence } = SCOORDGroup.ContentSequence; - const { - ReferencedSOPInstanceUID, - ReferencedFrameNumber - } = ReferencedSOPSequence; - const state = { - sopInstanceUid: ReferencedSOPInstanceUID, - frameIndex: ReferencedFrameNumber || 0, - toolType: ArrowAnnotate.toolType, - active: false, + let arrowState = { handles: { start: { x: GraphicData[0], @@ -68,18 +49,14 @@ class ArrowAnnotate { hasBoundingBox: true } }, - invalidated: true, text, - visible: true, - finding: findingGroup - ? findingGroup.ConceptCodeSequence - : undefined, - findingSites: findingSiteGroups.map(fsg => { - return { ...fsg.ConceptCodeSequence }; - }) + toolName: ARROW_ANNOTATE, + toolType: ArrowAnnotate.toolType }; - return state; + arrowState = Object.assign(toolState, arrowState); + + return arrowState; } static getTID300RepresentationArguments(tool) { @@ -89,7 +66,8 @@ class ArrowAnnotate { const TID300RepresentationArguments = { points, - trackingIdentifierTextValue: `cornerstoneTools@^4.0.0:ArrowAnnotate`, + trackingIdentifierTextValue: + CORNERSTONE_4_TAG + ":" + ARROW_ANNOTATE, findingSites: findingSites || [] }; diff --git a/src/adapters/Cornerstone/Bidirectional.js b/src/adapters/Cornerstone/Bidirectional.js index 7650287f..366d69ab 100644 --- a/src/adapters/Cornerstone/Bidirectional.js +++ b/src/adapters/Cornerstone/Bidirectional.js @@ -2,27 +2,18 @@ import MeasurementReport from "./MeasurementReport"; import TID300Bidirectional from "../../utilities/TID300/Bidirectional"; import CORNERSTONE_4_TAG from "./cornerstone4Tag"; import { toArray } from "../helpers.js"; +import GenericTool from "./GenericTool"; const BIDIRECTIONAL = "Bidirectional"; const LONG_AXIS = "Long Axis"; const SHORT_AXIS = "Short Axis"; -const FINDING = "121071"; -const FINDING_SITE = "G-C0E3"; - -class Bidirectional { - constructor() {} +class Bidirectional extends GenericTool { // TODO: this function is required for all Cornerstone Tool Adapters, since it is called by MeasurementReport. static getMeasurementData(MeasurementGroup) { - const { ContentSequence } = MeasurementGroup; - - const findingGroup = toArray(ContentSequence).find( - group => group.ConceptNameCodeSequence.CodeValue === FINDING - ); + const toolState = super.getMeasurementData(MeasurementGroup); - const findingSiteGroups = toArray(ContentSequence).filter( - group => group.ConceptNameCodeSequence.CodeValue === FINDING_SITE - ); + const { ContentSequence } = MeasurementGroup; const longAxisNUMGroup = toArray(ContentSequence).find( group => group.ConceptNameCodeSequence.CodeMeaning === LONG_AXIS @@ -40,12 +31,6 @@ class Bidirectional { shortAxisNUMGroup.ContentSequence ).find(group => group.ValueType === "SCOORD"); - const { ReferencedSOPSequence } = longAxisSCOORDGroup.ContentSequence; - const { - ReferencedSOPInstanceUID, - ReferencedFrameNumber - } = ReferencedSOPSequence; - // Long axis const longestDiameter = String( @@ -71,11 +56,7 @@ class Bidirectional { ) }; - const state = { - sopInstanceUid: ReferencedSOPInstanceUID, - frameIndex: ReferencedFrameNumber || 1, - toolType: Bidirectional.toolType, - active: false, + let bidirState = { handles: { start: { x: longAxisSCOORDGroup.GraphicData[0], @@ -125,42 +106,31 @@ class Bidirectional { y: bottomRight.y + 10 } }, - invalidated: false, - isCreating: false, longestDiameter, shortestDiameter, - toolType: "Bidirectional", - toolName: "Bidirectional", - visible: true, - finding: findingGroup - ? findingGroup.ConceptCodeSequence - : undefined, - findingSites: findingSiteGroups.map(fsg => { - return { ...fsg.ConceptCodeSequence }; - }) + toolName: BIDIRECTIONAL, + toolType: Bidirectional.toolType }; - return state; + bidirState = Object.assign(toolState, bidirState); + + return bidirState; } static getTID300RepresentationArguments(tool) { + const TID300Rep = super.getTID300RepresentationArguments(tool); const { start, end, perpendicularStart, perpendicularEnd } = tool.handles; - const { - shortestDiameter, - longestDiameter, - finding, - findingSites - } = tool; + const { shortestDiameter, longestDiameter } = tool; const trackingIdentifierTextValue = - "cornerstoneTools@^4.0.0:Bidirectional"; + CORNERSTONE_4_TAG + ":" + BIDIRECTIONAL; - return { + return Object.assign(TID300Rep, { longAxis: { point1: start, point2: end @@ -171,10 +141,8 @@ class Bidirectional { }, longAxisLength: longestDiameter, shortAxisLength: shortestDiameter, - trackingIdentifierTextValue, - finding: finding, - findingSites: findingSites || [] - }; + trackingIdentifierTextValue + }); } } diff --git a/src/adapters/Cornerstone/EllipticalRoi.js b/src/adapters/Cornerstone/EllipticalRoi.js index c0e3c6b9..e8690706 100644 --- a/src/adapters/Cornerstone/EllipticalRoi.js +++ b/src/adapters/Cornerstone/EllipticalRoi.js @@ -1,34 +1,20 @@ import MeasurementReport from "./MeasurementReport"; import TID300Ellipse from "../../utilities/TID300/Ellipse"; import CORNERSTONE_4_TAG from "./cornerstone4Tag"; -import { toArray } from "../helpers.js"; +import GenericTool from "./GenericTool"; const ELLIPTICALROI = "EllipticalRoi"; -const FINDING = "121071"; -const FINDING_SITE = "G-C0E3"; - -class EllipticalRoi { - constructor() {} +class EllipticalRoi extends GenericTool { // TODO: this function is required for all Cornerstone Tool Adapters, since it is called by MeasurementReport. static getMeasurementData(MeasurementGroup) { - const { ContentSequence } = MeasurementGroup; - - const findingGroup = toArray(ContentSequence).find( - group => group.ConceptNameCodeSequence.CodeValue === FINDING - ); + const toolState = super.getMeasurementData(MeasurementGroup); - const findingSiteGroups = toArray(ContentSequence).filter( - group => group.ConceptNameCodeSequence.CodeValue === FINDING_SITE - ); + const { ContentSequence } = MeasurementGroup; - const NUMGroup = toArray(ContentSequence).find( - group => group.ValueType === "NUM" - ); + const NUMGroup = this.getNumericContent(ContentSequence); - const SCOORDGroup = toArray(NUMGroup.ContentSequence).find( - group => group.ValueType === "SCOORD" - ); + const SCOORDGroup = this.getScoordContent(ContentSequence); const { GraphicData } = SCOORDGroup; @@ -66,16 +52,10 @@ class EllipticalRoi { x: majorAxis[1].x - minorAxisDirection.x * halfMinorAxisLength, y: majorAxis[1].y - minorAxisDirection.y * halfMinorAxisLength }; - const { ReferencedSOPSequence } = SCOORDGroup.ContentSequence; - const { - ReferencedSOPInstanceUID, - ReferencedFrameNumber - } = ReferencedSOPSequence; - const state = { - sopInstanceUid: ReferencedSOPInstanceUID, - frameIndex: ReferencedFrameNumber || 0, + + let ellipState = { + toolName: ELLIPTICALROI, toolType: EllipticalRoi.toolType, - active: false, cachedStats: { area: NUMGroup.MeasuredValueSequence.NumericValue }, @@ -100,22 +80,17 @@ class EllipticalRoi { allowedOutsideImage: true, hasBoundingBox: true } - }, - invalidated: true, - visible: true, - finding: findingGroup - ? findingGroup.ConceptCodeSequence - : undefined, - findingSites: findingSiteGroups.map(fsg => { - return { ...fsg.ConceptCodeSequence }; - }) + } }; - return state; + ellipState = Object.assign(toolState, ellipState); + + return ellipState; } static getTID300RepresentationArguments(tool) { - const { cachedStats, handles, finding, findingSites } = tool; + const TID300Rep = super.getTID300RepresentationArguments(tool); + const { cachedStats, handles } = tool; const { start, end } = handles; const { area } = cachedStats; @@ -145,15 +120,13 @@ class EllipticalRoi { } const trackingIdentifierTextValue = - "cornerstoneTools@^4.0.0:EllipticalRoi"; + CORNERSTONE_4_TAG + ":" + ELLIPTICALROI; - return { + return Object.assign(TID300Rep, { area, points, - trackingIdentifierTextValue, - finding, - findingSites: findingSites || [] - }; + trackingIdentifierTextValue + }); } } diff --git a/src/adapters/Cornerstone/GenericTool.js b/src/adapters/Cornerstone/GenericTool.js new file mode 100644 index 00000000..3358b27c --- /dev/null +++ b/src/adapters/Cornerstone/GenericTool.js @@ -0,0 +1,124 @@ +import MeasurementReport from "./MeasurementReport.js"; +import TID300Measurement from "../../utilities/TID300/TID300Measurement"; +import CORNERSTONE_4_TAG from "./cornerstone4Tag"; +import { toArray } from "../helpers.js"; + +const COORD = "SCOORD"; +const DESCRIPTION = "111033"; +const FINDING = "121071"; +const FINDING_SITE = "G-C0E3"; +const GENERIC = "GenericTool"; +const LOCATION = "121226"; +const NUMERIC = "NUM"; + +class GenericTool { + constructor() {} + + static getMeasurementData(MeasurementGroup) { + const { ContentSequence } = MeasurementGroup; + + const findingGroup = toArray(ContentSequence).find( + group => group.ConceptNameCodeSequence.CodeValue === FINDING + ); + + const findingSiteGroups = toArray(ContentSequence).filter( + group => group.ConceptNameCodeSequence.CodeValue === FINDING_SITE + ); + + const LocationGroup = toArray(ContentSequence).filter( + group => group.ConceptNameCodeSequence.CodeValue === LOCATION + ); + const location = LocationGroup[0].TextValue; + + const descriptionGroup = toArray(ContentSequence).filter( + group => group.ConceptNameCodeSequence.CodeValue === DESCRIPTION + ); + const description = descriptionGroup[0].TextValue; + + const SCOORDGroup = this.getScoordContent(ContentSequence); + + const { ReferencedSOPSequence } = SCOORDGroup.ContentSequence; + const { + ReferencedSOPInstanceUID, + ReferencedFrameNumber + } = ReferencedSOPSequence; + + const toolState = { + active: false, + description, + finding: findingGroup + ? findingGroup.ConceptCodeSequence + : undefined, + findingSites: findingSiteGroups.map(fsg => { + return { ...fsg.ConceptCodeSequence }; + }), + frameIndex: ReferencedFrameNumber || 1, + handles: { + start: {}, + end: {}, + textBox: { + hasMoved: false, + movesIndependently: false, + drawnIndependently: true, + allowedOutsideImage: true, + hasBoundingBox: true + } + }, + invalidated: false, + isCreating: false, + location, + sopInstanceUid: ReferencedSOPInstanceUID, + toolName: GENERIC, + toolType: GenericTool.toolType, + visible: true + }; + + return toolState; + } + + static getTID300RepresentationArguments(tool) { + const { finding, findingSites } = tool; + + const trackingIdentifierTextValue = CORNERSTONE_4_TAG + ":" + GENERIC; + + return { + finding, + findingSites: findingSites || [], + trackingIdentifierTextValue + }; + } + + static getNumericContent(ContentSequence) { + return toArray(ContentSequence).find( + group => group.ValueType === NUMERIC + ); + } + + static getScoordContent(ContentSequence) { + const NUMGroup = this.getNumericContent(ContentSequence); + return toArray(NUMGroup.ContentSequence).find( + group => group.ValueType === COORD + ); + } +} + +GenericTool.toolType = GenericTool; +GenericTool.utilityToolType = GENERIC; +GenericTool.TID300Representation = TID300Measurement; +GenericTool.isValidCornerstoneTrackingIdentifier = TrackingIdentifier => { + if (!TrackingIdentifier.includes(":")) { + return false; + } + + const [cornerstone4Tag, toolType] = TrackingIdentifier.split(":"); + + if (cornerstone4Tag !== CORNERSTONE_4_TAG) { + return false; + } + + return toolType === GENERIC; +}; + +MeasurementReport.registerTool(GenericTool); + +export default GenericTool; diff --git a/src/adapters/Cornerstone/Length.js b/src/adapters/Cornerstone/Length.js index 517e5602..30c4229c 100644 --- a/src/adapters/Cornerstone/Length.js +++ b/src/adapters/Cornerstone/Length.js @@ -1,45 +1,22 @@ import MeasurementReport from "./MeasurementReport.js"; import TID300Length from "../../utilities/TID300/Length.js"; import CORNERSTONE_4_TAG from "./cornerstone4Tag"; -import { toArray } from "../helpers.js"; +import GenericTool from "./GenericTool.js"; const LENGTH = "Length"; -const FINDING = "121071"; -const FINDING_SITE = "G-C0E3"; - -class Length { - constructor() {} +class Length extends GenericTool { // TODO: this function is required for all Cornerstone Tool Adapters, since it is called by MeasurementReport. static getMeasurementData(MeasurementGroup) { + const toolState = super.getMeasurementData(MeasurementGroup); + const { ContentSequence } = MeasurementGroup; - const findingGroup = toArray(ContentSequence).find( - group => group.ConceptNameCodeSequence.CodeValue === FINDING - ); - - const findingSiteGroups = toArray(ContentSequence).filter( - group => group.ConceptNameCodeSequence.CodeValue === FINDING_SITE - ); - - const NUMGroup = toArray(ContentSequence).find( - group => group.ValueType === "NUM" - ); - - const SCOORDGroup = toArray(NUMGroup.ContentSequence).find( - group => group.ValueType === "SCOORD" - ); - - const { ReferencedSOPSequence } = SCOORDGroup.ContentSequence; - const { - ReferencedSOPInstanceUID, - ReferencedFrameNumber - } = ReferencedSOPSequence; - const lengthState = { - sopInstanceUid: ReferencedSOPInstanceUID, - frameIndex: ReferencedFrameNumber || 1, - length: NUMGroup.MeasuredValueSequence.NumericValue, - toolType: Length.toolType, + const NUMGroup = this.getNumericContent(ContentSequence); + + const SCOORDGroup = this.getScoordContent(ContentSequence); + + let lengthState = { handles: { start: {}, end: {}, @@ -51,12 +28,9 @@ class Length { hasBoundingBox: true } }, - finding: findingGroup - ? findingGroup.ConceptCodeSequence - : undefined, - findingSites: findingSiteGroups.map(fsg => { - return { ...fsg.ConceptCodeSequence }; - }) + length: NUMGroup.MeasuredValueSequence.NumericValue, + toolName: LENGTH, + toolType: Length.toolType }; [ @@ -66,25 +40,26 @@ class Length { lengthState.handles.end.y ] = SCOORDGroup.GraphicData; + lengthState = Object.assign(toolState, lengthState); + return lengthState; } static getTID300RepresentationArguments(tool) { - const { handles, finding, findingSites } = tool; + const TID300Rep = super.getTID300RepresentationArguments(tool); + const { handles } = tool; const point1 = handles.start; const point2 = handles.end; const distance = tool.length; - const trackingIdentifierTextValue = "cornerstoneTools@^4.0.0:Length"; + const trackingIdentifierTextValue = CORNERSTONE_4_TAG + ":" + LENGTH; - return { + return Object.assign(TID300Rep, { + distance, point1, point2, - distance, - trackingIdentifierTextValue, - finding, - findingSites: findingSites || [] - }; + trackingIdentifierTextValue + }); } } From 4a73a0fb515a073765ee48c55d484ab45929e10b Mon Sep 17 00:00:00 2001 From: MarcoBologna91 Date: Mon, 1 Nov 2021 22:56:13 +0100 Subject: [PATCH 04/10] Angle tool added --- src/adapters/Cornerstone/Angle.js | 90 +++++++++++++++++++++++++++++++ src/adapters/Cornerstone/index.js | 8 +-- src/utilities/TID300/Angle.js | 52 ++++++++++++++++++ src/utilities/TID300/Length.js | 1 - src/utilities/TID300/index.js | 8 +-- 5 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 src/adapters/Cornerstone/Angle.js create mode 100644 src/utilities/TID300/Angle.js diff --git a/src/adapters/Cornerstone/Angle.js b/src/adapters/Cornerstone/Angle.js new file mode 100644 index 00000000..a93acb7a --- /dev/null +++ b/src/adapters/Cornerstone/Angle.js @@ -0,0 +1,90 @@ +import MeasurementReport from "./MeasurementReport.js"; +import TID300Angle from "../../utilities/TID300/Angle.js"; +import CORNERSTONE_4_TAG from "./cornerstone4Tag"; +import GenericTool from "./GenericTool.js"; + +const ANGLE = "Angle"; + +class Angle extends GenericTool { + // TODO: this function is required for all Cornerstone Tool Adapters, since it is called by MeasurementReport. + static getMeasurementData(MeasurementGroup) { + const toolState = super.getMeasurementData(MeasurementGroup); + + const { ContentSequence } = MeasurementGroup; + + const NUMGroup = this.getNumericContent(ContentSequence); + + const SCOORDGroup = this.getScoordContent(ContentSequence); + + let angleState = { + handles: { + start: {}, + middle: {}, + end: {}, + textBox: { + hasMoved: false, + movesIndependently: false, + drawnIndependently: true, + allowedOutsideImage: true, + hasBoundingBox: true + } + }, + rAngle: NUMGroup.MeasuredValueSequence.NumericValue, + toolName: ANGLE, + toolType: Angle.toolType + }; + + [ + angleState.handles.start.x, + angleState.handles.start.y, + angleState.handles.middle.x, + angleState.handles.middle.y, + angleState.handles.end.x, + angleState.handles.end.y + ] = SCOORDGroup.GraphicData; + + angleState = Object.assign(toolState, angleState); + + return angleState; + } + + static getTID300RepresentationArguments(tool) { + const TID300Rep = super.getTID300RepresentationArguments(tool); + const { handles } = tool; + const point1 = handles.start; + const point2 = handles.middle; + const point3 = handles.end; + const rAngle = tool.rAngle; + + const trackingIdentifierTextValue = CORNERSTONE_4_TAG + ":" + ANGLE; + + return Object.assign(TID300Rep, { + point1, + point2, + point3, + rAngle, + trackingIdentifierTextValue + }); + } +} + +Angle.toolType = ANGLE; +Angle.utilityToolType = ANGLE; +Angle.TID300Representation = TID300Angle; +Angle.isValidCornerstoneTrackingIdentifier = TrackingIdentifier => { + if (!TrackingIdentifier.includes(":")) { + return false; + } + + const [cornerstone4Tag, toolType] = TrackingIdentifier.split(":"); + + if (cornerstone4Tag !== CORNERSTONE_4_TAG) { + return false; + } + + return toolType === ANGLE; +}; + +MeasurementReport.registerTool(Angle); + +export default Angle; diff --git a/src/adapters/Cornerstone/index.js b/src/adapters/Cornerstone/index.js index 505289c6..314a3532 100644 --- a/src/adapters/Cornerstone/index.js +++ b/src/adapters/Cornerstone/index.js @@ -4,14 +4,16 @@ import Freehand from "./Freehand.js"; import Bidirectional from "./Bidirectional.js"; import EllipticalRoi from "./EllipticalRoi.js"; import ArrowAnnotate from "./ArrowAnnotate.js"; +import Angle from "./Angle.js"; import Segmentation from "./Segmentation.js"; const Cornerstone = { - Length, - Freehand, + Angle, + ArrowAnnotate, Bidirectional, EllipticalRoi, - ArrowAnnotate, + Freehand, + Length, MeasurementReport, Segmentation }; diff --git a/src/utilities/TID300/Angle.js b/src/utilities/TID300/Angle.js new file mode 100644 index 00000000..d0c635d0 --- /dev/null +++ b/src/utilities/TID300/Angle.js @@ -0,0 +1,52 @@ +import TID300Measurement from "./TID300Measurement.js"; + +export default class Angle extends TID300Measurement { + contentItem() { + const { + point1, + point2, + point3, + rAngle, + ReferencedSOPSequence + } = this.props; + + return this.getMeasurement([ + { + RelationshipType: "CONTAINS", + ValueType: "NUM", + ConceptNameCodeSequence: { + CodeValue: "4000022", + CodingSchemeDesignator: "99PDL-rad", + CodeMeaning: "Angle" + }, + MeasuredValueSequence: { + MeasurementUnitsCodeSequence: { + CodeValue: "deg", + CodingSchemeDesignator: "UCUM", + CodingSchemeVersion: "1.4", + CodeMeaning: "degree" + }, + NumericValue: rAngle + }, + ContentSequence: { + RelationshipType: "INFERRED FROM", + ValueType: "SCOORD", + GraphicType: "POLYLINE", + GraphicData: [ + point1.x, + point1.y, + point2.x, + point2.y, + point3.x, + point3.y + ], + ContentSequence: { + RelationshipType: "SELECTED FROM", + ValueType: "IMAGE", + ReferencedSOPSequence + } + } + } + ]); + } +} diff --git a/src/utilities/TID300/Length.js b/src/utilities/TID300/Length.js index bc418f4d..4c39beae 100644 --- a/src/utilities/TID300/Length.js +++ b/src/utilities/TID300/Length.js @@ -1,4 +1,3 @@ -import { DicomMetaDictionary } from "../../DicomMetaDictionary.js"; import TID300Measurement from "./TID300Measurement.js"; export default class Length extends TID300Measurement { diff --git a/src/utilities/TID300/index.js b/src/utilities/TID300/index.js index 7f965e87..182fc6b0 100644 --- a/src/utilities/TID300/index.js +++ b/src/utilities/TID300/index.js @@ -3,6 +3,7 @@ import Length from "./Length.js"; import Bidirectional from "./Bidirectional.js"; import Polyline from "./Polyline.js"; import Ellipse from "./Ellipse.js"; +import Angle from "./Angle.js"; // To be implemented: // - Cornerstone Probe @@ -61,11 +62,12 @@ import Ellipse from "./Ellipse.js"; // Should specify the Angle measured in Degrees, including the units in UCUM // const TID300 = { - TID300Measurement, - Length, + Angle, Bidirectional, + Ellipse, + Length, Polyline, - Ellipse + TID300Measurement }; export { TID300Measurement, Length }; From f337cb1f2c07055e922878293e5d60dba271a79e Mon Sep 17 00:00:00 2001 From: MarcoBologna91 Date: Tue, 2 Nov 2021 00:23:08 +0100 Subject: [PATCH 05/10] Rectangle tool added --- src/adapters/Cornerstone/RectangleRoi.js | 54 ++++++++++++++++++++++++ src/adapters/Cornerstone/index.js | 12 +++--- src/utilities/TID300/Rectangle.js | 12 ++++++ src/utilities/TID300/index.js | 10 +++-- 4 files changed, 79 insertions(+), 9 deletions(-) create mode 100644 src/adapters/Cornerstone/RectangleRoi.js create mode 100644 src/utilities/TID300/Rectangle.js diff --git a/src/adapters/Cornerstone/RectangleRoi.js b/src/adapters/Cornerstone/RectangleRoi.js new file mode 100644 index 00000000..12cd3af9 --- /dev/null +++ b/src/adapters/Cornerstone/RectangleRoi.js @@ -0,0 +1,54 @@ +import MeasurementReport from "./MeasurementReport"; +import TID300Rectangle from "../../utilities/TID300/Rectangle"; +import CORNERSTONE_4_TAG from "./cornerstone4Tag"; +import EllipticalRoi from "./EllipticalRoi"; + +const RECT = "RectangleRoi"; + +class RectangleRoi extends EllipticalRoi { + static getMeasurementData(MeasurementGroup) { + const ellipState = super.getMeasurementData(MeasurementGroup); + + let rectState = { + toolName: RECT, + toolType: RectangleRoi.toolType + }; + + rectState = Object.assign(ellipState, rectState); + + return rectState; + } + + static getTID300RepresentationArguments(tool) { + const TID300Rep = super.getTID300RepresentationArguments(tool); + + console.log(TID300Rep); + + const trackingIdentifierTextValue = CORNERSTONE_4_TAG + ":" + RECT; + + return Object.assign(TID300Rep, { + trackingIdentifierTextValue + }); + } +} + +RectangleRoi.toolType = RECT; +RectangleRoi.utilityToolType = RECT; +RectangleRoi.TID300Representation = TID300Rectangle; +RectangleRoi.isValidCornerstoneTrackingIdentifier = TrackingIdentifier => { + if (!TrackingIdentifier.includes(":")) { + return false; + } + + const [cornerstone4Tag, toolType] = TrackingIdentifier.split(":"); + + if (cornerstone4Tag !== CORNERSTONE_4_TAG) { + return false; + } + + return toolType === RECT; +}; + +MeasurementReport.registerTool(RectangleRoi); + +export default RectangleRoi; diff --git a/src/adapters/Cornerstone/index.js b/src/adapters/Cornerstone/index.js index 314a3532..a8a20024 100644 --- a/src/adapters/Cornerstone/index.js +++ b/src/adapters/Cornerstone/index.js @@ -1,10 +1,11 @@ -import MeasurementReport from "./MeasurementReport.js"; -import Length from "./Length.js"; -import Freehand from "./Freehand.js"; +import Angle from "./Angle.js"; +import ArrowAnnotate from "./ArrowAnnotate.js"; import Bidirectional from "./Bidirectional.js"; import EllipticalRoi from "./EllipticalRoi.js"; -import ArrowAnnotate from "./ArrowAnnotate.js"; -import Angle from "./Angle.js"; +import Freehand from "./Freehand.js"; +import Length from "./Length.js"; +import MeasurementReport from "./MeasurementReport.js"; +import RectangleRoi from "./RectangleRoi.js"; import Segmentation from "./Segmentation.js"; const Cornerstone = { @@ -15,6 +16,7 @@ const Cornerstone = { Freehand, Length, MeasurementReport, + RectangleRoi, Segmentation }; diff --git a/src/utilities/TID300/Rectangle.js b/src/utilities/TID300/Rectangle.js new file mode 100644 index 00000000..8e1618cd --- /dev/null +++ b/src/utilities/TID300/Rectangle.js @@ -0,0 +1,12 @@ +/* eslint-disable no-console */ +import Ellipse from "./Ellipse"; + +export default class Rectangle extends Ellipse { + contentItem() { + let sequence = super.contentItem(); + + sequence.at(-1).GraphicType = "RECTANGLE"; + console.log(sequence); + return sequence; + } +} diff --git a/src/utilities/TID300/index.js b/src/utilities/TID300/index.js index 182fc6b0..9d3a91dd 100644 --- a/src/utilities/TID300/index.js +++ b/src/utilities/TID300/index.js @@ -1,9 +1,10 @@ -import TID300Measurement from "./TID300Measurement.js"; -import Length from "./Length.js"; +import Angle from "./Angle.js"; import Bidirectional from "./Bidirectional.js"; -import Polyline from "./Polyline.js"; import Ellipse from "./Ellipse.js"; -import Angle from "./Angle.js"; +import Length from "./Length.js"; +import Polyline from "./Polyline.js"; +import Rectangle from "./Rectangle.js"; +import TID300Measurement from "./TID300Measurement.js"; // To be implemented: // - Cornerstone Probe @@ -67,6 +68,7 @@ const TID300 = { Ellipse, Length, Polyline, + Rectangle, TID300Measurement }; From 48cb5fcdc492a3f12554a955959d1ed29f0ff3fd Mon Sep 17 00:00:00 2001 From: MarcoBologna91 Date: Sat, 6 Nov 2021 22:53:56 +0100 Subject: [PATCH 06/10] Lack of description and location handled --- src/adapters/Cornerstone/GenericTool.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/adapters/Cornerstone/GenericTool.js b/src/adapters/Cornerstone/GenericTool.js index 3358b27c..678d38cb 100644 --- a/src/adapters/Cornerstone/GenericTool.js +++ b/src/adapters/Cornerstone/GenericTool.js @@ -28,12 +28,18 @@ class GenericTool { const LocationGroup = toArray(ContentSequence).filter( group => group.ConceptNameCodeSequence.CodeValue === LOCATION ); - const location = LocationGroup[0].TextValue; + let location = ""; + if (LocationGroup && LocationGroup.length > 0) { + location = LocationGroup[0].TextValue; + } const descriptionGroup = toArray(ContentSequence).filter( group => group.ConceptNameCodeSequence.CodeValue === DESCRIPTION ); - const description = descriptionGroup[0].TextValue; + let description = ""; + if (descriptionGroup && descriptionGroup.length > 0) { + description = descriptionGroup[0].TextValue; + } const SCOORDGroup = this.getScoordContent(ContentSequence); From 745704bb8d455de5c446ab392e550be496146c14 Mon Sep 17 00:00:00 2001 From: MarcoBologna91 Date: Sat, 6 Nov 2021 22:54:57 +0100 Subject: [PATCH 07/10] Enabled the loading and writing on cached statistics for rectangle and ellipse --- src/adapters/Cornerstone/EllipticalRoi.js | 8 +++-- src/adapters/Cornerstone/RectangleRoi.js | 2 -- src/utilities/TID300/Ellipse.js | 38 +++++++++++++++++------ src/utilities/TID300/Rectangle.js | 2 +- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/adapters/Cornerstone/EllipticalRoi.js b/src/adapters/Cornerstone/EllipticalRoi.js index e8690706..a95ce28a 100644 --- a/src/adapters/Cornerstone/EllipticalRoi.js +++ b/src/adapters/Cornerstone/EllipticalRoi.js @@ -57,7 +57,9 @@ class EllipticalRoi extends GenericTool { toolName: ELLIPTICALROI, toolType: EllipticalRoi.toolType, cachedStats: { - area: NUMGroup.MeasuredValueSequence.NumericValue + area: NUMGroup.MeasuredValueSequence[0].NumericValue, + mean: NUMGroup.MeasuredValueSequence[1].NumericValue, + stdDev: NUMGroup.MeasuredValueSequence[2].NumericValue }, handles: { end: { @@ -92,7 +94,7 @@ class EllipticalRoi extends GenericTool { const TID300Rep = super.getTID300RepresentationArguments(tool); const { cachedStats, handles } = tool; const { start, end } = handles; - const { area } = cachedStats; + //const { area } = cachedStats; const halfXLength = Math.abs(start.x - end.x) / 2; const halfYLength = Math.abs(start.y - end.y) / 2; @@ -123,7 +125,7 @@ class EllipticalRoi extends GenericTool { CORNERSTONE_4_TAG + ":" + ELLIPTICALROI; return Object.assign(TID300Rep, { - area, + cachedStats, points, trackingIdentifierTextValue }); diff --git a/src/adapters/Cornerstone/RectangleRoi.js b/src/adapters/Cornerstone/RectangleRoi.js index 12cd3af9..94dfc7d5 100644 --- a/src/adapters/Cornerstone/RectangleRoi.js +++ b/src/adapters/Cornerstone/RectangleRoi.js @@ -22,8 +22,6 @@ class RectangleRoi extends EllipticalRoi { static getTID300RepresentationArguments(tool) { const TID300Rep = super.getTID300RepresentationArguments(tool); - console.log(TID300Rep); - const trackingIdentifierTextValue = CORNERSTONE_4_TAG + ":" + RECT; return Object.assign(TID300Rep, { diff --git a/src/utilities/TID300/Ellipse.js b/src/utilities/TID300/Ellipse.js index a2e6c2b1..2c992826 100644 --- a/src/utilities/TID300/Ellipse.js +++ b/src/utilities/TID300/Ellipse.js @@ -20,7 +20,7 @@ function expandPoints(points) { export default class Ellipse extends TID300Measurement { contentItem() { - const { points, ReferencedSOPSequence, area } = this.props; + const { cachedStats, points, ReferencedSOPSequence } = this.props; const GraphicData = expandPoints(points); @@ -33,15 +33,35 @@ export default class Ellipse extends TID300Measurement { CodingSchemeDesignator: "SRT", CodeMeaning: "AREA" }, - MeasuredValueSequence: { - MeasurementUnitsCodeSequence: { - CodeValue: "mm2", - CodingSchemeDesignator: "UCUM", - CodingSchemeVersion: "1.4", - CodeMeaning: "squaremillimeter" + MeasuredValueSequence: [ + { + MeasurementUnitsCodeSequence: { + CodeValue: "mm2", + CodingSchemeDesignator: "UCUM", + CodingSchemeVersion: "1.4", + CodeMeaning: "squaremillimeter" + }, + NumericValue: cachedStats.area }, - NumericValue: area - }, + { + MeasurementUnitsCodeSequence: { + CodeValue: "HU", + CodingSchemeDesignator: "UCUM", + CodingSchemeVersion: "1.4", + CodeMeaning: "Hounsfield unit" + }, + NumericValue: cachedStats.mean + }, + { + MeasurementUnitsCodeSequence: { + CodeValue: "HU", + CodingSchemeDesignator: "UCUM", + CodingSchemeVersion: "1.4", + CodeMeaning: "Hounsfield unit" + }, + NumericValue: cachedStats.stdDev + } + ], ContentSequence: { RelationshipType: "INFERRED FROM", ValueType: "SCOORD", diff --git a/src/utilities/TID300/Rectangle.js b/src/utilities/TID300/Rectangle.js index 8e1618cd..b9c015de 100644 --- a/src/utilities/TID300/Rectangle.js +++ b/src/utilities/TID300/Rectangle.js @@ -5,7 +5,7 @@ export default class Rectangle extends Ellipse { contentItem() { let sequence = super.contentItem(); - sequence.at(-1).GraphicType = "RECTANGLE"; + sequence.at(-1).ContentSequence.GraphicType = "RECTANGLE"; console.log(sequence); return sequence; } From 402a064b34eb10f2fb63721685af640e6c828e2b Mon Sep 17 00:00:00 2001 From: MarcoBologna91 Date: Fri, 12 Nov 2021 15:42:55 +0100 Subject: [PATCH 08/10] Enabled saving of the tail position for the arrow annotation --- src/adapters/Cornerstone/ArrowAnnotate.js | 15 ++++++++++++--- src/utilities/TID300/Point.js | 11 +++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/adapters/Cornerstone/ArrowAnnotate.js b/src/adapters/Cornerstone/ArrowAnnotate.js index 3580c2a6..a104e89c 100644 --- a/src/adapters/Cornerstone/ArrowAnnotate.js +++ b/src/adapters/Cornerstone/ArrowAnnotate.js @@ -36,8 +36,8 @@ class ArrowAnnotate extends GenericTool { // TODO: How do we choose where the end goes? // Just put it pointing from the bottom right for now? end: { - x: GraphicData[0] + 20, - y: GraphicData[1] + 20, + x: GraphicData[2], + y: GraphicData[3], highlight: true, active: false }, @@ -54,13 +54,22 @@ class ArrowAnnotate extends GenericTool { toolType: ArrowAnnotate.toolType }; + if (GraphicData.length === 6) { + arrowState.handles.start.x = GraphicData[0]; + arrowState.handles.start.y = GraphicData[1]; + arrowState.handles.start.z = GraphicData[2]; + arrowState.handles.end.x = GraphicData[3]; + arrowState.handles.end.y = GraphicData[4]; + arrowState.handles.end.z = GraphicData[5]; + } + arrowState = Object.assign(toolState, arrowState); return arrowState; } static getTID300RepresentationArguments(tool) { - const points = [tool.handles.start]; + const points = [tool.handles.start, tool.handles.end]; let { finding, findingSites } = tool; diff --git a/src/utilities/TID300/Point.js b/src/utilities/TID300/Point.js index 2c7a55df..46b0f9a8 100644 --- a/src/utilities/TID300/Point.js +++ b/src/utilities/TID300/Point.js @@ -10,8 +10,15 @@ export default class Point extends TID300Measurement { } = this.props; const GraphicData = use3DSpatialCoordinates - ? [points[0].x, points[0].y, points[0].z] - : [points[0].x, points[0].y]; + ? [ + points[0].x, + points[0].y, + points[0].z, + points[1].x, + points[1].y, + points[1].z + ] + : [points[0].x, points[0].y, points[1].x, points[1].y]; return this.getMeasurement([ { From efa4ea9b64e542ca855d97abdf3e0b8d216f7ce0 Mon Sep 17 00:00:00 2001 From: MarcoBologna91 Date: Wed, 17 Nov 2021 15:12:12 +0100 Subject: [PATCH 09/10] Added checkMeasurementIntegrity in cornerstone adapters --- src/adapters/Cornerstone/Angle.js | 8 +++++++ src/adapters/Cornerstone/ArrowAnnotate.js | 8 +++++++ src/adapters/Cornerstone/Bidirectional.js | 8 +++++++ src/adapters/Cornerstone/EllipticalRoi.js | 8 +++++++ src/adapters/Cornerstone/GenericTool.js | 4 ++++ src/adapters/Cornerstone/Length.js | 8 +++++++ src/adapters/Cornerstone/MeasurementReport.js | 21 ++++++++++++------- 7 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/adapters/Cornerstone/Angle.js b/src/adapters/Cornerstone/Angle.js index a93acb7a..484fb08d 100644 --- a/src/adapters/Cornerstone/Angle.js +++ b/src/adapters/Cornerstone/Angle.js @@ -66,6 +66,14 @@ class Angle extends GenericTool { trackingIdentifierTextValue }); } + + static checkMeasurementIntegrity(tool) { + if (tool.hasOwnProperty("rAngle")) { + return true; + } else { + return false; + } + } } Angle.toolType = ANGLE; diff --git a/src/adapters/Cornerstone/ArrowAnnotate.js b/src/adapters/Cornerstone/ArrowAnnotate.js index a104e89c..9fd47d94 100644 --- a/src/adapters/Cornerstone/ArrowAnnotate.js +++ b/src/adapters/Cornerstone/ArrowAnnotate.js @@ -93,6 +93,14 @@ class ArrowAnnotate extends GenericTool { return TID300RepresentationArguments; } + + static checkMeasurementIntegrity(tool) { + if (tool.hasOwnProperty("text")) { + return true; + } else { + return false; + } + } } ArrowAnnotate.toolType = ARROW_ANNOTATE; diff --git a/src/adapters/Cornerstone/Bidirectional.js b/src/adapters/Cornerstone/Bidirectional.js index 366d69ab..9302918e 100644 --- a/src/adapters/Cornerstone/Bidirectional.js +++ b/src/adapters/Cornerstone/Bidirectional.js @@ -144,6 +144,14 @@ class Bidirectional extends GenericTool { trackingIdentifierTextValue }); } + + static checkMeasurementIntegrity(tool) { + if (tool.longestDiameter > 0 && tool.shortestDiameter > 0) { + return true; + } else { + return false; + } + } } Bidirectional.toolType = BIDIRECTIONAL; diff --git a/src/adapters/Cornerstone/EllipticalRoi.js b/src/adapters/Cornerstone/EllipticalRoi.js index a95ce28a..58e2bc71 100644 --- a/src/adapters/Cornerstone/EllipticalRoi.js +++ b/src/adapters/Cornerstone/EllipticalRoi.js @@ -130,6 +130,14 @@ class EllipticalRoi extends GenericTool { trackingIdentifierTextValue }); } + + static checkMeasurementIntegrity(tool) { + if (tool.hasOwnProperty("cachedStats")) { + return true; + } else { + return false; + } + } } EllipticalRoi.toolType = ELLIPTICALROI; diff --git a/src/adapters/Cornerstone/GenericTool.js b/src/adapters/Cornerstone/GenericTool.js index 678d38cb..33252fe5 100644 --- a/src/adapters/Cornerstone/GenericTool.js +++ b/src/adapters/Cornerstone/GenericTool.js @@ -106,6 +106,10 @@ class GenericTool { group => group.ValueType === COORD ); } + + static checkMeasurementIntegrity(tool) { + return true; + } } GenericTool.toolType = GenericTool; diff --git a/src/adapters/Cornerstone/Length.js b/src/adapters/Cornerstone/Length.js index 30c4229c..1e1374ff 100644 --- a/src/adapters/Cornerstone/Length.js +++ b/src/adapters/Cornerstone/Length.js @@ -61,6 +61,14 @@ class Length extends GenericTool { trackingIdentifierTextValue }); } + + static checkMeasurementIntegrity(tool) { + if (tool.hasOwnProperty("length")) { + return true; + } else { + return false; + } + } } Length.toolType = LENGTH; diff --git a/src/adapters/Cornerstone/MeasurementReport.js b/src/adapters/Cornerstone/MeasurementReport.js index 91aef1a1..2c21eaa7 100644 --- a/src/adapters/Cornerstone/MeasurementReport.js +++ b/src/adapters/Cornerstone/MeasurementReport.js @@ -31,6 +31,7 @@ function getMeasurementGroup(toolType, toolData, ReferencedSOPSequence) { const toolTypeData = toolData[toolType]; const toolClass = MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_TOOL_TYPE[toolType]; + if ( !toolTypeData || !toolTypeData.data || @@ -42,13 +43,19 @@ function getMeasurementGroup(toolType, toolData, ReferencedSOPSequence) { // Loop through the array of tool instances // for this tool - const Measurements = toolTypeData.data.map(tool => { - return getTID300ContentItem( - tool, - toolType, - ReferencedSOPSequence, - toolClass - ); + const Measurements = []; + + toolTypeData.data.forEach(tool => { + if (toolClass.checkMeasurementIntegrity(tool)) { + Measurements.push( + getTID300ContentItem( + tool, + toolType, + ReferencedSOPSequence, + toolClass + ) + ); + } }); return new TID1501MeasurementGroup(Measurements); From 15b05bcf51b0ee3d38983c8d761fcc479796f0d8 Mon Sep 17 00:00:00 2001 From: MarcoBologna91 Date: Mon, 20 Dec 2021 16:55:04 +0100 Subject: [PATCH 10/10] Enabled loading of 2D segmentations --- src/adapters/Cornerstone/Segmentation_4X.js | 28 +++++++++++++-------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/adapters/Cornerstone/Segmentation_4X.js b/src/adapters/Cornerstone/Segmentation_4X.js index 6506bf37..eb8a5091 100644 --- a/src/adapters/Cornerstone/Segmentation_4X.js +++ b/src/adapters/Cornerstone/Segmentation_4X.js @@ -298,16 +298,24 @@ function generateToolState( console.warn("Insufficient metadata, imagePlaneModule missing."); } - const ImageOrientationPatient = Array.isArray(imagePlaneModule.rowCosines) - ? [...imagePlaneModule.rowCosines, ...imagePlaneModule.columnCosines] - : [ - imagePlaneModule.rowCosines.x, - imagePlaneModule.rowCosines.y, - imagePlaneModule.rowCosines.z, - imagePlaneModule.columnCosines.x, - imagePlaneModule.columnCosines.y, - imagePlaneModule.columnCosines.z - ]; + let ImageOrientationPatient; + if (imagePlaneModule.rowCosines && imagePlaneModule.columnCosines) { + ImageOrientationPatient = Array.isArray(imagePlaneModule.rowCosines) + ? [ + ...imagePlaneModule.rowCosines, + ...imagePlaneModule.columnCosines + ] + : [ + imagePlaneModule.rowCosines.x, + imagePlaneModule.rowCosines.y, + imagePlaneModule.rowCosines.z, + imagePlaneModule.columnCosines.x, + imagePlaneModule.columnCosines.y, + imagePlaneModule.columnCosines.z + ]; + } else { + ImageOrientationPatient = [1, 0, 0, 0, 1, 0]; + } // Get IOP from ref series, compute supported orientations: const validOrientations = getValidOrientations(ImageOrientationPatient);