diff --git a/extensions/cornerstone-dicom-seg/package.json b/extensions/cornerstone-dicom-seg/package.json index ca5125ac9..4cc90eab4 100644 --- a/extensions/cornerstone-dicom-seg/package.json +++ b/extensions/cornerstone-dicom-seg/package.json @@ -44,7 +44,9 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/tools": "^1.19.4", + "@cornerstonejs/tools": "^1.20.1", + "@cornerstonejs/adapters": "^1.20.1", + "@kitware/vtk.js": "27.3.1", "react-color": "^2.19.3" } } diff --git a/extensions/cornerstone-dicom-seg/src/commandsModule.ts b/extensions/cornerstone-dicom-seg/src/commandsModule.ts index 7cffb83f7..1926ee9f1 100644 --- a/extensions/cornerstone-dicom-seg/src/commandsModule.ts +++ b/extensions/cornerstone-dicom-seg/src/commandsModule.ts @@ -2,9 +2,16 @@ import dcmjs from 'dcmjs'; import { createReportDialogPrompt } from '@ohif/extension-default'; import { ServicesManager, Types } from '@ohif/core'; import { cache, metaData } from '@cornerstonejs/core'; -import { segmentation as cornerstoneToolsSegmentation } from '@cornerstonejs/tools'; -import { adaptersSEG, helpers } from '@cornerstonejs/adapters'; -import { DicomMetadataStore } from '@ohif/core'; +import { + segmentation as cornerstoneToolsSegmentation, + Enums as cornerstoneToolsEnums, +} from '@cornerstonejs/tools'; +import { adaptersRT, helpers, adaptersSEG } from '@cornerstonejs/adapters'; +import { classes, DicomMetadataStore } from '@ohif/core'; + +import vtkImageMarchingSquares from '@kitware/vtk.js/Filters/General/ImageMarchingSquares'; +import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray'; +import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData'; import { updateViewportsForSegmentationRendering, @@ -12,12 +19,20 @@ import { getTargetViewport, } from './utils/hydrationUtils'; +const { datasetToBlob } = dcmjs.data; + const { Cornerstone3D: { Segmentation: { generateLabelMaps2DFrom3D, generateSegmentation }, }, } = adaptersSEG; +const { + Cornerstone3D: { + RTSS: { generateRTSSFromSegmentations }, + }, +} = adaptersRT; + const { downloadDICOMData } = helpers; const commandsModule = ({ @@ -348,6 +363,40 @@ const commandsModule = ({ return naturalizedReport; }, + /** + * Converts segmentations into RTSS for download. + * This sample function retrieves all segentations and passes to + * cornerstone tool adapter to convert to DICOM RTSS format. It then + * converts dataset to downloadable blob. + * + */ + downloadRTSS: ({ segmentationId }) => { + const segmentations = segmentationService.getSegmentation(segmentationId); + const vtkUtils = { + vtkImageMarchingSquares, + vtkDataArray, + vtkImageData, + }; + + const RTSS = generateRTSSFromSegmentations( + segmentations, + classes.MetadataProvider, + DicomMetadataStore, + cache, + cornerstoneToolsEnums, + vtkUtils + ); + + try { + const reportBlob = datasetToBlob(RTSS); + + //Create a URL for the binary. + const objectUrl = URL.createObjectURL(reportBlob); + window.location.assign(objectUrl); + } catch (e) { + console.warn(e); + } + }, }; const definitions = { @@ -372,6 +421,9 @@ const commandsModule = ({ storeSegmentation: { commandFn: actions.storeSegmentation, }, + downloadRTSS: { + commandFn: actions.downloadRTSS, + }, }; return { diff --git a/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx b/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx index 870c1d454..381fa7cc5 100644 --- a/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx +++ b/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx @@ -1,7 +1,7 @@ import { createReportAsync } from '@ohif/extension-default'; import React, { useEffect, useState, useCallback } from 'react'; import PropTypes from 'prop-types'; -import { SegmentationGroupTable } from '@ohif/ui'; +import { SegmentationGroupTable, LegacyButtonGroup, LegacyButton } from '@ohif/ui'; import callInputDialog from './callInputDialog'; import callColorPickerDialog from './colorPickerDialog'; @@ -215,6 +215,12 @@ export default function PanelSegmentation({ } }; + const onSegmentationDownloadRTSS = segmentationId => { + commandsManager.runCommand('downloadRTSS', { + segmentationId, + }); + }; + return ( <>
@@ -227,6 +233,7 @@ export default function PanelSegmentation({ onSegmentationClick={onSegmentationClick} onSegmentationDelete={onSegmentationDelete} onSegmentationDownload={onSegmentationDownload} + onSegmentationDownloadRTSS={onSegmentationDownloadRTSS} storeSegmentation={storeSegmentation} onSegmentationEdit={onSegmentationEdit} onSegmentClick={onSegmentClick} diff --git a/extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx b/extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx index 4be0ed1b3..f2d580cff 100644 --- a/extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx +++ b/extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx @@ -10,9 +10,9 @@ const TOOL_TYPES = { SPHERE_BRUSH: 'SphereBrush', CIRCULAR_ERASER: 'CircularEraser', SPHERE_ERASER: 'SphereEraser', - CIRCLE_SCISSOR: 'CircleScissor', - RECTANGLE_SCISSOR: 'RectangleScissor', - SPHERE_SCISSOR: 'SphereScissor', + CIRCLE_SHAPE: 'CircleScissor', + RECTANGLE_SHAPE: 'RectangleScissor', + SPHERE_SHAPE: 'SphereScissor', THRESHOLD_CIRCULAR_BRUSH: 'ThresholdCircularBrush', THRESHOLD_SPHERE_BRUSH: 'ThresholdSphereBrush', }; @@ -31,7 +31,7 @@ const initialState = { brushSize: 15, mode: 'CircularEraser', // Can be 'CircularEraser' or 'SphereEraser' }, - Scissors: { + Shapes: { brushSize: 15, mode: 'CircleScissor', // E.g., 'CircleScissor', 'RectangleScissor', or 'SphereScissor' }, @@ -301,24 +301,24 @@ function SegmentationToolbox({ servicesManager, extensionManager }) { ], }, { - name: 'Scissor', - icon: 'icon-tool-scissor', + name: 'Shapes', + icon: 'icon-tool-shape', disabled: !toolsEnabled, active: - state.activeTool === TOOL_TYPES.CIRCLE_SCISSOR || - state.activeTool === TOOL_TYPES.RECTANGLE_SCISSOR || - state.activeTool === TOOL_TYPES.SPHERE_SCISSOR, - onClick: () => setToolActive(TOOL_TYPES.CIRCLE_SCISSOR), + state.activeTool === TOOL_TYPES.CIRCLE_SHAPE || + state.activeTool === TOOL_TYPES.RECTANGLE_SHAPE || + state.activeTool === TOOL_TYPES.SPHERE_SHAPE, + onClick: () => setToolActive(TOOL_TYPES.CIRCLE_SHAPE), options: [ { name: 'Mode', type: 'radio', - value: state.Scissors.mode, - id: 'scissor-mode', + value: state.Shapes.mode, + id: 'shape-mode', values: [ - { value: TOOL_TYPES.CIRCLE_SCISSOR, label: 'Circle' }, - { value: TOOL_TYPES.RECTANGLE_SCISSOR, label: 'Rectangle' }, - { value: TOOL_TYPES.SPHERE_SCISSOR, label: 'Sphere' }, + { value: TOOL_TYPES.CIRCLE_SHAPE, label: 'Circle' }, + { value: TOOL_TYPES.RECTANGLE_SHAPE, label: 'Rectangle' }, + { value: TOOL_TYPES.SPHERE_SHAPE, label: 'Sphere' }, ], onChange: value => setToolActive(value), }, diff --git a/extensions/cornerstone-dicom-sr/package.json b/extensions/cornerstone-dicom-sr/package.json index f25fa2c2f..4dde7495a 100644 --- a/extensions/cornerstone-dicom-sr/package.json +++ b/extensions/cornerstone-dicom-sr/package.json @@ -44,9 +44,9 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^1.19.4", - "@cornerstonejs/core": "^1.19.4", - "@cornerstonejs/tools": "^1.19.4", + "@cornerstonejs/adapters": "^1.20.1", + "@cornerstonejs/core": "^1.20.1", + "@cornerstonejs/tools": "^1.20.1", "classnames": "^2.3.2" } } diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index f6e6440ac..cdaccb4f4 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -36,7 +36,7 @@ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.2", - "@cornerstonejs/dicom-image-loader": "^1.19.4", + "@cornerstonejs/dicom-image-loader": "^1.20.1", "@ohif/core": "3.7.0-beta.101", "@ohif/ui": "3.7.0-beta.101", "dcmjs": "^0.29.6", @@ -52,10 +52,10 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^1.19.4", - "@cornerstonejs/core": "^1.19.4", - "@cornerstonejs/streaming-image-volume-loader": "^1.19.4", - "@cornerstonejs/tools": "^1.19.4", + "@cornerstonejs/adapters": "^1.20.1", + "@cornerstonejs/core": "^1.20.1", + "@cornerstonejs/streaming-image-volume-loader": "^1.20.1", + "@cornerstonejs/tools": "^1.20.1", "@kitware/vtk.js": "27.3.1", "html2canvas": "^1.4.1", "lodash.debounce": "4.0.8", diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json index 62762098d..ad0734231 100644 --- a/extensions/measurement-tracking/package.json +++ b/extensions/measurement-tracking/package.json @@ -30,8 +30,8 @@ "start": "yarn run dev" }, "peerDependencies": { - "@cornerstonejs/core": "^1.19.4", - "@cornerstonejs/tools": "^1.19.4", + "@cornerstonejs/core": "^1.20.1", + "@cornerstonejs/tools": "^1.20.1", "@ohif/core": "3.7.0-beta.101", "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.101", "@ohif/ui": "3.7.0-beta.101", diff --git a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/RTSSReport.js b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/RTSSReport.js deleted file mode 100644 index afcab5604..000000000 --- a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/RTSSReport.js +++ /dev/null @@ -1,235 +0,0 @@ -import AnnotationToPointData from './measurements/AnnotationToPointData'; -import dcmjs from 'dcmjs'; -import { DicomMetadataStore } from '@ohif/core'; - -const { DicomMetaDictionary } = dcmjs.data; - -export default class RTSSReport { - constructor() {} - - /** - * Convert handles to RTSSReport report object containing the dcmjs dicom dataset. - * - * Note: The tool data needs to be formatted in a specific way, and currently - * it is limited to the RectangleROIStartEndTool in the Cornerstone. - * - * @param annotations Array of Cornerstone tool annotation data - * @param metadataProvider Metadata provider - * @param options report generation options - * @returns Report object containing the dataset - */ - static generateReport(annotations, metadataProvider, options) { - let dataset = initializeDataset(annotations, metadataProvider); - - annotations.forEach((annotation, index) => { - const ContourSequence = AnnotationToPointData.convert( - annotation, - index, - metadataProvider, - options - ); - - dataset.StructureSetROISequence.push( - getStructureSetModule(annotation, index, metadataProvider) - ); - - dataset.ROIContourSequence.push(ContourSequence); - dataset.RTROIObservationsSequence.push( - getRTROIObservationsSequence(annotation, index, metadataProvider) - ); - - // ReferencedSeriesSequence - // Todo: handle more than one series - dataset.ReferencedSeriesSequence = getReferencedSeriesSequence( - annotation, - index, - metadataProvider - ); - - // ReferencedFrameOfReferenceSequence - dataset.ReferencedFrameOfReferenceSequence = getReferencedFrameOfReferenceSequence( - annotation, - metadataProvider, - dataset - ); - }); - - const fileMetaInformationVersionArray = new Uint8Array(2); - fileMetaInformationVersionArray[1] = 1; - - const _meta = { - FileMetaInformationVersion: { - Value: [fileMetaInformationVersionArray.buffer], - vr: 'OB', - }, - TransferSyntaxUID: { - Value: ['1.2.840.10008.1.2.1'], - vr: 'UI', - }, - ImplementationClassUID: { - Value: [DicomMetaDictionary.uid()], // TODO: could be git hash or other valid id - vr: 'UI', - }, - ImplementationVersionName: { - Value: ['dcmjs'], - vr: 'SH', - }, - }; - - dataset._meta = _meta; - - return dataset; - } - - /** - * Generate Cornerstone tool state from dataset - * @param {object} dataset dataset - * @param {object} hooks - * @param {function} hooks.getToolClass Function to map dataset to a tool class - * @returns - */ - static generateToolState(dataset, hooks = {}) { - // Todo - console.warn('RTSSReport.generateToolState not implemented'); - } -} - -function initializeDataset(annotations, metadataProvider) { - const rtSOPInstanceUID = DicomMetaDictionary.uid(); - - // get the first annotation data - const { referencedImageId: imageId, FrameOfReferenceUID } = annotations[0].metadata; - - const { studyInstanceUID } = metadataProvider.get('generalSeriesModule', imageId); - - const patientModule = getPatientModule(imageId, metadataProvider); - const rtSeriesModule = getRTSeriesModule(imageId, metadataProvider); - - return { - StructureSetROISequence: [], - ROIContourSequence: [], - RTROIObservationsSequence: [], - ReferencedSeriesSequence: [], - ReferencedFrameOfReferenceSequence: [], - ...patientModule, - ...rtSeriesModule, - StudyInstanceUID: studyInstanceUID, - SOPClassUID: '1.2.840.10008.5.1.4.1.1.481.3', // RT Structure Set Storage - SOPInstanceUID: rtSOPInstanceUID, - Manufacturer: 'dcmjs', - Modality: 'RTSTRUCT', - FrameOfReferenceUID, - PositionReferenceIndicator: '', - StructureSetLabel: '', - StructureSetName: '', - ReferringPhysicianName: '', - OperatorsName: '', - StructureSetDate: DicomMetaDictionary.date(), - StructureSetTime: DicomMetaDictionary.time(), - }; -} - -function getPatientModule(imageId, metadataProvider) { - const generalSeriesModule = metadataProvider.get('generalSeriesModule', imageId); - const generalStudyModule = metadataProvider.get('generalStudyModule', imageId); - const patientStudyModule = metadataProvider.get('patientStudyModule', imageId); - const patientModule = metadataProvider.get('patientModule', imageId); - const patientDemographicModule = metadataProvider.get('patientDemographicModule', imageId); - - return { - Modality: generalSeriesModule.modality, - PatientID: patientModule.patientId, - PatientName: patientModule.patientName, - PatientBirthDate: '', - PatientAge: patientStudyModule.patientAge, - PatientSex: patientDemographicModule.patientSex, - PatientWeight: patientStudyModule.patientWeight, - StudyDate: generalStudyModule.studyDate, - StudyTime: generalStudyModule.studyTime, - StudyID: 'ToDo', - AccessionNumber: generalStudyModule.accessionNumber, - }; -} - -function getReferencedFrameOfReferenceSequence(toolData, metadataProvider, dataset) { - const { referencedImageId: imageId, FrameOfReferenceUID } = toolData.metadata; - const instance = metadataProvider.get('instance', imageId); - const { SeriesInstanceUID } = instance; - - const { ReferencedSeriesSequence } = dataset; - - return [ - { - FrameOfReferenceUID, - RTReferencedStudySequence: [ - { - ReferencedSOPClassUID: dataset.SOPClassUID, - ReferencedSOPInstanceUID: dataset.SOPInstanceUID, - RTReferencedSeriesSequence: [ - { - SeriesInstanceUID, - ContourImageSequence: [...ReferencedSeriesSequence[0].ReferencedInstanceSequence], - }, - ], - }, - ], - }, - ]; -} - -function getReferencedSeriesSequence(toolData, index, metadataProvider) { - // grab imageId from toolData - const { referencedImageId: imageId } = toolData.metadata; - const instance = metadataProvider.get('instance', imageId); - const { SeriesInstanceUID, StudyInstanceUID } = instance; - - const ReferencedSeriesSequence = []; - if (SeriesInstanceUID) { - const series = DicomMetadataStore.getSeries(StudyInstanceUID, SeriesInstanceUID); - - const ReferencedSeries = { - SeriesInstanceUID, - ReferencedInstanceSequence: [], - }; - - series.instances.forEach(instance => { - const { SOPInstanceUID, SOPClassUID } = instance; - ReferencedSeries.ReferencedInstanceSequence.push({ - ReferencedSOPClassUID: SOPClassUID, - ReferencedSOPInstanceUID: SOPInstanceUID, - }); - }); - - ReferencedSeriesSequence.push(ReferencedSeries); - } - - return ReferencedSeriesSequence; -} - -function getRTSeriesModule(imageId, metadataProvider) { - return { - SeriesInstanceUID: DicomMetaDictionary.uid(), // generate a new series instance uid - SeriesNumber: '99', // Todo:: what should be the series number? - }; -} - -function getStructureSetModule(toolData, index, metadataProvider) { - const { FrameOfReferenceUID } = toolData.metadata; - - return { - ROINumber: index + 1, - ROIName: `Todo: name ${index + 1}`, - ROIDescription: `Todo: description ${index + 1}`, - ROIGenerationAlgorithm: 'Todo: algorithm', - ReferencedFrameOfReferenceUID: FrameOfReferenceUID, - }; -} - -function getRTROIObservationsSequence(toolData, index, metadataProvider) { - return { - ObservationNumber: index + 1, - ReferencedROINumber: index + 1, - RTROIInterpretedType: 'Todo: type', - ROIInterpreter: 'Todo: interpreter', - }; -} diff --git a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/dicomRTAnnotationExport.js b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/dicomRTAnnotationExport.js index 999b37add..df4aa9c3c 100644 --- a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/dicomRTAnnotationExport.js +++ b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/dicomRTAnnotationExport.js @@ -1,12 +1,16 @@ -import RTSSReport from './RTSSReport'; import dcmjs from 'dcmjs'; -import { classes } from '@ohif/core'; +import { classes, DicomMetadataStore } from '@ohif/core'; +import { adaptersSEG } from '@cornerstonejs/adapters'; const { datasetToBlob } = dcmjs.data; const metadataProvider = classes.MetadataProvider; export default function dicomRTAnnotationExport(annotations) { - const dataset = RTSSReport.generateReport(annotations, metadataProvider); + const dataset = adaptersSEG.Cornerstone3D.RTStruct.RTSS.generateRTSSFromAnnotations( + annotations, + metadataProvider, + DicomMetadataStore + ); const reportBlob = datasetToBlob(dataset); //Create a URL for the binary. diff --git a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/AnnotationToPointData.js b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/AnnotationToPointData.js deleted file mode 100644 index d1d67cebf..000000000 --- a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/AnnotationToPointData.js +++ /dev/null @@ -1,53 +0,0 @@ -import RectangleROIStartEndThreshold from './RectangleROIStartEndThreshold'; - -function validateAnnotation(annotation) { - if (!annotation?.data) { - throw new Error('Tool data is empty'); - } - - if (!annotation.metadata || annotation.metadata.referenceImageId) { - throw new Error('Tool data is not associated with any imageId'); - } -} - -class AnnotationToPointData { - constructor() {} - - static convert(annotation, index, metadataProvider) { - validateAnnotation(annotation); - - const { toolName } = annotation.metadata; - const toolClass = AnnotationToPointData.TOOL_NAMES[toolName]; - - if (!toolClass) { - throw new Error(`Unknown tool type: ${toolName}, cannot convert to RTSSReport`); - } - - // Each toolData should become a list of contours, ContourSequence - // contains a list of contours with their pointData, their geometry - // type and their length. - const ContourSequence = toolClass.getContourSequence(annotation, metadataProvider); - - // Todo: random rgb color for now, options should be passed in - const color = [ - Math.floor(Math.random() * 255), - Math.floor(Math.random() * 255), - Math.floor(Math.random() * 255), - ]; - - return { - ReferencedROINumber: index + 1, - ROIDisplayColor: color, - ContourSequence, - }; - } - - static register(toolClass) { - AnnotationToPointData.TOOL_NAMES[toolClass.toolName] = toolClass; - } -} - -AnnotationToPointData.TOOL_NAMES = {}; -AnnotationToPointData.register(RectangleROIStartEndThreshold); - -export default AnnotationToPointData; diff --git a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/RectangleROIStartEndThreshold.js b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/RectangleROIStartEndThreshold.js deleted file mode 100644 index c3b86a627..000000000 --- a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/RectangleROIStartEndThreshold.js +++ /dev/null @@ -1,51 +0,0 @@ -// comment -class RectangleROIStartEndThreshold { - constructor() {} - - static getContourSequence(toolData, metadataProvider) { - const { data } = toolData; - const { projectionPoints, projectionPointsImageIds } = data.cachedStats; - - return projectionPoints.map((point, index) => { - const ContourData = getPointData(point); - const ContourImageSequence = getContourImageSequence( - projectionPointsImageIds[index], - metadataProvider - ); - - return { - NumberOfContourPoints: ContourData.length / 3, - ContourImageSequence, - ContourGeometricType: 'CLOSED_PLANAR', - ContourData, - }; - }); - } -} - -RectangleROIStartEndThreshold.toolName = 'RectangleROIStartEndThreshold'; - -function getPointData(points) { - // Since this is a closed contour, the order of the points is important. - // re-order the points to be in the correct order clockwise - // Spread to make sure Float32Arrays are converted to arrays - const orderedPoints = [...points[0], ...points[1], ...points[3], ...points[2]]; - const pointsArray = orderedPoints.flat(); - - // reduce the precision of the points to 2 decimal places - const pointsArrayWithPrecision = pointsArray.map(point => { - return point.toFixed(2); - }); - - return pointsArrayWithPrecision; -} - -function getContourImageSequence(imageId, metadataProvider) { - const sopCommon = metadataProvider.get('sopCommonModule', imageId); - - return { - ReferencedSOPClassUID: sopCommon.sopClassUID, - ReferencedSOPInstanceUID: sopCommon.sopInstanceUID, - }; -} -export default RectangleROIStartEndThreshold; diff --git a/package.json b/package.json index 97fb2c48b..dceeec8b3 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "build:package-all": "lerna run build:package --parallel --stream", "build:package-all-1": "lerna run build:package-1 --parallel --stream", "dev": "lerna run dev:viewer --stream", + "dev:no:cache": "lerna run dev:no:cache --stream", "dev:project": ".scripts/dev.sh", "dev:orthanc": "lerna run dev:orthanc --stream", "dev:dcm4chee": "lerna run dev:dcm4chee --stream", diff --git a/platform/app/package.json b/platform/app/package.json index 5136bf221..bbeaea1e7 100644 --- a/platform/app/package.json +++ b/platform/app/package.json @@ -26,6 +26,7 @@ "build:viewer:demo": "cross-env NODE_ENV=production APP_CONFIG=config/demo.js HTML_TEMPLATE=rollbar.html QUICK_BUILD=false yarn run build", "build": "node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --progress --config .webpack/webpack.pwa.js", "dev": "cross-env NODE_ENV=development webpack serve --config .webpack/webpack.pwa.js", + "dev:no:cache": "cross-env NODE_ENV=development webpack serve --no-cache --config .webpack/webpack.pwa.js", "dev:orthanc": "cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack serve --config .webpack/webpack.pwa.js", "dev:dcm4chee": "cross-env NODE_ENV=development APP_CONFIG=config/local_dcm4chee.js webpack serve --config .webpack/webpack.pwa.js", "dev:static": "cross-env NODE_ENV=development APP_CONFIG=config/local_static.js webpack serve --config .webpack/webpack.pwa.js", @@ -50,7 +51,7 @@ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.2", - "@cornerstonejs/dicom-image-loader": "^1.19.4", + "@cornerstonejs/dicom-image-loader": "^1.20.1", "@ohif/core": "3.7.0-beta.101", "@ohif/extension-cornerstone": "3.7.0-beta.101", "@ohif/extension-cornerstone-dicom-rt": "3.7.0-beta.101", diff --git a/platform/core/package.json b/platform/core/package.json index 57812d99c..e42b0d736 100644 --- a/platform/core/package.json +++ b/platform/core/package.json @@ -35,7 +35,7 @@ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.2", - "@cornerstonejs/dicom-image-loader": "^1.19.4", + "@cornerstonejs/dicom-image-loader": "^1.20.1", "@ohif/ui": "3.7.0-beta.101", "cornerstone-math": "0.1.9", "dicom-parser": "^1.8.21" diff --git a/platform/ui/src/assets/icons/icon-tool-brush.svg b/platform/ui/src/assets/icons/icon-tool-brush.svg index 095613710..7f78d720a 100644 --- a/platform/ui/src/assets/icons/icon-tool-brush.svg +++ b/platform/ui/src/assets/icons/icon-tool-brush.svg @@ -1,15 +1,13 @@ - + icon-tool-brush - - - - - - + + + + - + \ No newline at end of file diff --git a/platform/ui/src/assets/icons/icon-tool-eraser.svg b/platform/ui/src/assets/icons/icon-tool-eraser.svg index 35e29a5dd..5c72e4c37 100644 --- a/platform/ui/src/assets/icons/icon-tool-eraser.svg +++ b/platform/ui/src/assets/icons/icon-tool-eraser.svg @@ -1,16 +1,14 @@ - + icon-tool-eraser - - - - - - - + + + + + - + \ No newline at end of file diff --git a/platform/ui/src/assets/icons/icon-tool-shape.svg b/platform/ui/src/assets/icons/icon-tool-shape.svg new file mode 100644 index 000000000..132f7b58a --- /dev/null +++ b/platform/ui/src/assets/icons/icon-tool-shape.svg @@ -0,0 +1,13 @@ + + + icon-tool-shape + + + + + + + + + + \ No newline at end of file diff --git a/platform/ui/src/assets/icons/icon-tool-threshold.svg b/platform/ui/src/assets/icons/icon-tool-threshold.svg index 9a518c5b3..d1bcaa76e 100644 --- a/platform/ui/src/assets/icons/icon-tool-threshold.svg +++ b/platform/ui/src/assets/icons/icon-tool-threshold.svg @@ -1,21 +1,17 @@ - + icon-tool-threshold - - - - - - - - - - - - + + + + + + + + - + \ No newline at end of file diff --git a/platform/ui/src/components/Icon/getIcon.js b/platform/ui/src/components/Icon/getIcon.js index 9604964a4..d09de1e75 100644 --- a/platform/ui/src/components/Icon/getIcon.js +++ b/platform/ui/src/components/Icon/getIcon.js @@ -137,6 +137,7 @@ import iconMoreMenu from './../../assets/icons/icon-more-menu.svg'; import iconToolBrush from './../../assets/icons/icon-tool-brush.svg'; import iconToolEraser from './../../assets/icons/icon-tool-eraser.svg'; import iconToolScissor from './../../assets/icons/icon-tool-scissor.svg'; +import iconToolShape from './../../assets/icons/icon-tool-shape.svg'; import iconToolThreshold from './../../assets/icons/icon-tool-threshold.svg'; /** Old OHIF */ @@ -282,6 +283,7 @@ const ICONS = { 'icon-tool-brush': iconToolBrush, 'icon-tool-eraser': iconToolEraser, 'icon-tool-scissor': iconToolScissor, + 'icon-tool-shape': iconToolShape, 'icon-tool-threshold': iconToolThreshold, /** Old OHIF */ diff --git a/platform/ui/src/components/SegmentationGroupTable/SegmentationDropDownRow.tsx b/platform/ui/src/components/SegmentationGroupTable/SegmentationDropDownRow.tsx index 0095c9767..4fc5b188d 100644 --- a/platform/ui/src/components/SegmentationGroupTable/SegmentationDropDownRow.tsx +++ b/platform/ui/src/components/SegmentationGroupTable/SegmentationDropDownRow.tsx @@ -10,6 +10,7 @@ function SegmentationDropDownRow({ onToggleSegmentationVisibility, onSegmentationEdit, onSegmentationDownload, + onSegmentationDownloadRTSS, storeSegmentation, onSegmentationDelete, onSegmentationAdd, @@ -40,6 +41,7 @@ function SegmentationDropDownRow({ alignment="left" itemsClassName="text-primary-active" showBorders={false} + maxCharactersPerLine={30} list={[ ...(!disableEditing ? [ @@ -79,11 +81,17 @@ function SegmentationDropDownRow({ : []), ...[ { - title: 'Download', + title: 'Download DICOM SEG', onClick: () => { onSegmentationDownload(activeSegmentation.id); }, }, + { + title: 'Download DICOM RTSTRUCT', + onClick: () => { + onSegmentationDownloadRTSS(activeSegmentation.id); + }, + }, ], ]} > @@ -149,6 +157,7 @@ SegmentationDropDownRow.propTypes = { onToggleSegmentationVisibility: PropTypes.func, onSegmentationEdit: PropTypes.func, onSegmentationDownload: PropTypes.func, + onSegmentationDownloadRTSS: PropTypes.func, storeSegmentation: PropTypes.func, onSegmentationDelete: PropTypes.func, onSegmentationAdd: PropTypes.func, diff --git a/platform/ui/src/components/SegmentationGroupTable/SegmentationGroupTable.tsx b/platform/ui/src/components/SegmentationGroupTable/SegmentationGroupTable.tsx index a247326e3..295a79514 100644 --- a/platform/ui/src/components/SegmentationGroupTable/SegmentationGroupTable.tsx +++ b/platform/ui/src/components/SegmentationGroupTable/SegmentationGroupTable.tsx @@ -22,6 +22,7 @@ const SegmentationGroupTable = ({ onSegmentationClick, onSegmentationDelete, onSegmentationDownload, + onSegmentationDownloadRTSS, storeSegmentation, // segment handlers onSegmentClick, @@ -112,6 +113,7 @@ const SegmentationGroupTable = ({ onSegmentationDelete={onSegmentationDelete} onSegmentationEdit={onSegmentationEdit} onSegmentationDownload={onSegmentationDownload} + onSegmentationDownloadRTSS={onSegmentationDownloadRTSS} storeSegmentation={storeSegmentation} onSegmentationAdd={onSegmentationAdd} onToggleSegmentationVisibility={onToggleSegmentationVisibility} @@ -188,6 +190,7 @@ SegmentationGroupTable.propTypes = { onSegmentationClick: PropTypes.func.isRequired, onSegmentationDelete: PropTypes.func.isRequired, onSegmentationDownload: PropTypes.func.isRequired, + onSegmentationDownloadRTSS: PropTypes.func.isRequired, storeSegmentation: PropTypes.func.isRequired, onSegmentClick: PropTypes.func.isRequired, onSegmentAdd: PropTypes.func.isRequired, @@ -217,6 +220,7 @@ SegmentationGroupTable.defaultProps = { onSegmentationClick: () => {}, onSegmentationDelete: () => {}, onSegmentationDownload: () => {}, + onSemgnetationDownloadRTSS: () => {}, storeSegmentation: () => {}, onSegmentClick: () => {}, onSegmentAdd: () => {}, diff --git a/yarn.lock b/yarn.lock index c5ea684ce..4b89db159 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1482,10 +1482,10 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== -"@cornerstonejs/adapters@^1.19.4": - version "1.19.4" - resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-1.19.4.tgz#65b5f83f709faf2b62ba87effde7579a034fcfd6" - integrity sha512-ulirXb8Jx2zWRviXKbumU1K4J9q+nTRCAeG8SONZFFWlWTSwWWPNyQfOHDb3SE/KcupVk4gKzIjd+r0cC+cPdw== +"@cornerstonejs/adapters@^1.20.1": + version "1.20.1" + resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-1.20.1.tgz#3240a9af56f7508069c1e50fd1ceaa016d3df0e2" + integrity sha512-iEKhw8+/ll0I7bblbO82XNlW8DjdrtNO4M3lTnUj/lBTvt4pk0QK2KM+/ILcaSMizwgZ7ZANlOK06J4cTtINdw== dependencies: "@babel/runtime-corejs2" "^7.17.8" buffer "^6.0.3" @@ -1534,43 +1534,43 @@ resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.2.tgz#e96721d56f6ec96f7f95c16321d88cc8467d8d81" integrity sha512-lgdvBvvNezleY+4pIe2ceUsJzlZe/0PipdeubQ3vZZOz3xxtHHMR1XFCl4fgd8gosR8COHuD7h6q+MwgrwBsng== -"@cornerstonejs/core@^1.19.4": - version "1.19.4" - resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.19.4.tgz#7d527f151a1487955e7817b8113ab4c9ad2f7231" - integrity sha512-vVV3I6SzMbP4JqqRp49fBmVSr44l7qOXrO/gb6J3cY/EmwuUdFeSQdmN6ggouaQ45fhKPqPGcyHNO/iww9BR3Q== +"@cornerstonejs/core@^1.20.1": + version "1.20.1" + resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.20.1.tgz#0d8765b1f9e7a9314a29286d59e5cf7ece3bee2f" + integrity sha512-83Ebs35XSNMhbTXDP3V3tTDjM00Qh3o/eQGDu4cHUq0Cu2nVYXTQeIlaQkLHffoqUDKJh3PzbQCDRYtodJ/vhQ== dependencies: "@kitware/vtk.js" "27.3.1" detect-gpu "^5.0.22" gl-matrix "^3.4.3" lodash.clonedeep "4.5.0" -"@cornerstonejs/dicom-image-loader@^1.19.4": - version "1.19.4" - resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-1.19.4.tgz#778d933f8531233382080c3afea45854e7cf92b5" - integrity sha512-1+Dt+FR12pfbybPwOVJONx2KcoHbTyp8zCxrbmpRUxivwbq55SMExQa4ef8fFL97/+Hb9euczdA+PO9bbcD3Fw== +"@cornerstonejs/dicom-image-loader@^1.20.1": + version "1.20.1" + resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-1.20.1.tgz#01d112f626a6a3bf01c5fc44f2d32061e5c4b835" + integrity sha512-PpRykX3Y3D8LRyNg9vG8hh+CmvUKB8Q8cJ5R1XY0lSv7jsnB1hW2NGA8L9JfKalkUxAugC/nPEwRG6sf9c5z0A== dependencies: "@cornerstonejs/codec-charls" "^1.2.3" "@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2" "@cornerstonejs/codec-openjpeg" "^1.2.2" "@cornerstonejs/codec-openjph" "^2.4.2" - "@cornerstonejs/core" "^1.19.4" + "@cornerstonejs/core" "^1.20.1" dicom-parser "^1.8.9" pako "^2.0.4" uuid "^9.0.0" -"@cornerstonejs/streaming-image-volume-loader@^1.19.4": - version "1.19.4" - resolved "https://registry.yarnpkg.com/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-1.19.4.tgz#d2a7afcd673ba112d851e95d2895109c3490cdce" - integrity sha512-Y+g6hdM3cTsdjKjsgy/H19/qEveRqSeT/Og/mCi7AjKe/UeS+EB3vqA7GfkkyGkM2x/U/+F+5RkyzFCXPA2nOw== +"@cornerstonejs/streaming-image-volume-loader@^1.20.1": + version "1.20.1" + resolved "https://registry.yarnpkg.com/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-1.20.1.tgz#e5053a7bfe9967882115054b50142f36df8f468a" + integrity sha512-E36rlnLDE4EjCPf1f9tB5LQdxtwAHHJuTSdZoBvcEbKOG/EzlNr1bVfHoJoMkPX8aTGfO6Y7dNP57IyqqEY3Bw== dependencies: - "@cornerstonejs/core" "^1.19.4" + "@cornerstonejs/core" "^1.20.1" -"@cornerstonejs/tools@^1.19.4": - version "1.19.4" - resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.19.4.tgz#c0d97c40c3d3b0c13dd359eef703de394994c719" - integrity sha512-BVlmi1LuLH4Q5ZYGAPJT/p7t9p8NGLrux1eBGx7LmDe7G2ZOsOWU3kGkbGAzVtb/d0HB9eBV3cruA0+xRwmpMA== +"@cornerstonejs/tools@^1.20.1": + version "1.20.1" + resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.20.1.tgz#7242ce2cdc25fe8367c855f67f329be1f235ae64" + integrity sha512-0tPvnS7RNkVkZWmUOG/CnPfXRAdU0P9oBGtEC3t2h8uBAiCX/VUxBiXOKkbsnmzkEj1LDElCqLONSHqCvz35tg== dependencies: - "@cornerstonejs/core" "^1.19.4" + "@cornerstonejs/core" "^1.20.1" lodash.clonedeep "4.5.0" lodash.get "^4.4.2"