diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index 4da437df9..9a19c0562 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -35,7 +35,7 @@ "cornerstone-math": "^0.1.8", "cornerstone-tools": "4.18.1", "cornerstone-wado-image-loader": "^3.1.2", - "dcmjs": "0.14.2", + "dcmjs": "0.16.0", "dicom-parser": "^1.8.3", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/default/package.json b/extensions/default/package.json index 931584c38..4663a943c 100644 --- a/extensions/default/package.json +++ b/extensions/default/package.json @@ -34,7 +34,7 @@ "react": "^16.13.1", "react-dom": "^16.13.1", "webpack": "^4.0.0", - "dcmjs": "0.14.2" + "dcmjs": "0.16.0" }, "dependencies": { "@babel/runtime": "7.7.6" diff --git a/extensions/dicom-html/package.json b/extensions/dicom-html/package.json index f09dc872c..0ce0817f9 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.2", + "dcmjs": "0.16.0", "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 64529c2bb..1a100d03d 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.18.1", - "dcmjs": "0.14.2", + "dcmjs": "0.16.0", "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 fac34f06e..4f8609606 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.18.1", - "dcmjs": "0.14.2", + "dcmjs": "0.16.0", "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 c9c2f679f..a84facd9f 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.18.1", "cornerstone-wado-image-loader": "^3.1.2", - "dcmjs": "0.14.2", + "dcmjs": "0.16.0", "dicom-parser": "^1.8.3", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/dicom-sr/src/OHIFCornerstoneSRViewport.js b/extensions/dicom-sr/src/OHIFCornerstoneSRViewport.js index f5a501972..049b579f0 100644 --- a/extensions/dicom-sr/src/OHIFCornerstoneSRViewport.js +++ b/extensions/dicom-sr/src/OHIFCornerstoneSRViewport.js @@ -8,6 +8,7 @@ import { ViewportActionBar, useViewportGrid } from '@ohif/ui'; import TOOL_NAMES from './constants/toolNames'; import { adapters } from 'dcmjs'; import getToolStateToCornerstoneMeasurementSchema from './utils/getToolStateToCornerstoneMeasurementSchema'; +import getLabelFromDCMJSImportedToolData from './utils/getLabelFromDCMJSImportedToolData'; import id from './id'; const { formatDate } = utils; @@ -376,6 +377,8 @@ function OHIFCornerstoneSRViewport({ const source = MeasurementService.getSource('CornerstoneTools', '4'); + data.label = getLabelFromDCMJSImportedToolData(data); + MeasurementService.addRawMeasurement( source, toolType, diff --git a/extensions/dicom-sr/src/getSopClassHandlerModule.js b/extensions/dicom-sr/src/getSopClassHandlerModule.js index 2569e34d5..3ee85e897 100644 --- a/extensions/dicom-sr/src/getSopClassHandlerModule.js +++ b/extensions/dicom-sr/src/getSopClassHandlerModule.js @@ -25,13 +25,19 @@ const CodeNameCodeSequenceValues = { TrackingUniqueIdentifier: '112040', TrackingIdentifier: '112039', Finding: '121071', + FindingSite: 'G-C0E3', // SRT + CornerstoneFreeText: 'CORNERSTONEFREETEXT', // CST4 +}; + +const CodingSchemeDesignators = { + SRT: 'SRT', + cornerstoneTools4: 'CST4', }; const RELATIONSHIP_TYPE = { INFERRED_FROM: 'INFERRED FROM', }; -const CORNERSTONE_CODING_SCHEME_DESIGNATOR = 'CST4'; const CORNERSTONE_FREETEXT_CODE_VALUE = 'CORNERSTONEFREETEXT'; /** @@ -428,12 +434,20 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { CodeNameCodeSequenceValues.TrackingIdentifier ); - const Findings = mergedContentSequence.filter( + const Finding = mergedContentSequence.find( item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.Finding ); + const FindingSites = mergedContentSequence.filter( + item => + item.ConceptNameCodeSequence.CodingSchemeDesignator === + CodingSchemeDesignators.SRT && + item.ConceptNameCodeSequence.CodeValue === + CodeNameCodeSequenceValues.FindingSite + ); + const measurement = { loaded: false, labels: [], @@ -442,19 +456,33 @@ 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 ( + Finding && + Finding.ConceptCodeSequence.CodingSchemeDesignator === + CodingSchemeDesignators.cornerstoneTools4 && + Finding.ConceptCodeSequence.CodeValue === + CodeNameCodeSequenceValues.CornerstoneFreeText + ) { + measurement.labels.push({ + label: CORNERSTONE_FREETEXT_CODE_VALUE, + value: Finding.ConceptCodeSequence.CodeMeaning, + }); + } + + // TODO -> Eventually hopefully support SNOMED or some proper code library, just free text for now. + if (FindingSites.length) { + const cornerstoneFreeTextFindingSite = FindingSites.find( + FindingSite => + FindingSite.ConceptCodeSequence.CodingSchemeDesignator === + CodingSchemeDesignators.cornerstoneTools4 && + FindingSite.ConceptCodeSequence.CodeValue === + CodeNameCodeSequenceValues.CornerstoneFreeText ); - if (cornerstoneFreeTextFinding) { + + if (cornerstoneFreeTextFindingSite) { measurement.labels.push({ label: CORNERSTONE_FREETEXT_CODE_VALUE, - value: cornerstoneFreeTextFinding.ConceptCodeSequence.CodeMeaning, + value: cornerstoneFreeTextFindingSite.ConceptCodeSequence.CodeMeaning, }); } } diff --git a/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js b/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js index 4efca058e..d337cba59 100644 --- a/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js +++ b/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js @@ -280,10 +280,9 @@ 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); + // TODO -> max 3 for now (label + shortAxis + longAxis), need a generic solution for this! + const labelLength = Math.min(labels.length, 3); const lines = []; for (let i = 0; i < labelLength; i++) { diff --git a/extensions/dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js b/extensions/dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js new file mode 100644 index 000000000..77dc0ad75 --- /dev/null +++ b/extensions/dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js @@ -0,0 +1,23 @@ +/** + * Extracts the label from the toolData imported from dcmjs. We need to do this + * as dcmjs does not depeend on OHIF/the MeasurementService, it just produces data for cornestoneTools. + * This optional data is available for the consumer to process if they wish to. + * @param {object} toolData The tooldata relating to the + * + * @returns {string} The extracted label. + */ +export default function getLabelFromDCMJSImportedToolData(toolData) { + const { findingSites = [], finding } = toolData; + + let freeTextLabel = findingSites.find( + fs => fs.CodeValue === 'CORNERSTONEFREETEXT' + ); + + if (freeTextLabel) { + return freeTextLabel.CodeMeaning; + } + + if (finding && finding.CodeValue === 'CORNERSTONEFREETEXT') { + return finding.CodeMeaning; + } +} diff --git a/extensions/dicom-sr/src/utils/getToolStateToCornerstoneMeasurementSchema.js b/extensions/dicom-sr/src/utils/getToolStateToCornerstoneMeasurementSchema.js index d37b8c0be..21f2c2dc1 100644 --- a/extensions/dicom-sr/src/utils/getToolStateToCornerstoneMeasurementSchema.js +++ b/extensions/dicom-sr/src/utils/getToolStateToCornerstoneMeasurementSchema.js @@ -51,7 +51,7 @@ function Length(measurementData, imageId, _getValueTypeFromToolType) { StudyInstanceUID, } = instance; - const { handles } = measurementData; + const { handles, label } = measurementData; const points = []; Object.keys(handles).map(handle => { @@ -75,6 +75,7 @@ function Length(measurementData, imageId, _getValueTypeFromToolType) { length: measurementData.length, type: _getValueTypeFromToolType(tool), points, + label, }; } @@ -88,7 +89,7 @@ function Bidirectional(measurementData, imageId, _getValueTypeFromToolType) { StudyInstanceUID, } = instance; - const { handles } = measurementData; + const { handles, label } = measurementData; const longAxis = [handles.start, handles.end]; const shortAxis = [handles.perpendicularStart, handles.perpendicularEnd]; @@ -106,6 +107,7 @@ function Bidirectional(measurementData, imageId, _getValueTypeFromToolType) { longestDiameter: measurementData.longestDiameter, type: _getValueTypeFromToolType(tool), points: { longAxis, shortAxis }, + label, }; } @@ -119,7 +121,8 @@ function EllipticalRoi(measurementData, imageId, _getValueTypeFromToolType) { StudyInstanceUID, } = instance; - const { start, end } = measurementData.handles; + const { handles, label } = measurementData; + const { start, end } = handles; const halfXLength = Math.abs(start.x - end.x) / 2; const halfYLength = Math.abs(start.y - end.y) / 2; @@ -161,6 +164,7 @@ function EllipticalRoi(measurementData, imageId, _getValueTypeFromToolType) { .area /* TODO: Add concept names instead (descriptor) */, type: _getValueTypeFromToolType(tool), points, + label, }; } @@ -198,5 +202,6 @@ function ArrowAnnotate(measurementData, imageId, _getValueTypeFromToolType) { text: measurementData.text, type: _getValueTypeFromToolType(tool), points, + label, }; } diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json index 36a719cf0..9400de9f8 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.18.1", - "dcmjs": "0.14.2", + "dcmjs": "0.16.0", "prop-types": "^15.6.2", "react": "^16.13.1", "react-dom": "^16.13.1", diff --git a/extensions/measurement-tracking/src/_shared/createReportAsync.js b/extensions/measurement-tracking/src/_shared/createReportAsync.js index baae99cae..5aca672db 100644 --- a/extensions/measurement-tracking/src/_shared/createReportAsync.js +++ b/extensions/measurement-tracking/src/_shared/createReportAsync.js @@ -18,7 +18,8 @@ async function createReportAsync(servicesManager, dataSource, measurements) { try { const naturalizedReport = await DICOMSR.storeMeasurements( measurements, - dataSource + dataSource, + ['ArrowAnnotate'] ); DisplaySetService.makeDisplaySets([naturalizedReport], { diff --git a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js index b19144db8..ab130a28c 100644 --- a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js +++ b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js @@ -147,8 +147,6 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { } function exportReport() { - const dataSources = extensionManager.getDataSources(); - const dataSource = dataSources[0]; const measurements = MeasurementService.getMeasurements(); const trackedMeasurements = measurements.filter( m => @@ -156,8 +154,9 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { trackedSeries.includes(m.referenceSeriesUID) ); - // TODO -> local download. - DICOMSR.downloadReport(trackedMeasurements, dataSource); + const additionalFindings = ['ArrowAnnotate']; + + DICOMSR.downloadReport(trackedMeasurements, additionalFindings); } const jumpToImage = ({ id, isActive }) => { diff --git a/extensions/vtk/package.json b/extensions/vtk/package.json index 6c9039679..52104c230 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.2", + "dcmjs": "0.16.0", "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 8307d44f2..bcb7a8541 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.2", + "dcmjs": "0.16.0", "dicomweb-client": "^0.6.0", "immer": "6.0.2", "isomorphic-base64": "^1.0.2", diff --git a/platform/core/src/DICOMSR/dataExchange.js b/platform/core/src/DICOMSR/dataExchange.js index b0dad9044..817d80482 100644 --- a/platform/core/src/DICOMSR/dataExchange.js +++ b/platform/core/src/DICOMSR/dataExchange.js @@ -46,10 +46,12 @@ const retrieveMeasurements = server => { /** * * @param {object[]} measurementData An array of measurements from the measurements service + * @param {string[]} additionalFindingTypes toolTypes that should be stored with labels as Findings + * as opposed to Finding Sites. * that you wish to serialize. */ -const downloadReport = measurementData => { - const srDataset = generateReport(measurementData); +const downloadReport = (measurementData, additionalFindingTypes = []) => { + const srDataset = generateReport(measurementData, additionalFindingTypes); const reportBlob = dcmjs.data.datasetToBlob(srDataset); //Create a URL for the binary. @@ -61,10 +63,13 @@ const downloadReport = measurementData => { * * @param {object[]} measurementData An array of measurements from the measurements service * that you wish to serialize. + * @param {string[]} additionalFindingTypes toolTypes that should be stored with labels as Findings */ -const generateReport = measurementData => { - const ids = measurementData.map(md => md.id); - const filteredToolState = _getFilteredCornerstoneToolState(ids); +const generateReport = (measurementData, additionalFindingTypes) => { + const filteredToolState = _getFilteredCornerstoneToolState( + measurementData, + additionalFindingTypes + ); const report = MeasurementReport.generateReport( filteredToolState, @@ -79,9 +84,15 @@ const generateReport = measurementData => { * @param {object[]} measurementData An array of measurements from the measurements service * that you wish to serialize. * @param {object} dataSource The dataSource that you wish to use to persist the data. + * @param {string[]} additionalFindingTypes toolTypes that should be stored with labels as Findings + * as opposed to Finding Sites. * @return {object} The naturalized report */ -const storeMeasurements = async (measurementData, dataSource) => { +const storeMeasurements = async ( + measurementData, + dataSource, + additionalFindingTypes = [] +) => { // TODO -> Eventually use the measurements directly and not the dcmjs adapter, // But it is good enough for now whilst we only have cornerstone as a datasource. log.info('[DICOMSR] storeMeasurements'); @@ -92,7 +103,10 @@ const storeMeasurements = async (measurementData, dataSource) => { } try { - const naturalizedReport = generateReport(measurementData); + const naturalizedReport = generateReport( + measurementData, + additionalFindingTypes + ); const { StudyInstanceUID } = naturalizedReport; await dataSource.store.dicom(naturalizedReport); @@ -110,7 +124,12 @@ const storeMeasurements = async (measurementData, dataSource) => { } }; -function _getFilteredCornerstoneToolState(uidFilter) { +function _getFilteredCornerstoneToolState( + measurementData, + additionalFindingTypes +) { + const uidFilter = measurementData.map(md => md.id); + const globalToolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState(); const filteredToolState = {}; @@ -127,9 +146,38 @@ function _getFilteredCornerstoneToolState(uidFilter) { }; } + const measurmentDataI = measurementData.find(md => md.id === toolDataI.id); + const toolData = imageIdSpecificToolState[toolType].data; - toolData.push(toolDataI); + let finding; + const findingSites = []; + + // NOTE -> Any kind of freetext value abuses the DICOM standard, + // As CodeValues should map 1:1 with CodeMeanings. + // Ideally we would actually use SNOMED codes for this. + if (measurmentDataI.label) { + if (additionalFindingTypes.includes(toolType)) { + finding = { + CodeValue: 'CORNERSTONEFREETEXT', + CodingSchemeDesignator: 'CST4', + CodeMeaning: measurmentDataI.label, + }; + } else { + findingSites.push({ + CodeValue: 'CORNERSTONEFREETEXT', + CodingSchemeDesignator: 'CST4', + CodeMeaning: measurmentDataI.label, + }); + } + } + + const measurement = Object.assign({}, toolDataI, { + finding, + findingSites, + }); + + toolData.push(measurement); } const uids = uidFilter.slice(); diff --git a/platform/viewer/package.json b/platform/viewer/package.json index 0a630f589..b7b7a7665 100644 --- a/platform/viewer/package.json +++ b/platform/viewer/package.json @@ -69,7 +69,7 @@ "cornerstone-math": "^0.1.8", "cornerstone-tools": "4.18.1", "cornerstone-wado-image-loader": "^3.1.2", - "dcmjs": "0.14.2", + "dcmjs": "0.16.0", "dicom-parser": "^1.8.3", "dicomweb-client": "^0.4.4", "dotenv-webpack": "^1.7.0", diff --git a/yarn.lock b/yarn.lock index 89765864a..5a2d2bd98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7364,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.2: - version "0.14.2" - resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.14.2.tgz#bc4926c3312632ec61a5d68753c0857aebb65793" - integrity sha512-W4/f34Ph6Nrc95QhL/7f1kPlfR2YJMbyEsbPELt3xml17SFnkEgXAeO41qHZ7KywozO/lBW5sXGOpMenuYVV2g== +dcmjs@0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.16.0.tgz#714755d006c3a49cf65ef13e627ff7a23151d35a" + integrity sha512-41nY3Isg5i5FoSd1s67aUgUHi146r+fb8JuiRvDsxvm7MEkkdjIzMYMtxjsrVwKRW1xRH+x34nxbV9+8cc7WQw== dependencies: "@babel/polyfill" "^7.8.3" "@babel/runtime" "^7.8.4"