diff --git a/.circleci/config.yml b/.circleci/config.yml index d2fce6395..88ea0bcd3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,7 @@ version: 2.1 ## orbs: codecov: codecov/codecov@1.0.5 - cypress: cypress-io/cypress@3.1.4 + cypress: cypress-io/cypress@3.3.1 executors: cypress-custom: diff --git a/.codecov.yml b/.codecov.yml deleted file mode 100644 index ec9519c68..000000000 --- a/.codecov.yml +++ /dev/null @@ -1,25 +0,0 @@ -# ABOUT: -# https://docs.codecov.io/docs/codecov-yaml -# -# -# COMMIT STATUS: -# https://docs.codecov.io/docs/commit-status -coverage: - status: - project: - default: - threshold: 0.5% - core: - flags: core - threshold: 0.5% - viewer: - flags: viewer - threshold: 0.5% - patch: off -flags: - core: - paths: - - platform/core - viewer: - paths: - - platform/app diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml deleted file mode 100644 index 7373affc3..000000000 --- a/.github/workflows/codespell.yml +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Codespell - -on: - push: - branches: [master] - pull_request: - branches: [master] - -permissions: - contents: read - -jobs: - codespell: - name: Check for spelling errors - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Codespell - uses: codespell-project/actions-codespell@v2 diff --git a/.gitignore b/.gitignore index ed3400749..245556be5 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ yarn-error.log .DS_Store .env *.code-workspace +.directory # Common Example Data Directories sampledata/ diff --git a/.scripts/dicom-json-generator.js b/.scripts/dicom-json-generator.js new file mode 100644 index 000000000..5b9f45a16 --- /dev/null +++ b/.scripts/dicom-json-generator.js @@ -0,0 +1,265 @@ +/* + * This script uses nodejs to generate a JSON file from a DICOM study folder. + * You need to have dcmjs installed in your project. + * The JSON file can be used to load the study into the OHIF Viewer. You can get more detail + * in the DICOM JSON Data source on docs.ohif.org + * + * Usage: node dicomStudyToJSONLaunch.js + * + * params: + * - studyFolder: path to the study folder + * - urlPrefix: prefix to the url that will be used to load the study into the viewer. For instance + * we use https://ohif-assets.s3.us-east-2.amazonaws.com/dicom-json/data as the urlPrefix for the + * example since the data is hosted on S3 and each study is in a folder. So the url in the generated + * json file for the first instance of the first series of the first study will be + * dicomweb:https://ohif-assets.s3.us-east-2.amazonaws.com/dicom-json/data/Series1/Instance1 + * - outputJSONPath: path to the output JSON file + */ +const dcmjs = require('dcmjs'); +const path = require('path'); +const fs = require('fs').promises; + +const args = process.argv.slice(2); +const [studyDirectory, urlPrefix, outputPath] = args; + +if (args.length !== 3) { + console.error('Usage: node dicomStudyToJSONLaunch.js '); + process.exit(1); +} + +const model = { + studies: [], +}; + +async function convertDICOMToJSON(studyDirectory, urlPrefix, outputPath) { + try { + const files = await recursiveReadDir(studyDirectory); + console.debug('Processing...'); + + for (const file of files) { + if (!file.includes('.DS_Store') && !file.includes('.xml')) { + const arrayBuffer = await fs.readFile(file); + const dicomDict = dcmjs.data.DicomMessage.readFile(arrayBuffer.buffer); + const instance = dcmjs.data.DicomMetaDictionary.naturalizeDataset(dicomDict.dict); + + instance.fileLocation = createImageId(file, urlPrefix, studyDirectory); + processInstance(instance); + } + } + + console.log('Successfully loaded data'); + + model.studies.forEach(study => { + study.NumInstances = findInstancesNumber(study); + study.Modalities = findModalities(study).join('/'); + }); + + await fs.writeFile(outputPath, JSON.stringify(model, null, 2)); + console.log('JSON saved'); + } catch (error) { + console.error(error); + } +} + +async function recursiveReadDir(dir) { + let results = []; + const list = await fs.readdir(dir); + for (const file of list) { + const filePath = path.resolve(dir, file); + const stat = await fs.stat(filePath); + if (stat.isDirectory()) { + const res = await recursiveReadDir(filePath); + results = results.concat(res); + } else { + results.push(filePath); + } + } + return results; +} + +function createImageId(fileLocation, urlPrefix, studyDirectory) { + const relativePath = path.relative(studyDirectory, fileLocation); + const normalizedPath = path.normalize(relativePath).replace(/\\/g, '/'); + return `dicomweb:${urlPrefix}${normalizedPath}`; +} + +function processInstance(instance) { + const { StudyInstanceUID, SeriesInstanceUID } = instance; + let study = getStudy(StudyInstanceUID); + + if (!study) { + study = createStudyMetadata(StudyInstanceUID, instance); + model.studies.push(study); + } + + let series = getSeries(StudyInstanceUID, SeriesInstanceUID); + + if (!series) { + series = createSeriesMetadata(instance); + study.series.push(series); + } + + const instanceMetaData = + instance.NumberOfFrames > 1 + ? createInstanceMetaDataMultiFrame(instance) + : createInstanceMetaData(instance); + + series.instances.push(...[].concat(instanceMetaData)); +} + +function getStudy(StudyInstanceUID) { + return model.studies.find(study => study.StudyInstanceUID === StudyInstanceUID); +} + +function getSeries(StudyInstanceUID, SeriesInstanceUID) { + const study = getStudy(StudyInstanceUID); + return study + ? study.series.find(series => series.SeriesInstanceUID === SeriesInstanceUID) + : undefined; +} + +const findInstancesNumber = study => { + let numInstances = 0; + study.series.forEach(aSeries => { + numInstances = numInstances + aSeries.instances.length; + }); + return numInstances; +}; + +const findModalities = study => { + let modalities = new Set(); + study.series.forEach(aSeries => { + modalities.add(aSeries.Modality); + }); + return Array.from(modalities); +}; + +function createStudyMetadata(StudyInstanceUID, instance) { + return { + StudyInstanceUID, + StudyDescription: instance.StudyDescription, + StudyDate: instance.StudyDate, + StudyTime: instance.StudyTime, + PatientName: instance.PatientName, + PatientID: instance.PatientID || '1234', // this is critical to have + AccessionNumber: instance.AccessionNumber, + PatientAge: instance.PatientAge, + PatientSex: instance.PatientSex, + PatientWeight: instance.PatientWeight, + series: [], + }; +} +function createSeriesMetadata(instance) { + return { + SeriesInstanceUID: instance.SeriesInstanceUID, + SeriesDescription: instance.SeriesDescription, + SeriesNumber: instance.SeriesNumber, + SeriesTime: instance.SeriesTime, + Modality: instance.Modality, + SliceThickness: instance.SliceThickness, + instances: [], + }; +} +function commonMetaData(instance) { + return { + Columns: instance.Columns, + Rows: instance.Rows, + InstanceNumber: instance.InstanceNumber, + SOPClassUID: instance.SOPClassUID, + AcquisitionNumber: instance.AcquisitionNumber, + PhotometricInterpretation: instance.PhotometricInterpretation, + BitsAllocated: instance.BitsAllocated, + BitsStored: instance.BitsStored, + PixelRepresentation: instance.PixelRepresentation, + SamplesPerPixel: instance.SamplesPerPixel, + PixelSpacing: instance.PixelSpacing, + HighBit: instance.HighBit, + ImageOrientationPatient: instance.ImageOrientationPatient, + ImagePositionPatient: instance.ImagePositionPatient, + FrameOfReferenceUID: instance.FrameOfReferenceUID, + ImageType: instance.ImageType, + Modality: instance.Modality, + SOPInstanceUID: instance.SOPInstanceUID, + SeriesInstanceUID: instance.SeriesInstanceUID, + StudyInstanceUID: instance.StudyInstanceUID, + WindowCenter: instance.WindowCenter, + WindowWidth: instance.WindowWidth, + RescaleIntercept: instance.RescaleIntercept, + RescaleSlope: instance.RescaleSlope, + }; +} + +function conditionalMetaData(instance) { + return { + ...(instance.ConceptNameCodeSequence && { + ConceptNameCodeSequence: instance.ConceptNameCodeSequence, + }), + ...(instance.SeriesDate && { SeriesDate: instance.SeriesDate }), + ...(instance.ReferencedSeriesSequence && { + ReferencedSeriesSequence: instance.ReferencedSeriesSequence, + }), + ...(instance.SharedFunctionalGroupsSequence && { + SharedFunctionalGroupsSequence: instance.SharedFunctionalGroupsSequence, + }), + ...(instance.PerFrameFunctionalGroupsSequence && { + PerFrameFunctionalGroupsSequence: instance.PerFrameFunctionalGroupsSequence, + }), + ...(instance.ContentSequence && { ContentSequence: instance.ContentSequence }), + ...(instance.ContentTemplateSequence && { + ContentTemplateSequence: instance.ContentTemplateSequence, + }), + ...(instance.CurrentRequestedProcedureEvidenceSequence && { + CurrentRequestedProcedureEvidenceSequence: instance.CurrentRequestedProcedureEvidenceSequence, + }), + ...(instance.CodingSchemeIdentificationSequence && { + CodingSchemeIdentificationSequence: instance.CodingSchemeIdentificationSequence, + }), + ...(instance.RadiopharmaceuticalInformationSequence && { + RadiopharmaceuticalInformationSequence: instance.RadiopharmaceuticalInformationSequence, + }), + ...(instance.ROIContourSequence && { + ROIContourSequence: instance.ROIContourSequence, + }), + ...(instance.StructureSetROISequence && { + StructureSetROISequence: instance.StructureSetROISequence, + }), + ...(instance.ReferencedFrameOfReferenceSequence && { + ReferencedFrameOfReferenceSequence: instance.ReferencedFrameOfReferenceSequence, + }), + ...(instance.CorrectedImage && { CorrectedImage: instance.CorrectedImage }), + ...(instance.Units && { Units: instance.Units }), + ...(instance.DecayCorrection && { DecayCorrection: instance.DecayCorrection }), + ...(instance.AcquisitionDate && { AcquisitionDate: instance.AcquisitionDate }), + ...(instance.AcquisitionTime && { AcquisitionTime: instance.AcquisitionTime }), + ...(instance.PatientWeight && { PatientWeight: instance.PatientWeight }), + ...(instance.NumberOfFrames && { NumberOfFrames: instance.NumberOfFrames }), + ...(instance.FrameTime && { FrameTime: instance.FrameTime }), + ...(instance.EncapsulatedDocument && { EncapsulatedDocument: instance.EncapsulatedDocument }), + ...(instance.SequenceOfUltrasoundRegions && { + SequenceOfUltrasoundRegions: instance.SequenceOfUltrasoundRegions, + }), + }; +} + +function createInstanceMetaData(instance) { + const metadata = { + ...commonMetaData(instance), + ...conditionalMetaData(instance), + }; + return { metadata, url: instance.fileLocation }; +} + +function createInstanceMetaDataMultiFrame(instance) { + const instances = []; + const commonData = commonMetaData(instance); + const conditionalData = conditionalMetaData(instance); + + for (let i = 1; i <= instance.NumberOfFrames; i++) { + const metadata = { ...commonData, ...conditionalData }; + const result = { metadata, url: instance.fileLocation + `?frame=${i}` }; + instances.push(result); + } + return instances; +} + +convertDICOMToJSON(studyDirectory, urlPrefix, outputPath); diff --git a/.vscode/settings.json b/.vscode/settings.json index db08c79b1..84cc76bc2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -31,6 +31,6 @@ "prettier.endOfLine": "lf", "workbench.colorCustomizations": {}, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" } } diff --git a/.webpack/webpack.base.js b/.webpack/webpack.base.js index 6efedd0d2..aa48230d1 100644 --- a/.webpack/webpack.base.js +++ b/.webpack/webpack.base.js @@ -120,6 +120,17 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => { test: /\.wasm/, type: 'asset/resource', }, + { + test: /\.(png|jpe?g|gif|svg)$/i, + use: [ + { + loader: 'file-loader', + options: { + name: 'assets/images/[name].[ext]', + }, + }, + ], + }, ], //.concat(vtkRules), }, resolve: { diff --git a/CHANGELOG.md b/CHANGELOG.md index f71b59961..87382b8ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,20 +3,1008 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -# [3.7.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.7.0) (2023-10-11) +# [3.8.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.92...v3.8.0-beta.93) (2024-04-29) + + +### Bug Fixes + +* **toolbox:** Preserve user-specified tool state and streamline command execution ([#4063](https://github.com/OHIF/Viewers/issues/4063)) ([f1a736d](https://github.com/OHIF/Viewers/commit/f1a736d1934733a434cb87b2c284907a3122403f)) -# [3.6.0](https://github.com/OHIF/Viewers/compare/v3.6.0-beta.3...v3.6.0) (2023-06-07) + + +# [3.8.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.91...v3.8.0-beta.92) (2024-04-28) + + +### Bug Fixes + +* **bugs:** fix patient header for doc, track ball rotate resize observer and add segmentation button not being enabled on viewport data change ([#4068](https://github.com/OHIF/Viewers/issues/4068)) ([c09311d](https://github.com/OHIF/Viewers/commit/c09311d3b7df05fcd00a9f36a7233e9d7e5589d0)) -# [3.5.0](https://github.com/OHIF/Viewers/compare/v3.5.0-beta.1...v3.5.0) (2023-06-07) + + +# [3.8.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.90...v3.8.0-beta.91) (2024-04-25) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.89...v3.8.0-beta.90) (2024-04-22) + + +### Bug Fixes + +* **viewport-sync:** Enable re-sync image slices in a different position when needed ([#3984](https://github.com/OHIF/Viewers/issues/3984)) ([6ebd2cc](https://github.com/OHIF/Viewers/commit/6ebd2cc7cb70cd88fd01dc1e516077f27b201943)) + + + + + +# [3.8.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.88...v3.8.0-beta.89) (2024-04-22) + + +### Bug Fixes + +* **vewport:** Add missing blendmodes from cornerstonejs ([#4055](https://github.com/OHIF/Viewers/issues/4055)) ([3ec7e51](https://github.com/OHIF/Viewers/commit/3ec7e512169a07506388902acb5b2c118093fa50)) +* **viewport-webworker-segmentation:** Resolve issues with viewport detection, webworker termination, and segmentation panel layout change ([#4059](https://github.com/OHIF/Viewers/issues/4059)) ([52a0c59](https://github.com/OHIF/Viewers/commit/52a0c59294a4161fcca0a6708855549034849951)) + + + + + +# [3.8.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.87...v3.8.0-beta.88) (2024-04-22) + + +### Bug Fixes + +* **hp:** Fails to display any layouts in the layout selector if first layout has multiple stages ([#4058](https://github.com/OHIF/Viewers/issues/4058)) ([f0ed3fd](https://github.com/OHIF/Viewers/commit/f0ed3fd7b99b0e4e00b261ceb9888ba94726719c)) + + + + + +# [3.8.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.86...v3.8.0-beta.87) (2024-04-19) ### Features -* **app:** change ohif viewer to ohif app ([#3451](https://github.com/OHIF/Viewers/issues/3451)) ([16afa74](https://github.com/OHIF/Viewers/commit/16afa740b60b31037100444ef9311b80ffea2f67)) +* **tmtv-mode:** Add Brush tools and move SUV peak calculation to web worker ([#4053](https://github.com/OHIF/Viewers/issues/4053)) ([8192e34](https://github.com/OHIF/Viewers/commit/8192e348eca993fec331d4963efe88f9a730eceb)) + + + + + +# [3.8.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.85...v3.8.0-beta.86) (2024-04-19) + + +### Bug Fixes + +* **layouts:** and fix thumbnail in touch and update migration guide for 3.8 release ([#4052](https://github.com/OHIF/Viewers/issues/4052)) ([d250d04](https://github.com/OHIF/Viewers/commit/d250d04580883446fcb8d748b2a97c5c198922af)) + + + + + +# [3.8.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.84...v3.8.0-beta.85) (2024-04-18) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.83...v3.8.0-beta.84) (2024-04-18) + + +### Bug Fixes + +* **bugs:** and replace seriesInstanceUID and seriesInstanceUIDs URL with seriesInstanceUIDs ([#4049](https://github.com/OHIF/Viewers/issues/4049)) ([da7c1a5](https://github.com/OHIF/Viewers/commit/da7c1a5d8c54bfa1d3f97bbc500386bf76e7fd9d)) + + + + + +# [3.8.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.82...v3.8.0-beta.83) (2024-04-18) + + +### Bug Fixes + +* **bugs:** enhancements and bug fixes - final ([#4048](https://github.com/OHIF/Viewers/issues/4048)) ([170bb96](https://github.com/OHIF/Viewers/commit/170bb96983082c39b22b7352e0c54aacf3e73b02)) + + + + + +# [3.8.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.81...v3.8.0-beta.82) (2024-04-17) + + +### Bug Fixes + +* **bugs:** enhancements and bug fixes - more ([#4043](https://github.com/OHIF/Viewers/issues/4043)) ([3754c22](https://github.com/OHIF/Viewers/commit/3754c224b4dab28182adb0a41e37d890942144d8)) + + + + + +# [3.8.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.80...v3.8.0-beta.81) (2024-04-16) + + +### Bug Fixes + +* **viewport:** Reset viewport state and fix CINE looping, thumbnail resolution, and dynamic tool settings ([#4037](https://github.com/OHIF/Viewers/issues/4037)) ([f99a0bf](https://github.com/OHIF/Viewers/commit/f99a0bfb31434aa137bbb3ed1f9eef1dfcc09025)) + + + + + +# [3.8.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.79...v3.8.0-beta.80) (2024-04-16) + + +### Bug Fixes + +* **bugs:** enhancements and bug fixes ([#4036](https://github.com/OHIF/Viewers/issues/4036)) ([e80fc6f](https://github.com/OHIF/Viewers/commit/e80fc6f47708e1d6b1a1e1de438196a4b74ec637)) + + + + + +# [3.8.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.78...v3.8.0-beta.79) (2024-04-10) + + +### Features + +* **SM:** remove SM measurements from measurement panel ([#4022](https://github.com/OHIF/Viewers/issues/4022)) ([df49a65](https://github.com/OHIF/Viewers/commit/df49a653be61a93f6e9fb3663aabe9775c31fd13)) + + + + + +# [3.8.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.77...v3.8.0-beta.78) (2024-04-10) + + +### Bug Fixes + +* **general:** enhancements and bug fixes ([#4018](https://github.com/OHIF/Viewers/issues/4018)) ([2b83393](https://github.com/OHIF/Viewers/commit/2b83393f91cb16ea06821d79d14ff60f80c29c90)) + + + + + +# [3.8.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.76...v3.8.0-beta.77) (2024-04-10) + + +### Bug Fixes + +* **dicom-video:** Update get direct func for dicom json to use url if present and fix config argument ([#4017](https://github.com/OHIF/Viewers/issues/4017)) ([4f99244](https://github.com/OHIF/Viewers/commit/4f99244d864427d69be6f863cb7a6a78411adb12)) + + + + + +# [3.8.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.75...v3.8.0-beta.76) (2024-04-10) + + +### Bug Fixes + +* **MetaDataProvider:** Fix tag in GeneralImageModule ([#4000](https://github.com/OHIF/Viewers/issues/4000)) ([e9c30a1](https://github.com/OHIF/Viewers/commit/e9c30a108e2dd14a8b137b81e5b832cc167bc3d1)) + + + + + +# [3.8.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.74...v3.8.0-beta.75) (2024-04-10) + + +### Bug Fixes + +* Microscopy bulkdata and image retrieve ([#3894](https://github.com/OHIF/Viewers/issues/3894)) ([7fac49b](https://github.com/OHIF/Viewers/commit/7fac49b4492b4bd5e9ece8e2e2b0fa2faa840d7f)) + + + + + +# [3.8.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.73...v3.8.0-beta.74) (2024-04-10) + + +### Features + +* **4D:** Add 4D dynamic volume rendering and new pre-clinical 4d pt/ct mode ([#3664](https://github.com/OHIF/Viewers/issues/3664)) ([d57e8bc](https://github.com/OHIF/Viewers/commit/d57e8bc1571c6da4effaa492ee2d162c552365a2)) + + + + + +# [3.8.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.72...v3.8.0-beta.73) (2024-04-08) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.71...v3.8.0-beta.72) (2024-04-05) + + +### Bug Fixes + +* **cornerstone-dicom-sr:** Freehand SR hydration support ([#3996](https://github.com/OHIF/Viewers/issues/3996)) ([5645ac1](https://github.com/OHIF/Viewers/commit/5645ac1b271e1ed8c57f5d71100809362447267e)) + + + + + +# [3.8.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.70...v3.8.0-beta.71) (2024-04-05) + + +### Features + +* **advanced-roi-tools:** new tools and icon updates and overlay bug fixes ([#4014](https://github.com/OHIF/Viewers/issues/4014)) ([cea27d4](https://github.com/OHIF/Viewers/commit/cea27d438d1de2c1ec90cbaefdc2b31a1d9980a1)) + + + + + +# [3.8.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.69...v3.8.0-beta.70) (2024-04-05) + + +### Features + +* **measurement:** Add support measurement label autocompletion ([#3855](https://github.com/OHIF/Viewers/issues/3855)) ([56b1eae](https://github.com/OHIF/Viewers/commit/56b1eae6356a6534960df1196bdd1e95b0a9a470)) + + + + + +# [3.8.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.68...v3.8.0-beta.69) (2024-04-03) + + +### Bug Fixes + +* **presentation-state:** Iterate over map properly to restore the presentation state ([#4013](https://github.com/OHIF/Viewers/issues/4013)) ([fa38e6a](https://github.com/OHIF/Viewers/commit/fa38e6a07a259d8cb33277922884e722414ac548)) + + + + + +# [3.8.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.67...v3.8.0-beta.68) (2024-04-03) + + +### Features + +* **segmentation:** Enhanced segmentation panel design for TMTV ([#3988](https://github.com/OHIF/Viewers/issues/3988)) ([9f3235f](https://github.com/OHIF/Viewers/commit/9f3235ff096636aafa88d8a42859e8dc85d9036d)) + + + + + +# [3.8.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.66...v3.8.0-beta.67) (2024-04-02) + + +### Features + +* **ViewportActionMenu:** window level per viewport / new patient info / colorbars/ 3D presets and 3D volume rendering ([#3963](https://github.com/OHIF/Viewers/issues/3963)) ([b7f90e3](https://github.com/OHIF/Viewers/commit/b7f90e3951845396f99b69f0a74fc56b2ffeada1)) + + + + + +# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28) + + +### Bug Fixes + +* **new layout:** address black screen bugs ([#4008](https://github.com/OHIF/Viewers/issues/4008)) ([158a181](https://github.com/OHIF/Viewers/commit/158a1816703e0ad66cae08cb9bd1ffb93bbd8d43)) + + + + + +# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28) + + +### Features + +* **layout:** new layout selector with 3D volume rendering ([#3923](https://github.com/OHIF/Viewers/issues/3923)) ([617043f](https://github.com/OHIF/Viewers/commit/617043fe0da5de91fbea4ac33a27f1df16ae1ca6)) + + + + + +# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27) + + +### Features + +* **toolbar:** new Toolbar to enable reactive state synchronization ([#3983](https://github.com/OHIF/Viewers/issues/3983)) ([566b25a](https://github.com/OHIF/Viewers/commit/566b25a54425399096864bd263193646556011a5)) + + + + + +# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25) + + +### Features + +* **worklist:** new investigational use text ([#3999](https://github.com/OHIF/Viewers/issues/3999)) ([45b68e8](https://github.com/OHIF/Viewers/commit/45b68e841dcb9e28a2ea991c37ee7ac4a8c5b71e)) + + + + + +# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19) + + +### Features + +* **worklist:** New worklist buttons and tooltips ([#3989](https://github.com/OHIF/Viewers/issues/3989)) ([9bcd1ae](https://github.com/OHIF/Viewers/commit/9bcd1ae6f51d61786cc1e99624f396b56a47cd69)) + + + + + +# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18) + + +### Bug Fixes + +* **SR display:** and the token based navigation ([#3995](https://github.com/OHIF/Viewers/issues/3995)) ([feed230](https://github.com/OHIF/Viewers/commit/feed2304c124dc2facc7a7371ed9851548c223c5)) + + + + + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + + +### Features + +* **delete measurement:** icon for measurement table ([#3775](https://github.com/OHIF/Viewers/issues/3775)) ([f7fe91c](https://github.com/OHIF/Viewers/commit/f7fe91c5f6c4f05f3f3f5f640d3a119bd40a5870)) + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + + +### Bug Fixes + +* **cli:** mode creation template ([#3876](https://github.com/OHIF/Viewers/issues/3876)) ([#3981](https://github.com/OHIF/Viewers/issues/3981)) ([e485d68](https://github.com/OHIF/Viewers/commit/e485d68fd4619ce7187113cbe59e47f9523dbcc8)) + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + + +### Bug Fixes + +* **docs:** Minor typos in hpModule.md ([#3962](https://github.com/OHIF/Viewers/issues/3962)) ([4cdfdae](https://github.com/OHIF/Viewers/commit/4cdfdae8149166cf9dc91a55c0d7f2a224e55d8f)) + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + + +### Bug Fixes + +* **demo:** Deploy issue ([#3951](https://github.com/OHIF/Viewers/issues/3951)) ([21e8a2b](https://github.com/OHIF/Viewers/commit/21e8a2bd0b7cc72f90a31e472d285d761be15d30)) + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + + +### Features + +* **errorboundary:** format stack trace properly ([#3931](https://github.com/OHIF/Viewers/issues/3931)) ([0eac386](https://github.com/OHIF/Viewers/commit/0eac386a31a5d6965536360aa65a44769c1a5740)) + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + + +### Bug Fixes + +* 🐛 Sort merge results based on default data source (input) ([#3903](https://github.com/OHIF/Viewers/issues/3903)) ([5bba98e](https://github.com/OHIF/Viewers/commit/5bba98ed848bdf46b5ba4fc4708527cced3308b5)) + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + + +### Bug Fixes + +* catch errors in getPTImageIdInstanceMetadata ([#3897](https://github.com/OHIF/Viewers/issues/3897)) ([a47aeb8](https://github.com/OHIF/Viewers/commit/a47aeb8bd729dcb8d2cfc13b27a31b0dd88f11ad)) + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + + +### Bug Fixes + +* **viewport-sync:** remember synced viewports bw stack and volume and RENAME StackImageSync to ImageSliceSync ([#3849](https://github.com/OHIF/Viewers/issues/3849)) ([e4a116b](https://github.com/OHIF/Viewers/commit/e4a116b074fcb85c8cbcc9db44fdec565f3386db)) + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + + +### Bug Fixes + +* is same orientaiton ([#3905](https://github.com/OHIF/Viewers/issues/3905)) ([31b837f](https://github.com/OHIF/Viewers/commit/31b837fa90f631d4984482c6e952373fbb8bdbfc)) + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + + +### Bug Fixes + +* 🐛 Check merge key for merge data source ([#3901](https://github.com/OHIF/Viewers/issues/3901)) ([911d672](https://github.com/OHIF/Viewers/commit/911d67283536b2fe7930948f2819ea0ad66e2a32)) + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + + +### Bug Fixes + +* Update CS3D to fix second render ([#3892](https://github.com/OHIF/Viewers/issues/3892)) ([d00a86b](https://github.com/OHIF/Viewers/commit/d00a86b022742ea089d246d06cfd691f43b64412)) + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + + +### Features + +* **hp:** enable OHIF to run with partial metadata for large studies at the cost of less effective hanging protocol ([#3804](https://github.com/OHIF/Viewers/issues/3804)) ([0049f4c](https://github.com/OHIF/Viewers/commit/0049f4c0303f0b6ea995972326fc8784259f5a47)) + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + + +### Features + +* **transferSyntax:** prefer server transcoded transfer syntax for all images ([#3883](https://github.com/OHIF/Viewers/issues/3883)) ([1456a49](https://github.com/OHIF/Viewers/commit/1456a493d66c90c787b022256c9f2846afb115fc)) + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + + +### Bug Fixes + +* **segmentation:** upgrade cs3d to fix various segmentation bugs ([#3885](https://github.com/OHIF/Viewers/issues/3885)) ([b1efe40](https://github.com/OHIF/Viewers/commit/b1efe40aa146e4052cc47b3f774cabbb47a8d1a6)) + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + + +### Features + +* Add on mode init hook ([#3882](https://github.com/OHIF/Viewers/issues/3882)) ([f58725c](https://github.com/OHIF/Viewers/commit/f58725ce40685f7297181ef98d81bc28420c8291)) + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + + +### Features + +* **ui:** sidePanel expandedWidth ([#3728](https://github.com/OHIF/Viewers/issues/3728)) ([61bf22c](https://github.com/OHIF/Viewers/commit/61bf22c6f80e764bdf5c3b56bb0124a95aa0f793)) + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + + +### Features + +* improve disableEditing flag ([#3875](https://github.com/OHIF/Viewers/issues/3875)) ([2049c09](https://github.com/OHIF/Viewers/commit/2049c0936c86f819604c243d3dc7b3fe971b5b2c)) + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + + +### Bug Fixes + +* convert radian to degree value for mip rotation ([#3881](https://github.com/OHIF/Viewers/issues/3881)) ([bf846c9](https://github.com/OHIF/Viewers/commit/bf846c94c378f04b9f44dcd71be3f056dbcfe0b5)) +* PDF display request in v3 ([#3878](https://github.com/OHIF/Viewers/issues/3878)) ([9865030](https://github.com/OHIF/Viewers/commit/98650302c7575f0aea386e32cfc4112c378035e6)) + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + + +### Bug Fixes + +* colormap for stack viewports via HangingProtocol ([#3866](https://github.com/OHIF/Viewers/issues/3866)) ([e8858f3](https://github.com/OHIF/Viewers/commit/e8858f3eb55552f695af4a55980f9ae2e9af7291)) + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + + +### Bug Fixes + +* **icon-style:** Ensure consistent icon dimensions ([#3727](https://github.com/OHIF/Viewers/issues/3727)) ([6ca13c0](https://github.com/OHIF/Viewers/commit/6ca13c0a4cb5a95bbb52b0db902b5dbf72f8aa6e)) + + +### Features + +* **overlay:** add inline binary overlays ([#3852](https://github.com/OHIF/Viewers/issues/3852)) ([0177b62](https://github.com/OHIF/Viewers/commit/0177b625ba86760168bc4db58c8a109aa9ee83cb)) + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + + +### Features + +* **customizationService:** Enable saving and loading of private tags in SRs ([#3842](https://github.com/OHIF/Viewers/issues/3842)) ([e1f55e6](https://github.com/OHIF/Viewers/commit/e1f55e65f2d2a34136ad5d0b1ada77d337a0ea23)) + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + + +### Bug Fixes + +* address and improve system vulnerabilities ([#3851](https://github.com/OHIF/Viewers/issues/3851)) ([805c532](https://github.com/OHIF/Viewers/commit/805c53270f243ec61f142a3ffa0af500021cd5ec)) + + +### Features + +* **config:** Add activateViewportBeforeInteraction parameter for viewport interaction customization ([#3847](https://github.com/OHIF/Viewers/issues/3847)) ([f707b4e](https://github.com/OHIF/Viewers/commit/f707b4ebc996f379cd30337badc06b07e6e35ac5)) +* **i18n:** enhanced i18n support ([#3761](https://github.com/OHIF/Viewers/issues/3761)) ([d14a8f0](https://github.com/OHIF/Viewers/commit/d14a8f0199db95cd9e85866a011b64d6bf830d57)) + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + + +### Features + +* **HP:** Added new 3D hanging protocols to be used in the new layout selector ([#3844](https://github.com/OHIF/Viewers/issues/3844)) ([59576d6](https://github.com/OHIF/Viewers/commit/59576d695d4d26601d35c43f73d602f0b12d72bf)) + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + + +### Bug Fixes + +* **auth:** fix the issue with oauth at a non root path ([#3840](https://github.com/OHIF/Viewers/issues/3840)) ([6651008](https://github.com/OHIF/Viewers/commit/6651008fbb35dabd5991c7f61128e6ef324012df)) + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + + +### Bug Fixes + +* **SM:** drag and drop is now fixed for SM ([#3813](https://github.com/OHIF/Viewers/issues/3813)) ([f1a6764](https://github.com/OHIF/Viewers/commit/f1a67647aed635437b188cea7cf5d5a8fb974bbe)) + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + + +### Bug Fixes + +* **cine:** Set cine disabled on mode exit. ([#3812](https://github.com/OHIF/Viewers/issues/3812)) ([924affa](https://github.com/OHIF/Viewers/commit/924affa7b5d420c2f91522a075cecbb3c78e8f52)) + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + + +### Bug Fixes + +* Update the CS3D packages to add the most recent HTJ2K TSUIDS ([#3806](https://github.com/OHIF/Viewers/issues/3806)) ([9d1884d](https://github.com/OHIF/Viewers/commit/9d1884d7d8b6b2a1cdc26965a96995838aa72682)) + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + + +### Features + +* Merge Data Source ([#3788](https://github.com/OHIF/Viewers/issues/3788)) ([c4ff2c2](https://github.com/OHIF/Viewers/commit/c4ff2c2f09546ce8b72eab9c5e7beed611e3cab0)) + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + + +### Features + +* **events:** broadcast series summary metadata ([#3798](https://github.com/OHIF/Viewers/issues/3798)) ([404b0a5](https://github.com/OHIF/Viewers/commit/404b0a5d535182d1ae44e33f7232db500a7b2c16)) + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + + +### Bug Fixes + +* **DICOM Overlay:** The overlay data wasn't being refreshed on change ([#3793](https://github.com/OHIF/Viewers/issues/3793)) ([00e7519](https://github.com/OHIF/Viewers/commit/00e751933ac6d611a34773fa69594243f1b99082)) + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + + +### Bug Fixes + +* **metadata:** to handle cornerstone3D update for htj2k ([#3783](https://github.com/OHIF/Viewers/issues/3783)) ([8c8924a](https://github.com/OHIF/Viewers/commit/8c8924af373d906773f5db20defe38628cacd4a0)) + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + + +### Features + +* **docs:** Added various training videos to support the OHIF CLI tools ([#3794](https://github.com/OHIF/Viewers/issues/3794)) ([d83beb7](https://github.com/OHIF/Viewers/commit/d83beb7c62c1d5be19c54e08d23883f112147fe1)) + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + + +### Features + +* **url:** Add SeriesInstanceUIDs wado query param ([#3746](https://github.com/OHIF/Viewers/issues/3746)) ([b694228](https://github.com/OHIF/Viewers/commit/b694228dd535e4b97cb86a1dc085b6e8716bdaf3)) + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + + +### Bug Fixes + +* 🐛 Run error handler for failed image requests ([#3773](https://github.com/OHIF/Viewers/issues/3773)) ([3234014](https://github.com/OHIF/Viewers/commit/323401418e7ccab74655ba02f990bbe0ed4e523b)) + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + + +### Bug Fixes + +* **overlay:** Overlays aren't shown on undefined origin ([#3781](https://github.com/OHIF/Viewers/issues/3781)) ([fd1251f](https://github.com/OHIF/Viewers/commit/fd1251f751d8147b8a78c7f4d81c67ba69769afa)) + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + + +### Features + +* **dicomJSON:** Add Loading Other Display Sets and JSON Metadata Generation script ([#3777](https://github.com/OHIF/Viewers/issues/3777)) ([43b1c17](https://github.com/OHIF/Viewers/commit/43b1c17209502e4876ad59bae09ed9442eda8024)) + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + + +### Bug Fixes + +* **path:** upgrade docusaurus for security ([#3780](https://github.com/OHIF/Viewers/issues/3780)) ([8bbcd0e](https://github.com/OHIF/Viewers/commit/8bbcd0e692e25917c1b6dd94a39fac834c812fca)) + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + + +### Bug Fixes + +* **arrow:** ArrowAnnotate text key cause validation error ([#3771](https://github.com/OHIF/Viewers/issues/3771)) ([8af1046](https://github.com/OHIF/Viewers/commit/8af10468035f1f59e0a21e579d50ad63c8cbf7ad)) + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + + +### Features + +* add VolumeViewport rotation ([#3776](https://github.com/OHIF/Viewers/issues/3776)) ([442f99d](https://github.com/OHIF/Viewers/commit/442f99d5eb2ceece7def20e14da59af1dd7d8442)) + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + + +### Features + +* **hp callback:** Add viewport ready callback ([#3772](https://github.com/OHIF/Viewers/issues/3772)) ([bf252bc](https://github.com/OHIF/Viewers/commit/bf252bcec2aae3a00479fdcb732110b344bcf2c0)) + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + + +### Bug Fixes + +* **thumbnail:** Avoid multiple promise creations for thumbnails ([#3756](https://github.com/OHIF/Viewers/issues/3756)) ([b23eeff](https://github.com/OHIF/Viewers/commit/b23eeff93745769e67e60c33d75293d6242c5ec9)) + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + + +### Features + +* **i18n:** enhanced i18n support ([#3730](https://github.com/OHIF/Viewers/issues/3730)) ([330e11c](https://github.com/OHIF/Viewers/commit/330e11c7ff0151e1096e19b8ffdae7d64cae280e)) + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + + +### Bug Fixes + +* **measurement service:** Implemented correct check of schema keys in _isValidMeasurment. ([#3750](https://github.com/OHIF/Viewers/issues/3750)) ([db39585](https://github.com/OHIF/Viewers/commit/db395852b6fc6cd5c265a9282e5eee5bd6f951b7)) + + +### Features + +* **filters:** save worklist query filters to session storage so that they persist between navigation to the viewer and back ([#3749](https://github.com/OHIF/Viewers/issues/3749)) ([2a15ef0](https://github.com/OHIF/Viewers/commit/2a15ef0e44b7b4d8bbf5cb9363db6e523201c681)) + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + + +### Bug Fixes + +* **toolbar:** allow customizable toolbar for active viewport and allow active tool to be deactivated via a click ([#3608](https://github.com/OHIF/Viewers/issues/3608)) ([dd6d976](https://github.com/OHIF/Viewers/commit/dd6d9768bbca1d3cc472e8c1e6d85822500b96ef)) + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + + +### Bug Fixes + +* **sr:** dcm4chee requires the patient name for an SR to match what is in the original study ([#3739](https://github.com/OHIF/Viewers/issues/3739)) ([d98439f](https://github.com/OHIF/Viewers/commit/d98439fe7f3825076dbc87b664a1d1480ff414d3)) + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + + +### Bug Fixes + +* **recipes:** package.json script orthanc:up docker-compose path ([#3741](https://github.com/OHIF/Viewers/issues/3741)) ([49514ae](https://github.com/OHIF/Viewers/commit/49514aedfe0498b5bd505193106a9745a6a5b5e6)) + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + + +### Bug Fixes + +* **cine:** Use the frame rate specified in DICOM and optionally auto play cine ([#3735](https://github.com/OHIF/Viewers/issues/3735)) ([d9258ec](https://github.com/OHIF/Viewers/commit/d9258eca70587cf4dc18be4e56c79b16bae73d6d)) + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + + +### Bug Fixes + +* **calibration:** No calibration popup caused by perhaps an unused code optimization for production builds ([#3736](https://github.com/OHIF/Viewers/issues/3736)) ([93d798d](https://github.com/OHIF/Viewers/commit/93d798db99c0dee53ef73c376f8a74ac3049cf3f)) + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) + +**Note:** Version bump only for package ohif-monorepo-root diff --git a/README.md b/README.md index 5befea75c..c505c9581 100644 --- a/README.md +++ b/README.md @@ -41,12 +41,15 @@ provided by the Open Health Imaging Foundation (OHIF | | | | | :-: | :--- | :--- | -| Measurement tracking | Measurement Tracking | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5) | -| Segmentations | Labelmap Segmentations | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046) | -| Hanging Protocols | Fusion and Custom Hanging protocols | [Demo](https://viewer.ohif.org/tmtv?StudyInstanceUIDs=1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463) | -| Microscopy | Slide Microscopy | [Demo](https://viewer.ohif.org/microscopy?StudyInstanceUIDs=2.25.275741864483510678566144889372061815320) | -| Volume Rendering | Volume Rendering | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5&hangingprotocolId=mprAnd3DVolumeViewport) | - +| Measurement tracking | Measurement Tracking | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5) | +| Segmentations | Labelmap Segmentations | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046) | +| Hanging Protocols | Fusion and Custom Hanging protocols | [Demo](https://viewer.ohif.org/tmtv?StudyInstanceUIDs=1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463) | +| Volume Rendering | Volume Rendering | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5&hangingprotocolId=mprAnd3DVolumeViewport) | +| PDF | PDF | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=2.25.317377619501274872606137091638706705333) | +| RTSTRUCT | RT STRUCT | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.5962.99.1.2968617883.1314880426.1493322302363.3.0) | +| 4D | 4D | [Demo](https://viewer.ohif.org/dynamic-volume?StudyInstanceUIDs=2.25.232704420736447710317909004159492840763) | +| VIDEO | Video | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=2.25.96975534054447904995905761963464388233) | +| microscopy | Slide Microscopy | [Demo](https://viewer.ohif.org/microscopy?StudyInstanceUIDs=2.25.141277760791347900862109212450152067508) | ## About diff --git a/babel.config.js b/babel.config.js index b55cbfdfa..9fbd80463 100644 --- a/babel.config.js +++ b/babel.config.js @@ -26,6 +26,7 @@ module.exports = { '@babel/plugin-transform-typescript', ['@babel/plugin-proposal-private-property-in-object', { loose: true }], ['@babel/plugin-proposal-private-methods', { loose: true }], + '@babel/plugin-transform-class-static-block', ], env: { test: { @@ -45,6 +46,7 @@ module.exports = { '@babel/plugin-proposal-object-rest-spread', '@babel/plugin-syntax-dynamic-import', '@babel/plugin-transform-regenerator', + '@babel/transform-destructuring', '@babel/plugin-transform-runtime', '@babel/plugin-transform-typescript', ], diff --git a/commit.txt b/commit.txt index 621364cf7..64a6129b7 100644 --- a/commit.txt +++ b/commit.txt @@ -1 +1 @@ -5ddf8a16027255d28dc01c1740099cf85bbcf458 \ No newline at end of file +f1a736d1934733a434cb87b2c284907a3122403f \ No newline at end of file diff --git a/extensions/cornerstone-dicom-rt/CHANGELOG.md b/extensions/cornerstone-dicom-rt/CHANGELOG.md index ebc0a858c..5787d6a97 100644 --- a/extensions/cornerstone-dicom-rt/CHANGELOG.md +++ b/extensions/cornerstone-dicom-rt/CHANGELOG.md @@ -3,7 +3,769 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -# [3.7.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.7.0) (2023-10-11) +# [3.8.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.92...v3.8.0-beta.93) (2024-04-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.91...v3.8.0-beta.92) (2024-04-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.90...v3.8.0-beta.91) (2024-04-25) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.89...v3.8.0-beta.90) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.88...v3.8.0-beta.89) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.87...v3.8.0-beta.88) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.86...v3.8.0-beta.87) (2024-04-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.85...v3.8.0-beta.86) (2024-04-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.84...v3.8.0-beta.85) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.83...v3.8.0-beta.84) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.82...v3.8.0-beta.83) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.81...v3.8.0-beta.82) (2024-04-17) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.80...v3.8.0-beta.81) (2024-04-16) + + +### Bug Fixes + +* **viewport:** Reset viewport state and fix CINE looping, thumbnail resolution, and dynamic tool settings ([#4037](https://github.com/OHIF/Viewers/issues/4037)) ([f99a0bf](https://github.com/OHIF/Viewers/commit/f99a0bfb31434aa137bbb3ed1f9eef1dfcc09025)) + + + + + +# [3.8.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.79...v3.8.0-beta.80) (2024-04-16) + + +### Bug Fixes + +* **bugs:** enhancements and bug fixes ([#4036](https://github.com/OHIF/Viewers/issues/4036)) ([e80fc6f](https://github.com/OHIF/Viewers/commit/e80fc6f47708e1d6b1a1e1de438196a4b74ec637)) + + + + + +# [3.8.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.78...v3.8.0-beta.79) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.77...v3.8.0-beta.78) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.76...v3.8.0-beta.77) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.75...v3.8.0-beta.76) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.74...v3.8.0-beta.75) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.73...v3.8.0-beta.74) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.72...v3.8.0-beta.73) (2024-04-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.71...v3.8.0-beta.72) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.70...v3.8.0-beta.71) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.69...v3.8.0-beta.70) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.68...v3.8.0-beta.69) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.67...v3.8.0-beta.68) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.66...v3.8.0-beta.67) (2024-04-02) + + +### Features + +* **ViewportActionMenu:** window level per viewport / new patient info / colorbars/ 3D presets and 3D volume rendering ([#3963](https://github.com/OHIF/Viewers/issues/3963)) ([b7f90e3](https://github.com/OHIF/Viewers/commit/b7f90e3951845396f99b69f0a74fc56b2ffeada1)) + + + + + +# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + + +### Bug Fixes + +* **viewport-sync:** remember synced viewports bw stack and volume and RENAME StackImageSync to ImageSliceSync ([#3849](https://github.com/OHIF/Viewers/issues/3849)) ([e4a116b](https://github.com/OHIF/Viewers/commit/e4a116b074fcb85c8cbcc9db44fdec565f3386db)) + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + + +### Bug Fixes + +* **segmentation:** upgrade cs3d to fix various segmentation bugs ([#3885](https://github.com/OHIF/Viewers/issues/3885)) ([b1efe40](https://github.com/OHIF/Viewers/commit/b1efe40aa146e4052cc47b3f774cabbb47a8d1a6)) + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) **Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt diff --git a/extensions/cornerstone-dicom-rt/package.json b/extensions/cornerstone-dicom-rt/package.json index a5c62e67a..898882f75 100644 --- a/extensions/cornerstone-dicom-rt/package.json +++ b/extensions/cornerstone-dicom-rt/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone-dicom-rt", - "version": "3.7.0", + "version": "3.8.0-beta.93", "description": "DICOM RT read workflow", "author": "OHIF", "license": "MIT", @@ -24,6 +24,8 @@ "yarn": ">=1.18.0" }, "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "dev:dicom-seg": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", @@ -31,10 +33,10 @@ "start": "yarn run dev" }, "peerDependencies": { - "@ohif/core": "3.7.0", - "@ohif/extension-cornerstone": "3.7.0", - "@ohif/extension-default": "3.7.0", - "@ohif/i18n": "3.7.0", + "@ohif/core": "3.8.0-beta.93", + "@ohif/extension-cornerstone": "3.8.0-beta.93", + "@ohif/extension-default": "3.8.0-beta.93", + "@ohif/i18n": "3.8.0-beta.93", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts b/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts index 91492cbfb..2baa0305b 100644 --- a/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts +++ b/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts @@ -66,6 +66,11 @@ function _askHydrate(uiViewportDialogService, viewportId) { uiViewportDialogService.hide(); resolve(RESPONSE.CANCEL); }, + onKeyPress: event => { + if (event.key === 'Enter') { + onSubmit(RESPONSE.HYDRATE_SEG); + } + }, }); }); } diff --git a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx index e9c7450c6..1b2b42b22 100644 --- a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx +++ b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx @@ -1,13 +1,11 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import PropTypes from 'prop-types'; -import OHIF, { utils } from '@ohif/core'; -import { ViewportActionBar, useViewportGrid, LoadingIndicatorTotalPercent } from '@ohif/ui'; +import { useViewportGrid, LoadingIndicatorTotalPercent, ViewportActionArrows } from '@ohif/ui'; import promptHydrateRT from '../utils/promptHydrateRT'; import _getStatusComponent from './_getStatusComponent'; import createRTToolGroupAndAddTools from '../utils/initRTToolGroup'; -const { formatDate } = utils; const RT_TOOLGROUP_BASE_NAME = 'RTToolGroup'; function OHIFCornerstoneRTViewport(props) { @@ -15,7 +13,6 @@ function OHIFCornerstoneRTViewport(props) { children, displaySets, viewportOptions, - viewportLabel, servicesManager, extensionManager, commandsManager, @@ -27,6 +24,7 @@ function OHIFCornerstoneRTViewport(props) { segmentationService, uiNotificationService, customizationService, + viewportActionCornersService, } = servicesManager.services; const viewportId = viewportOptions.viewportId; @@ -118,7 +116,10 @@ function OHIFCornerstoneRTViewport(props) { orientation: viewportOptions.orientation, viewportId: viewportOptions.viewportId, }} - onElementEnabled={onElementEnabled} + onElementEnabled={evt => { + props.onElementEnabled?.(evt); + onElementEnabled(evt); + }} onElementDisabled={onElementDisabled} > ); @@ -126,7 +127,6 @@ function OHIFCornerstoneRTViewport(props) { const onSegmentChange = useCallback( direction => { - direction = direction === 'left' ? -1 : 1; const segmentationId = rtDisplaySet.displaySetInstanceUID; const segmentation = segmentationService.getSegmentation(segmentationId); @@ -168,6 +168,8 @@ function OHIFCornerstoneRTViewport(props) { }, [servicesManager, viewportId, rtDisplaySet, rtIsLoading]); useEffect(() => { + // I'm not sure what is this, since in RT we support Overlapping segments + // via contours const { unsubscribe } = segmentationService.subscribe( segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE, evt => { @@ -280,20 +282,7 @@ function OHIFCornerstoneRTViewport(props) { }); } - const { - PatientID, - PatientName, - PatientSex, - PatientAge, - SliceThickness, - ManufacturerModelName, - StudyDate, - SeriesDescription, - SpacingBetweenSlices, - SeriesNumber, - } = referencedDisplaySetRef.current.metadata; - - const onStatusClick = async () => { + const onStatusClick = useCallback(async () => { // Before hydrating a RT and make it added to all viewports in the grid // that share the same frameOfReferenceUID, we need to store the viewport grid // presentation state, so that we can restore it after hydrating the RT. This is @@ -307,41 +296,47 @@ function OHIFCornerstoneRTViewport(props) { }); setIsHydrated(isHydrated); - }; + }, [hydrateRTDisplaySet, rtDisplaySet, storePresentationState, viewportId]); + + useEffect(() => { + viewportActionCornersService.setComponents([ + { + viewportId, + id: 'viewportStatusComponent', + component: _getStatusComponent({ + isHydrated, + onStatusClick, + }), + indexPriority: -100, + location: viewportActionCornersService.LOCATIONS.topLeft, + }, + { + viewportId, + id: 'viewportActionArrowsComponent', + component: ( + + ), + indexPriority: 0, + location: viewportActionCornersService.LOCATIONS.topRight, + }, + ]); + }, [ + activeViewportId, + isHydrated, + onSegmentChange, + onStatusClick, + viewportActionCornersService, + viewportId, + ]); return ( <> - { - evt.stopPropagation(); - evt.preventDefault(); - }} - onArrowsClick={onSegmentChange} - getStatusComponent={() => { - return _getStatusComponent({ - isHydrated, - onStatusClick, - }); - }} - studyData={{ - label: viewportLabel, - useAltStyling: true, - studyDate: formatDate(StudyDate), - currentSeries: SeriesNumber, - seriesDescription: `RT Viewport ${SeriesDescription}`, - patientInformation: { - patientName: PatientName ? OHIF.utils.formatPN(PatientName.Alphabetic) : '', - patientSex: PatientSex || '', - patientAge: PatientAge || '', - MRN: PatientID || '', - thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '', - spacing: - SpacingBetweenSlices !== undefined ? `${SpacingBetweenSlices.toFixed(2)}mm` : '', - scanner: ManufacturerModelName || '', - }, - }} - /> -
{rtIsLoading && ( ; @@ -26,23 +23,28 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) { ToolTipMessage = () =>
Click LOAD to load RTSTRUCT.
; } - const StatusArea = () => ( -
-
- - RTSTRUCT -
- {!isHydrated && ( -
- {loadStr} + const StatusArea = () => { + const { t } = useTranslation('Common'); + const loadStr = t('LOAD'); + + return ( +
+
+ + RTSTRUCT
- )} -
- ); + {!isHydrated && ( +
+ {loadStr} +
+ )} +
+ ); + }; return ( <> diff --git a/extensions/cornerstone-dicom-seg/CHANGELOG.md b/extensions/cornerstone-dicom-seg/CHANGELOG.md index 6cbff3409..76a12840f 100644 --- a/extensions/cornerstone-dicom-seg/CHANGELOG.md +++ b/extensions/cornerstone-dicom-seg/CHANGELOG.md @@ -3,7 +3,820 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -# [3.7.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.7.0) (2023-10-11) +# [3.8.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.92...v3.8.0-beta.93) (2024-04-29) + + +### Bug Fixes + +* **toolbox:** Preserve user-specified tool state and streamline command execution ([#4063](https://github.com/OHIF/Viewers/issues/4063)) ([f1a736d](https://github.com/OHIF/Viewers/commit/f1a736d1934733a434cb87b2c284907a3122403f)) + + + + + +# [3.8.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.91...v3.8.0-beta.92) (2024-04-28) + + +### Bug Fixes + +* **bugs:** fix patient header for doc, track ball rotate resize observer and add segmentation button not being enabled on viewport data change ([#4068](https://github.com/OHIF/Viewers/issues/4068)) ([c09311d](https://github.com/OHIF/Viewers/commit/c09311d3b7df05fcd00a9f36a7233e9d7e5589d0)) + + + + + +# [3.8.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.90...v3.8.0-beta.91) (2024-04-25) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.89...v3.8.0-beta.90) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.88...v3.8.0-beta.89) (2024-04-22) + + +### Bug Fixes + +* **viewport-webworker-segmentation:** Resolve issues with viewport detection, webworker termination, and segmentation panel layout change ([#4059](https://github.com/OHIF/Viewers/issues/4059)) ([52a0c59](https://github.com/OHIF/Viewers/commit/52a0c59294a4161fcca0a6708855549034849951)) + + + + + +# [3.8.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.87...v3.8.0-beta.88) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.86...v3.8.0-beta.87) (2024-04-19) + + +### Features + +* **tmtv-mode:** Add Brush tools and move SUV peak calculation to web worker ([#4053](https://github.com/OHIF/Viewers/issues/4053)) ([8192e34](https://github.com/OHIF/Viewers/commit/8192e348eca993fec331d4963efe88f9a730eceb)) + + + + + +# [3.8.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.85...v3.8.0-beta.86) (2024-04-19) + + +### Bug Fixes + +* **layouts:** and fix thumbnail in touch and update migration guide for 3.8 release ([#4052](https://github.com/OHIF/Viewers/issues/4052)) ([d250d04](https://github.com/OHIF/Viewers/commit/d250d04580883446fcb8d748b2a97c5c198922af)) + + + + + +# [3.8.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.84...v3.8.0-beta.85) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.83...v3.8.0-beta.84) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.82...v3.8.0-beta.83) (2024-04-18) + + +### Bug Fixes + +* **bugs:** enhancements and bug fixes - final ([#4048](https://github.com/OHIF/Viewers/issues/4048)) ([170bb96](https://github.com/OHIF/Viewers/commit/170bb96983082c39b22b7352e0c54aacf3e73b02)) + + + + + +# [3.8.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.81...v3.8.0-beta.82) (2024-04-17) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.80...v3.8.0-beta.81) (2024-04-16) + + +### Bug Fixes + +* **viewport:** Reset viewport state and fix CINE looping, thumbnail resolution, and dynamic tool settings ([#4037](https://github.com/OHIF/Viewers/issues/4037)) ([f99a0bf](https://github.com/OHIF/Viewers/commit/f99a0bfb31434aa137bbb3ed1f9eef1dfcc09025)) + + + + + +# [3.8.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.79...v3.8.0-beta.80) (2024-04-16) + + +### Bug Fixes + +* **bugs:** enhancements and bug fixes ([#4036](https://github.com/OHIF/Viewers/issues/4036)) ([e80fc6f](https://github.com/OHIF/Viewers/commit/e80fc6f47708e1d6b1a1e1de438196a4b74ec637)) + + + + + +# [3.8.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.78...v3.8.0-beta.79) (2024-04-10) + + +### Features + +* **SM:** remove SM measurements from measurement panel ([#4022](https://github.com/OHIF/Viewers/issues/4022)) ([df49a65](https://github.com/OHIF/Viewers/commit/df49a653be61a93f6e9fb3663aabe9775c31fd13)) + + + + + +# [3.8.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.77...v3.8.0-beta.78) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.76...v3.8.0-beta.77) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.75...v3.8.0-beta.76) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.74...v3.8.0-beta.75) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.73...v3.8.0-beta.74) (2024-04-10) + + +### Features + +* **4D:** Add 4D dynamic volume rendering and new pre-clinical 4d pt/ct mode ([#3664](https://github.com/OHIF/Viewers/issues/3664)) ([d57e8bc](https://github.com/OHIF/Viewers/commit/d57e8bc1571c6da4effaa492ee2d162c552365a2)) + + + + + +# [3.8.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.72...v3.8.0-beta.73) (2024-04-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.71...v3.8.0-beta.72) (2024-04-05) + + +### Bug Fixes + +* **cornerstone-dicom-sr:** Freehand SR hydration support ([#3996](https://github.com/OHIF/Viewers/issues/3996)) ([5645ac1](https://github.com/OHIF/Viewers/commit/5645ac1b271e1ed8c57f5d71100809362447267e)) + + + + + +# [3.8.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.70...v3.8.0-beta.71) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.69...v3.8.0-beta.70) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.68...v3.8.0-beta.69) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.67...v3.8.0-beta.68) (2024-04-03) + + +### Features + +* **segmentation:** Enhanced segmentation panel design for TMTV ([#3988](https://github.com/OHIF/Viewers/issues/3988)) ([9f3235f](https://github.com/OHIF/Viewers/commit/9f3235ff096636aafa88d8a42859e8dc85d9036d)) + + + + + +# [3.8.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.66...v3.8.0-beta.67) (2024-04-02) + + +### Features + +* **ViewportActionMenu:** window level per viewport / new patient info / colorbars/ 3D presets and 3D volume rendering ([#3963](https://github.com/OHIF/Viewers/issues/3963)) ([b7f90e3](https://github.com/OHIF/Viewers/commit/b7f90e3951845396f99b69f0a74fc56b2ffeada1)) + + + + + +# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28) + + +### Bug Fixes + +* **new layout:** address black screen bugs ([#4008](https://github.com/OHIF/Viewers/issues/4008)) ([158a181](https://github.com/OHIF/Viewers/commit/158a1816703e0ad66cae08cb9bd1ffb93bbd8d43)) + + + + + +# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28) + + +### Features + +* **layout:** new layout selector with 3D volume rendering ([#3923](https://github.com/OHIF/Viewers/issues/3923)) ([617043f](https://github.com/OHIF/Viewers/commit/617043fe0da5de91fbea4ac33a27f1df16ae1ca6)) + + + + + +# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27) + + +### Features + +* **toolbar:** new Toolbar to enable reactive state synchronization ([#3983](https://github.com/OHIF/Viewers/issues/3983)) ([566b25a](https://github.com/OHIF/Viewers/commit/566b25a54425399096864bd263193646556011a5)) + + + + + +# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18) + + +### Bug Fixes + +* **SR display:** and the token based navigation ([#3995](https://github.com/OHIF/Viewers/issues/3995)) ([feed230](https://github.com/OHIF/Viewers/commit/feed2304c124dc2facc7a7371ed9851548c223c5)) + + + + + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + + +### Bug Fixes + +* **demo:** Deploy issue ([#3951](https://github.com/OHIF/Viewers/issues/3951)) ([21e8a2b](https://github.com/OHIF/Viewers/commit/21e8a2bd0b7cc72f90a31e472d285d761be15d30)) + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + + +### Bug Fixes + +* Update CS3D to fix second render ([#3892](https://github.com/OHIF/Viewers/issues/3892)) ([d00a86b](https://github.com/OHIF/Viewers/commit/d00a86b022742ea089d246d06cfd691f43b64412)) + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + + +### Bug Fixes + +* **segmentation:** upgrade cs3d to fix various segmentation bugs ([#3885](https://github.com/OHIF/Viewers/issues/3885)) ([b1efe40](https://github.com/OHIF/Viewers/commit/b1efe40aa146e4052cc47b3f774cabbb47a8d1a6)) + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + + +### Bug Fixes + +* **auth:** fix the issue with oauth at a non root path ([#3840](https://github.com/OHIF/Viewers/issues/3840)) ([6651008](https://github.com/OHIF/Viewers/commit/6651008fbb35dabd5991c7f61128e6ef324012df)) + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + + +### Bug Fixes + +* Update the CS3D packages to add the most recent HTJ2K TSUIDS ([#3806](https://github.com/OHIF/Viewers/issues/3806)) ([9d1884d](https://github.com/OHIF/Viewers/commit/9d1884d7d8b6b2a1cdc26965a96995838aa72682)) + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) **Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg diff --git a/extensions/cornerstone-dicom-seg/package.json b/extensions/cornerstone-dicom-seg/package.json index ff59b268c..c129d681e 100644 --- a/extensions/cornerstone-dicom-seg/package.json +++ b/extensions/cornerstone-dicom-seg/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone-dicom-seg", - "version": "3.7.0", + "version": "3.8.0-beta.93", "description": "DICOM SEG read workflow", "author": "OHIF", "license": "MIT", @@ -24,6 +24,8 @@ "yarn": ">=1.18.0" }, "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "dev:dicom-seg": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", @@ -31,10 +33,10 @@ "start": "yarn run dev" }, "peerDependencies": { - "@ohif/core": "3.7.0", - "@ohif/extension-cornerstone": "3.7.0", - "@ohif/extension-default": "3.7.0", - "@ohif/i18n": "3.7.0", + "@ohif/core": "3.8.0-beta.93", + "@ohif/extension-cornerstone": "3.8.0-beta.93", + "@ohif/extension-default": "3.8.0-beta.93", + "@ohif/i18n": "3.8.0-beta.93", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -44,9 +46,9 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^1.20.3", - "@cornerstonejs/tools": "^1.20.3", - "@kitware/vtk.js": "27.3.1", + "@cornerstonejs/adapters": "^1.70.14", + "@cornerstonejs/core": "^1.70.14", + "@kitware/vtk.js": "30.4.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 1926ee9f1..4a770d7e1 100644 --- a/extensions/cornerstone-dicom-seg/src/commandsModule.ts +++ b/extensions/cornerstone-dicom-seg/src/commandsModule.ts @@ -5,6 +5,7 @@ import { cache, metaData } from '@cornerstonejs/core'; import { segmentation as cornerstoneToolsSegmentation, Enums as cornerstoneToolsEnums, + utilities, } from '@cornerstonejs/tools'; import { adaptersRT, helpers, adaptersSEG } from '@cornerstonejs/adapters'; import { classes, DicomMetadataStore } from '@ohif/core'; @@ -18,6 +19,7 @@ import { getUpdatedViewportsForSegmentation, getTargetViewport, } from './utils/hydrationUtils'; +const { segmentation: segmentationUtils } = utilities; const { datasetToBlob } = dcmjs.data; @@ -45,6 +47,7 @@ const commandsModule = ({ uiDialogService, displaySetService, viewportGridService, + toolGroupService, } = (servicesManager as ServicesManager).services; const actions = { @@ -203,6 +206,9 @@ const commandsModule = ({ loadSegmentationDisplaySetsForViewport: async ({ viewportId, displaySets }) => { // Todo: handle adding more than one segmentation const displaySet = displaySets[0]; + const referencedDisplaySet = displaySetService.getDisplaySetByUID( + displaySet.referencedDisplaySetInstanceUID + ); updateViewportsForSegmentationRendering({ viewportId, @@ -218,7 +224,8 @@ const commandsModule = ({ const boundFn = segmentationService[serviceFunction].bind(segmentationService); const segmentationId = await boundFn(segDisplaySet, null, suppressEvents); - + const segmentation = segmentationService.getSegmentation(segmentationId); + segmentation.description = `S${referencedDisplaySet.SeriesNumber}: ${referencedDisplaySet.SeriesDescription}`; return segmentationId; }, }); @@ -397,6 +404,36 @@ const commandsModule = ({ console.warn(e); } }, + setBrushSize: ({ value, toolNames }) => { + const brushSize = Number(value); + + toolGroupService.getToolGroupIds()?.forEach(toolGroupId => { + if (toolNames?.length === 0) { + segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize); + } else { + toolNames?.forEach(toolName => { + segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize, toolName); + }); + } + }); + }, + setThresholdRange: ({ + value, + toolNames = ['ThresholdCircularBrush', 'ThresholdSphereBrush'], + }) => { + toolGroupService.getToolGroupIds()?.forEach(toolGroupId => { + const toolGroup = toolGroupService.getToolGroup(toolGroupId); + toolNames?.forEach(toolName => { + toolGroup.setToolConfiguration(toolName, { + strategySpecificConfiguration: { + THRESHOLD: { + threshold: value, + }, + }, + }); + }); + }); + }, }; const definitions = { @@ -424,11 +461,18 @@ const commandsModule = ({ downloadRTSS: { commandFn: actions.downloadRTSS, }, + setBrushSize: { + commandFn: actions.setBrushSize, + }, + setThresholdRange: { + commandFn: actions.setThresholdRange, + }, }; return { actions, definitions, + defaultContext: 'SEGMENTATION', }; }; diff --git a/extensions/cornerstone-dicom-seg/src/getPanelModule.tsx b/extensions/cornerstone-dicom-seg/src/getPanelModule.tsx index e626c87d7..a86144d4f 100644 --- a/extensions/cornerstone-dicom-seg/src/getPanelModule.tsx +++ b/extensions/cornerstone-dicom-seg/src/getPanelModule.tsx @@ -1,17 +1,21 @@ import React from 'react'; import { useAppConfig } from '@state'; +import { Toolbox } from '@ohif/ui'; import PanelSegmentation from './panels/PanelSegmentation'; -import SegmentationToolbox from './panels/SegmentationToolbox'; -const getPanelModule = ({ commandsManager, servicesManager, extensionManager, configuration }) => { +const getPanelModule = ({ + commandsManager, + servicesManager, + extensionManager, + configuration, + title, +}) => { const { customizationService } = servicesManager.services; const wrappedPanelSegmentation = configuration => { const [appConfig] = useAppConfig(); - const disableEditingForMode = customizationService.get('segmentation.disableEditing'); - return ( ); @@ -27,12 +32,15 @@ const getPanelModule = ({ commandsManager, servicesManager, extensionManager, co const wrappedPanelSegmentationWithTools = configuration => { const [appConfig] = useAppConfig(); + return ( <> - diff --git a/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js b/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js index 7548d492e..d150201e0 100644 --- a/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js +++ b/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js @@ -1,5 +1,6 @@ import { utils } from '@ohif/core'; import { metaData, cache, triggerEvent, eventTarget } from '@cornerstonejs/core'; +import { CONSTANTS } from '@cornerstonejs/tools'; import { adaptersSEG, Enums } from '@cornerstonejs/adapters'; import { SOPClassHandlerId } from './id'; @@ -141,7 +142,7 @@ async function _loadSegments({ extensionManager, servicesManager, segDisplaySet, '@ohif/extension-cornerstone.utilityModule.common' ); - const { segmentationService } = servicesManager.services; + const { segmentationService, uiNotificationService } = servicesManager.services; const { dicomLoaderService } = utilityModule.exports; const arrayBuffer = await dicomLoaderService.findDicomDataPromise(segDisplaySet, null, headers); @@ -174,12 +175,40 @@ async function _loadSegments({ extensionManager, servicesManager, segDisplaySet, { skipOverlapping, tolerance, eventTarget, triggerEvent } ); + let usedRecommendedDisplayCIELabValue = true; results.segMetadata.data.forEach((data, i) => { if (i > 0) { - data.rgba = dicomlabToRGB(data.RecommendedDisplayCIELabValue); + data.rgba = data.RecommendedDisplayCIELabValue; + + if (data.rgba) { + data.rgba = dicomlabToRGB(data.rgba); + } else { + usedRecommendedDisplayCIELabValue = false; + data.rgba = CONSTANTS.COLOR_LUT[i % CONSTANTS.COLOR_LUT.length]; + } } }); + if (results.overlappingSegments) { + uiNotificationService.show({ + title: 'Overlapping Segments', + message: + 'Unsupported overlapping segments detected, segmentation rendering results may be incorrect.', + type: 'warning', + }); + } + + if (!usedRecommendedDisplayCIELabValue) { + // Display a notification about the non-utilization of RecommendedDisplayCIELabValue + uiNotificationService.show({ + title: 'DICOM SEG import', + message: + 'RecommendedDisplayCIELabValue not found for one or more segments. The default color was used instead.', + type: 'warning', + duration: 5000, + }); + } + Object.assign(segDisplaySet, results); } diff --git a/extensions/cornerstone-dicom-seg/src/getToolbarModule.ts b/extensions/cornerstone-dicom-seg/src/getToolbarModule.ts new file mode 100644 index 000000000..4234cf71c --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/getToolbarModule.ts @@ -0,0 +1,57 @@ +export function getToolbarModule({ servicesManager }) { + const { segmentationService, toolbarService, toolGroupService } = servicesManager.services; + return [ + { + name: 'evaluate.cornerstone.segmentation', + evaluate: ({ viewportId, button, toolNames, disabledText }) => { + // Todo: we need to pass in the button section Id since we are kind of + // forcing the button to have black background since initially + // it is designed for the toolbox not the toolbar on top + // we should then branch the buttonSectionId to have different styles + const segmentations = segmentationService.getSegmentations(); + if (!segmentations?.length) { + return { + disabled: true, + className: '!text-common-bright !bg-black opacity-50', + disabledText: disabledText ?? 'No segmentations available', + }; + } + + const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); + + if (!toolGroup) { + return { + disabled: true, + className: '!text-common-bright ohif-disabled', + disabledText: disabledText ?? 'Not available on the current viewport', + }; + } + + const toolName = toolbarService.getToolNameForButton(button); + + if (!toolGroup.hasTool(toolName) && !toolNames) { + return { + disabled: true, + className: '!text-common-bright ohif-disabled', + disabledText: disabledText ?? 'Not available on the current viewport', + }; + } + + const isPrimaryActive = toolNames + ? toolNames.includes(toolGroup.getActivePrimaryMouseButtonTool()) + : toolGroup.getActivePrimaryMouseButtonTool() === toolName; + + return { + disabled: false, + className: isPrimaryActive + ? '!text-black !bg-primary-light hover:bg-primary-light hover-text-black hover:cursor-pointer' + : '!text-common-bright !bg-black hover:bg-primary-light hover:cursor-pointer hover:text-black', + // Todo: isActive right now is used for nested buttons where the primary + // button needs to be fully rounded (vs partial rounded) when active + // otherwise it does not have any other use + isActive: isPrimaryActive, + }; + }, + }, + ]; +} diff --git a/extensions/cornerstone-dicom-seg/src/index.tsx b/extensions/cornerstone-dicom-seg/src/index.tsx index bb6a6d4b1..02db57545 100644 --- a/extensions/cornerstone-dicom-seg/src/index.tsx +++ b/extensions/cornerstone-dicom-seg/src/index.tsx @@ -5,7 +5,7 @@ import getSopClassHandlerModule from './getSopClassHandlerModule'; import getHangingProtocolModule from './getHangingProtocolModule'; import getPanelModule from './getPanelModule'; import getCommandsModule from './commandsModule'; -import preRegistration from './init'; +import { getToolbarModule } from './getToolbarModule'; const Component = React.lazy(() => { return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneSEGViewport'); @@ -28,8 +28,6 @@ const extension = { * You ID can be anything you want, but it should be unique. */ id, - preRegistration, - /** * PanelModule should provide a list of panels that will be available in OHIF * for Modes to consume and render. Each panel is defined by a {name, @@ -38,8 +36,8 @@ const extension = { */ getPanelModule, getCommandsModule, - - getViewportModule({ servicesManager, extensionManager }) { + getToolbarModule, + getViewportModule({ servicesManager, extensionManager, commandsManager }) { const ExtendedOHIFCornerstoneSEGViewport = props => { return ( { + const handleActiveViewportChange = viewportId => { + const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport( + viewportId || viewportGridService.getActiveViewportId() + ); + + if (!displaySetUIDs) { + return; + } + + const isReconstructable = + displaySetUIDs?.some(displaySetUID => { + const displaySet = displaySetService.getDisplaySetByUID(displaySetUID); + return displaySet?.isReconstructable; + }) || false; + + if (isReconstructable) { + setAddSegmentationClassName(''); + } else { + setAddSegmentationClassName('ohif-disabled'); + } + }; + + // Handle initial state + handleActiveViewportChange(); + + const changedGrid = viewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED; + const ready = viewportGridService.EVENTS.VIEWPORTS_READY; + + const subsGrid = []; + [ready, changedGrid].forEach(evt => { + const { unsubscribe } = viewportGridService.subscribe(evt, ({ viewportId }) => { + handleActiveViewportChange(viewportId); + }); + + subsGrid.push(unsubscribe); + }); + + const changedData = cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED; + + const subsData = []; + [changedData].forEach(evt => { + const { unsubscribe } = cornerstoneViewportService.subscribe(evt, () => { + handleActiveViewportChange(); + }); + + subsData.push(unsubscribe); + }); + + // Clean up + return () => { + subsGrid.forEach(unsub => unsub()); + subsData.forEach(unsub => unsub()); + }; + }, []); + const getToolGroupIds = segmentationId => { const toolGroupIds = segmentationService.getToolGroupIdsWithSegmentation(segmentationId); @@ -54,7 +124,9 @@ export default function PanelSegmentation({ }; const onSegmentationAdd = async () => { - commandsManager.runCommand('createEmptySegmentationForViewport'); + commandsManager.runCommand('createEmptySegmentationForViewport', { + viewportId: viewportGridService.getActiveViewportId(), + }); }; const onSegmentationClick = (segmentationId: string) => { @@ -147,6 +219,7 @@ export default function PanelSegmentation({ segmentationService.removeSegment(segmentationId, segmentIndex); }; + // segment hide const onToggleSegmentVisibility = (segmentationId, segmentIndex) => { const segmentation = segmentationService.getSegmentation(segmentationId); const segmentInfo = segmentation.segments[segmentIndex]; @@ -170,6 +243,22 @@ export default function PanelSegmentation({ const onToggleSegmentationVisibility = segmentationId => { segmentationService.toggleSegmentationVisibility(segmentationId); + const segmentation = segmentationService.getSegmentation(segmentationId); + const isVisible = segmentation.isVisible; + const segments = segmentation.segments; + + const toolGroupIds = getToolGroupIds(segmentationId); + + toolGroupIds.forEach(toolGroupId => { + segments.forEach((segment, segmentIndex) => { + segmentationService.setSegmentVisibility( + segmentationId, + segmentIndex, + isVisible, + toolGroupId + ); + }); + }); }; const _setSegmentationConfiguration = useCallback( @@ -221,59 +310,61 @@ export default function PanelSegmentation({ }); }; + const SegmentationGroupTableComponent = + components[configuration?.segmentationPanelMode] || SegmentationGroupTable; + const allowAddSegment = configuration?.addSegment; + const onSegmentationAddWrapper = + configuration?.onSegmentationAdd && typeof configuration?.onSegmentationAdd === 'function' + ? configuration?.onSegmentationAdd + : onSegmentationAdd; + return ( - <> -
- - _setSegmentationConfiguration(selectedSegmentationId, 'renderOutline', value) - } - setOutlineOpacityActive={value => - _setSegmentationConfiguration(selectedSegmentationId, 'outlineOpacity', value) - } - setRenderFill={value => - _setSegmentationConfiguration(selectedSegmentationId, 'renderFill', value) - } - setRenderInactiveSegmentations={value => - _setSegmentationConfiguration( - selectedSegmentationId, - 'renderInactiveSegmentations', - value - ) - } - setOutlineWidthActive={value => - _setSegmentationConfiguration(selectedSegmentationId, 'outlineWidthActive', value) - } - setFillAlpha={value => - _setSegmentationConfiguration(selectedSegmentationId, 'fillAlpha', value) - } - setFillAlphaInactive={value => - _setSegmentationConfiguration(selectedSegmentationId, 'fillAlphaInactive', value) - } - /> -
- + + _setSegmentationConfiguration(selectedSegmentationId, 'renderOutline', value) + } + setOutlineOpacityActive={value => + _setSegmentationConfiguration(selectedSegmentationId, 'outlineOpacity', value) + } + setRenderFill={value => + _setSegmentationConfiguration(selectedSegmentationId, 'renderFill', value) + } + setRenderInactiveSegmentations={value => + _setSegmentationConfiguration(selectedSegmentationId, 'renderInactiveSegmentations', value) + } + setOutlineWidthActive={value => + _setSegmentationConfiguration(selectedSegmentationId, 'outlineWidthActive', value) + } + setFillAlpha={value => + _setSegmentationConfiguration(selectedSegmentationId, 'fillAlpha', value) + } + setFillAlphaInactive={value => + _setSegmentationConfiguration(selectedSegmentationId, 'fillAlphaInactive', value) + } + /> ); } diff --git a/extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx b/extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx deleted file mode 100644 index f2d580cff..000000000 --- a/extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx +++ /dev/null @@ -1,405 +0,0 @@ -import React, { useCallback, useEffect, useState, useReducer } from 'react'; -import { AdvancedToolbox, InputDoubleRange, useViewportGrid } from '@ohif/ui'; -import { Types } from '@ohif/extension-cornerstone'; -import { utilities } from '@cornerstonejs/tools'; - -const { segmentation: segmentationUtils } = utilities; - -const TOOL_TYPES = { - CIRCULAR_BRUSH: 'CircularBrush', - SPHERE_BRUSH: 'SphereBrush', - CIRCULAR_ERASER: 'CircularEraser', - SPHERE_ERASER: 'SphereEraser', - CIRCLE_SHAPE: 'CircleScissor', - RECTANGLE_SHAPE: 'RectangleScissor', - SPHERE_SHAPE: 'SphereScissor', - THRESHOLD_CIRCULAR_BRUSH: 'ThresholdCircularBrush', - THRESHOLD_SPHERE_BRUSH: 'ThresholdSphereBrush', -}; - -const ACTIONS = { - SET_TOOL_CONFIG: 'SET_TOOL_CONFIG', - SET_ACTIVE_TOOL: 'SET_ACTIVE_TOOL', -}; - -const initialState = { - Brush: { - brushSize: 15, - mode: 'CircularBrush', // Can be 'CircularBrush' or 'SphereBrush' - }, - Eraser: { - brushSize: 15, - mode: 'CircularEraser', // Can be 'CircularEraser' or 'SphereEraser' - }, - Shapes: { - brushSize: 15, - mode: 'CircleScissor', // E.g., 'CircleScissor', 'RectangleScissor', or 'SphereScissor' - }, - ThresholdBrush: { - brushSize: 15, - thresholdRange: [-500, 500], - }, - activeTool: null, -}; - -function toolboxReducer(state, action) { - switch (action.type) { - case ACTIONS.SET_TOOL_CONFIG: - const { tool, config } = action.payload; - return { - ...state, - [tool]: { - ...state[tool], - ...config, - }, - }; - case ACTIONS.SET_ACTIVE_TOOL: - return { ...state, activeTool: action.payload }; - default: - return state; - } -} - -function SegmentationToolbox({ servicesManager, extensionManager }) { - const { toolbarService, segmentationService, toolGroupService } = - servicesManager.services as Types.CornerstoneServices; - - const [viewportGrid] = useViewportGrid(); - const { viewports, activeViewportId } = viewportGrid; - - const [toolsEnabled, setToolsEnabled] = useState(false); - const [state, dispatch] = useReducer(toolboxReducer, initialState); - - const updateActiveTool = useCallback(() => { - if (!viewports?.size || activeViewportId === undefined) { - return; - } - const viewport = viewports.get(activeViewportId); - - if (!viewport) { - return; - } - - dispatch({ - type: ACTIONS.SET_ACTIVE_TOOL, - payload: toolGroupService.getActiveToolForViewport(viewport.viewportId), - }); - }, [activeViewportId, viewports, toolGroupService, dispatch]); - - const setToolActive = useCallback( - toolName => { - toolbarService.recordInteraction({ - interactionType: 'tool', - commands: [ - { - commandName: 'setToolActive', - commandOptions: { - toolName, - }, - }, - ], - }); - - dispatch({ type: ACTIONS.SET_ACTIVE_TOOL, payload: toolName }); - }, - [toolbarService, dispatch] - ); - - /** - * sets the tools enabled IF there are segmentations - */ - useEffect(() => { - const events = [ - segmentationService.EVENTS.SEGMENTATION_ADDED, - segmentationService.EVENTS.SEGMENTATION_UPDATED, - segmentationService.EVENTS.SEGMENTATION_REMOVED, - ]; - - const unsubscriptions = []; - - events.forEach(event => { - const { unsubscribe } = segmentationService.subscribe(event, () => { - const segmentations = segmentationService.getSegmentations(); - - const activeSegmentation = segmentations?.find(seg => seg.isActive); - - setToolsEnabled(activeSegmentation?.segmentCount > 0); - }); - - unsubscriptions.push(unsubscribe); - }); - - updateActiveTool(); - - return () => { - unsubscriptions.forEach(unsubscribe => unsubscribe()); - }; - }, [activeViewportId, viewports, segmentationService, updateActiveTool]); - - /** - * Update the active tool when the toolbar state changes - */ - useEffect(() => { - const { unsubscribe } = toolbarService.subscribe( - toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED, - () => { - updateActiveTool(); - } - ); - - return () => { - unsubscribe(); - }; - }, [toolbarService, updateActiveTool]); - - useEffect(() => { - // if the active tool is not a brush tool then do nothing - if (!Object.values(TOOL_TYPES).includes(state.activeTool)) { - return; - } - - // if the tool is Segmentation and it is enabled then do nothing - if (toolsEnabled) { - return; - } - - // if the tool is Segmentation and it is disabled, then switch - // back to the window level tool to not confuse the user when no - // segmentation is active or when there is no segment in the segmentation - setToolActive('WindowLevel'); - }, [toolsEnabled, state.activeTool, setToolActive]); - - const updateBrushSize = useCallback( - (toolName, brushSize) => { - toolGroupService.getToolGroupIds()?.forEach(toolGroupId => { - segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize, toolName); - }); - }, - [toolGroupService] - ); - - const onBrushSizeChange = useCallback( - (valueAsStringOrNumber, toolCategory) => { - const value = Number(valueAsStringOrNumber); - - _getToolNamesFromCategory(toolCategory).forEach(toolName => { - updateBrushSize(toolName, value); - }); - - dispatch({ - type: ACTIONS.SET_TOOL_CONFIG, - payload: { - tool: toolCategory, - config: { brushSize: value }, - }, - }); - }, - [toolGroupService, dispatch] - ); - - const handleRangeChange = useCallback( - newRange => { - if ( - newRange[0] === state.ThresholdBrush.thresholdRange[0] && - newRange[1] === state.ThresholdBrush.thresholdRange[1] - ) { - return; - } - - const toolNames = _getToolNamesFromCategory('ThresholdBrush'); - - toolNames.forEach(toolName => { - toolGroupService.getToolGroupIds()?.forEach(toolGroupId => { - const toolGroup = toolGroupService.getToolGroup(toolGroupId); - toolGroup.setToolConfiguration(toolName, { - strategySpecificConfiguration: { - THRESHOLD_INSIDE_CIRCLE: { - threshold: newRange, - }, - }, - }); - }); - }); - - dispatch({ - type: ACTIONS.SET_TOOL_CONFIG, - payload: { - tool: 'ThresholdBrush', - config: { thresholdRange: newRange }, - }, - }); - }, - [toolGroupService, dispatch, state.ThresholdBrush.thresholdRange] - ); - - return ( - setToolActive(TOOL_TYPES.CIRCULAR_BRUSH), - options: [ - { - name: 'Radius (mm)', - id: 'brush-radius', - type: 'range', - min: 0.5, - max: 99.5, - value: state.Brush.brushSize, - step: 0.5, - onChange: value => onBrushSizeChange(value, 'Brush'), - }, - { - name: 'Mode', - type: 'radio', - id: 'brush-mode', - value: state.Brush.mode, - values: [ - { value: TOOL_TYPES.CIRCULAR_BRUSH, label: 'Circle' }, - { value: TOOL_TYPES.SPHERE_BRUSH, label: 'Sphere' }, - ], - onChange: value => setToolActive(value), - }, - ], - }, - { - name: 'Eraser', - icon: 'icon-tool-eraser', - disabled: !toolsEnabled, - active: - state.activeTool === TOOL_TYPES.CIRCULAR_ERASER || - state.activeTool === TOOL_TYPES.SPHERE_ERASER, - onClick: () => setToolActive(TOOL_TYPES.CIRCULAR_ERASER), - options: [ - { - name: 'Radius (mm)', - type: 'range', - id: 'eraser-radius', - min: 0.5, - max: 99.5, - value: state.Eraser.brushSize, - step: 0.5, - onChange: value => onBrushSizeChange(value, 'Eraser'), - }, - { - name: 'Mode', - type: 'radio', - id: 'eraser-mode', - value: state.Eraser.mode, - values: [ - { value: TOOL_TYPES.CIRCULAR_ERASER, label: 'Circle' }, - { value: TOOL_TYPES.SPHERE_ERASER, label: 'Sphere' }, - ], - onChange: value => setToolActive(value), - }, - ], - }, - { - name: 'Shapes', - icon: 'icon-tool-shape', - disabled: !toolsEnabled, - active: - 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.Shapes.mode, - id: 'shape-mode', - values: [ - { 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), - }, - ], - }, - { - name: 'Threshold Tool', - icon: 'icon-tool-threshold', - disabled: !toolsEnabled, - active: - state.activeTool === TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH || - state.activeTool === TOOL_TYPES.THRESHOLD_SPHERE_BRUSH, - onClick: () => setToolActive(TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH), - options: [ - { - name: 'Radius (mm)', - id: 'threshold-radius', - type: 'range', - min: 0.5, - max: 99.5, - value: state.ThresholdBrush.brushSize, - step: 0.5, - onChange: value => onBrushSizeChange(value, 'ThresholdBrush'), - }, - { - name: 'Mode', - type: 'radio', - id: 'threshold-mode', - value: state.activeTool, - values: [ - { value: TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH, label: 'Circle' }, - { value: TOOL_TYPES.THRESHOLD_SPHERE_BRUSH, label: 'Sphere' }, - ], - onChange: value => setToolActive(value), - }, - { - type: 'custom', - id: 'segmentation-threshold-range', - children: () => { - return ( -
-
-
Threshold
- -
- ); - }, - }, - ], - }, - ]} - /> - ); -} - -function _getToolNamesFromCategory(category) { - let toolNames = []; - switch (category) { - case 'Brush': - toolNames = ['CircularBrush', 'SphereBrush']; - break; - case 'Eraser': - toolNames = ['CircularEraser', 'SphereEraser']; - break; - case 'ThresholdBrush': - toolNames = ['ThresholdCircularBrush', 'ThresholdSphereBrush']; - break; - default: - break; - } - - return toolNames; -} - -export default SegmentationToolbox; diff --git a/extensions/cornerstone-dicom-seg/src/types/segmentation.tsx b/extensions/cornerstone-dicom-seg/src/types/segmentation.tsx new file mode 100644 index 000000000..170c09ca4 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/types/segmentation.tsx @@ -0,0 +1,4 @@ +export enum SegmentationPanelMode { + Expanded = 'expanded', + Dropdown = 'dropdown', +} diff --git a/extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts b/extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts index fa3c6d47c..8726d9dc4 100644 --- a/extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts +++ b/extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts @@ -138,7 +138,7 @@ function getUpdatedViewportsForSegmentation({ const { hangingProtocolService, displaySetService, segmentationService, viewportGridService } = servicesManager.services; - const { viewports } = viewportGridService.getState(); + const { viewports, isHangingProtocolLayout } = viewportGridService.getState(); const viewport = getTargetViewport({ viewportId, viewportGridService }); const targetViewportId = viewport.viewportOptions.viewportId; @@ -153,7 +153,8 @@ function getUpdatedViewportsForSegmentation({ const updatedViewports = hangingProtocolService.getViewportsRequireUpdate( targetViewportId, - referenceDisplaySetInstanceUID + referenceDisplaySetInstanceUID, + isHangingProtocolLayout ); viewports.forEach((viewport, viewportId) => { @@ -180,7 +181,8 @@ function getUpdatedViewportsForSegmentation({ }); } }); - return updatedViewports; + + return updatedViewports.filter(v => v.viewportOptions?.viewportType !== 'volume3d'); } export { diff --git a/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts b/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts index 9f8c1dddf..c42b32e69 100644 --- a/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts +++ b/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts @@ -63,6 +63,11 @@ function _askHydrate(uiViewportDialogService, viewportId) { uiViewportDialogService.hide(); resolve(RESPONSE.CANCEL); }, + onKeyPress: event => { + if (event.key === 'Enter') { + onSubmit(RESPONSE.HYDRATE_SEG); + } + }, }); }); } diff --git a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx index fb39f8c36..b4e0f4528 100644 --- a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx +++ b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx @@ -1,13 +1,11 @@ import PropTypes from 'prop-types'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import OHIF, { utils } from '@ohif/core'; -import { LoadingIndicatorTotalPercent, useViewportGrid, ViewportActionBar } from '@ohif/ui'; +import { LoadingIndicatorTotalPercent, useViewportGrid, ViewportActionArrows } from '@ohif/ui'; import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup'; import promptHydrateSEG from '../utils/promptHydrateSEG'; import _getStatusComponent from './_getStatusComponent'; -const { formatDate } = utils; const SEG_TOOLGROUP_BASE_NAME = 'SEGToolGroup'; function OHIFCornerstoneSEGViewport(props) { @@ -15,7 +13,6 @@ function OHIFCornerstoneSEGViewport(props) { children, displaySets, viewportOptions, - viewportLabel, servicesManager, extensionManager, commandsManager, @@ -28,8 +25,8 @@ function OHIFCornerstoneSEGViewport(props) { displaySetService, toolGroupService, segmentationService, - uiNotificationService, customizationService, + viewportActionCornersService, } = servicesManager.services; const toolGroupId = `${SEG_TOOLGROUP_BASE_NAME}-${viewportId}`; @@ -114,16 +111,17 @@ function OHIFCornerstoneSEGViewport(props) { orientation: viewportOptions.orientation, viewportId: viewportOptions.viewportId, }} - onElementEnabled={onElementEnabled} + onElementEnabled={evt => { + props.onElementEnabled?.(evt); + onElementEnabled(evt); + }} onElementDisabled={onElementDisabled} - // initialImageIndex={initialImageIndex} > ); }, [viewportId, segDisplaySet, toolGroupId]); const onSegmentChange = useCallback( direction => { - direction = direction === 'left' ? -1 : 1; const segmentationId = segDisplaySet.displaySetInstanceUID; const segmentation = segmentationService.getSegmentation(segmentationId); @@ -172,14 +170,6 @@ function OHIFCornerstoneSEGViewport(props) { if (evt.segDisplaySet.displaySetInstanceUID === segDisplaySet.displaySetInstanceUID) { setSegIsLoading(false); } - - if (evt.overlappingSegments) { - uiNotificationService.show({ - title: 'Overlapping Segments', - message: 'Overlapping segments detected which is not currently supported', - type: 'warning', - }); - } } ); @@ -256,6 +246,69 @@ function OHIFCornerstoneSEGViewport(props) { }; }, [segDisplaySet]); + const hydrateSEGDisplaySet = useCallback( + ({ segDisplaySet, viewportId }) => { + commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', { + displaySets: [segDisplaySet], + viewportId, + }); + }, + [commandsManager] + ); + + const onStatusClick = useCallback(async () => { + // Before hydrating a SEG and make it added to all viewports in the grid + // that share the same frameOfReferenceUID, we need to store the viewport grid + // presentation state, so that we can restore it after hydrating the SEG. This is + // required if the user has changed the viewport (other viewport than SEG viewport) + // presentation state (w/l and invert) and then opens the SEG. If we don't store + // the presentation state, the viewport will be reset to the default presentation + storePresentationState(); + const isHydrated = await hydrateSEGDisplaySet({ + segDisplaySet, + viewportId, + }); + + setIsHydrated(isHydrated); + }, [hydrateSEGDisplaySet, segDisplaySet, storePresentationState, viewportId]); + + useEffect(() => { + viewportActionCornersService.setComponents([ + { + viewportId, + id: 'viewportStatusComponent', + component: _getStatusComponent({ + isHydrated, + onStatusClick, + }), + indexPriority: -100, + location: viewportActionCornersService.LOCATIONS.topLeft, + }, + { + viewportId, + id: 'viewportActionArrowsComponent', + component: ( + + ), + indexPriority: 0, + location: viewportActionCornersService.LOCATIONS.topRight, + }, + ]); + }, [ + activeViewportId, + isHydrated, + onSegmentChange, + onStatusClick, + viewportActionCornersService, + viewportId, + ]); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ let childrenWithProps = null; @@ -291,61 +344,8 @@ function OHIFCornerstoneSEGViewport(props) { SpacingBetweenSlices, } = referencedDisplaySetRef.current.metadata; - const hydrateSEGDisplaySet = ({ segDisplaySet, viewportId }) => { - commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', { - displaySets: [segDisplaySet], - viewportId, - }); - }; - - const onStatusClick = async () => { - // Before hydrating a SEG and make it added to all viewports in the grid - // that share the same frameOfReferenceUID, we need to store the viewport grid - // presentation state, so that we can restore it after hydrating the SEG. This is - // required if the user has changed the viewport (other viewport than SEG viewport) - // presentation state (w/l and invert) and then opens the SEG. If we don't store - // the presentation state, the viewport will be reset to the default presentation - storePresentationState(); - const isHydrated = await hydrateSEGDisplaySet({ - segDisplaySet, - viewportId, - }); - - setIsHydrated(isHydrated); - }; return ( <> - { - evt.stopPropagation(); - evt.preventDefault(); - }} - onArrowsClick={onSegmentChange} - getStatusComponent={() => { - return _getStatusComponent({ - isHydrated, - onStatusClick, - }); - }} - studyData={{ - label: viewportLabel, - useAltStyling: true, - studyDate: formatDate(StudyDate), - seriesDescription: `SEG Viewport ${SeriesDescription}`, - patientInformation: { - patientName: PatientName ? OHIF.utils.formatPN(PatientName.Alphabetic) : '', - patientSex: PatientSex || '', - patientAge: PatientAge || '', - MRN: PatientID || '', - thickness: SliceThickness ? utils.roundNumber(SliceThickness, 2) : '', - thicknessUnits: SliceThickness !== undefined ? 'mm' : '', - spacing: - SpacingBetweenSlices !== undefined ? utils.roundNumber(SpacingBetweenSlices, 2) : '', - scanner: ManufacturerModelName || '', - }, - }} - /> -
{segIsLoading && ( ; @@ -26,23 +23,28 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) { ToolTipMessage = () =>
Click LOAD to load segmentation.
; } - const StatusArea = () => ( -
-
- - SEG -
- {!isHydrated && ( -
- {loadStr} + const StatusArea = () => { + const { t } = useTranslation('Common'); + const loadStr = t('LOAD'); + + return ( +
+
+ + SEG
- )} -
- ); + {!isHydrated && ( +
+ {loadStr} +
+ )} +
+ ); + }; return ( <> diff --git a/extensions/cornerstone-dicom-sr/CHANGELOG.md b/extensions/cornerstone-dicom-sr/CHANGELOG.md index db8175162..af0c57aa0 100644 --- a/extensions/cornerstone-dicom-sr/CHANGELOG.md +++ b/extensions/cornerstone-dicom-sr/CHANGELOG.md @@ -3,7 +3,820 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -# [3.7.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.7.0) (2023-10-11) +# [3.8.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.92...v3.8.0-beta.93) (2024-04-29) + + +### Bug Fixes + +* **toolbox:** Preserve user-specified tool state and streamline command execution ([#4063](https://github.com/OHIF/Viewers/issues/4063)) ([f1a736d](https://github.com/OHIF/Viewers/commit/f1a736d1934733a434cb87b2c284907a3122403f)) + + + + + +# [3.8.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.91...v3.8.0-beta.92) (2024-04-28) + + +### Bug Fixes + +* **bugs:** fix patient header for doc, track ball rotate resize observer and add segmentation button not being enabled on viewport data change ([#4068](https://github.com/OHIF/Viewers/issues/4068)) ([c09311d](https://github.com/OHIF/Viewers/commit/c09311d3b7df05fcd00a9f36a7233e9d7e5589d0)) + + + + + +# [3.8.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.90...v3.8.0-beta.91) (2024-04-25) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.89...v3.8.0-beta.90) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.88...v3.8.0-beta.89) (2024-04-22) + + +### Bug Fixes + +* **viewport-webworker-segmentation:** Resolve issues with viewport detection, webworker termination, and segmentation panel layout change ([#4059](https://github.com/OHIF/Viewers/issues/4059)) ([52a0c59](https://github.com/OHIF/Viewers/commit/52a0c59294a4161fcca0a6708855549034849951)) + + + + + +# [3.8.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.87...v3.8.0-beta.88) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.86...v3.8.0-beta.87) (2024-04-19) + + +### Features + +* **tmtv-mode:** Add Brush tools and move SUV peak calculation to web worker ([#4053](https://github.com/OHIF/Viewers/issues/4053)) ([8192e34](https://github.com/OHIF/Viewers/commit/8192e348eca993fec331d4963efe88f9a730eceb)) + + + + + +# [3.8.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.85...v3.8.0-beta.86) (2024-04-19) + + +### Bug Fixes + +* **layouts:** and fix thumbnail in touch and update migration guide for 3.8 release ([#4052](https://github.com/OHIF/Viewers/issues/4052)) ([d250d04](https://github.com/OHIF/Viewers/commit/d250d04580883446fcb8d748b2a97c5c198922af)) + + + + + +# [3.8.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.84...v3.8.0-beta.85) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.83...v3.8.0-beta.84) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.82...v3.8.0-beta.83) (2024-04-18) + + +### Bug Fixes + +* **bugs:** enhancements and bug fixes - final ([#4048](https://github.com/OHIF/Viewers/issues/4048)) ([170bb96](https://github.com/OHIF/Viewers/commit/170bb96983082c39b22b7352e0c54aacf3e73b02)) + + + + + +# [3.8.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.81...v3.8.0-beta.82) (2024-04-17) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.80...v3.8.0-beta.81) (2024-04-16) + + +### Bug Fixes + +* **viewport:** Reset viewport state and fix CINE looping, thumbnail resolution, and dynamic tool settings ([#4037](https://github.com/OHIF/Viewers/issues/4037)) ([f99a0bf](https://github.com/OHIF/Viewers/commit/f99a0bfb31434aa137bbb3ed1f9eef1dfcc09025)) + + + + + +# [3.8.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.79...v3.8.0-beta.80) (2024-04-16) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.78...v3.8.0-beta.79) (2024-04-10) + + +### Features + +* **SM:** remove SM measurements from measurement panel ([#4022](https://github.com/OHIF/Viewers/issues/4022)) ([df49a65](https://github.com/OHIF/Viewers/commit/df49a653be61a93f6e9fb3663aabe9775c31fd13)) + + + + + +# [3.8.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.77...v3.8.0-beta.78) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.76...v3.8.0-beta.77) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.75...v3.8.0-beta.76) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.74...v3.8.0-beta.75) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.73...v3.8.0-beta.74) (2024-04-10) + + +### Features + +* **4D:** Add 4D dynamic volume rendering and new pre-clinical 4d pt/ct mode ([#3664](https://github.com/OHIF/Viewers/issues/3664)) ([d57e8bc](https://github.com/OHIF/Viewers/commit/d57e8bc1571c6da4effaa492ee2d162c552365a2)) + + + + + +# [3.8.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.72...v3.8.0-beta.73) (2024-04-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.71...v3.8.0-beta.72) (2024-04-05) + + +### Bug Fixes + +* **cornerstone-dicom-sr:** Freehand SR hydration support ([#3996](https://github.com/OHIF/Viewers/issues/3996)) ([5645ac1](https://github.com/OHIF/Viewers/commit/5645ac1b271e1ed8c57f5d71100809362447267e)) + + + + + +# [3.8.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.70...v3.8.0-beta.71) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.69...v3.8.0-beta.70) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.68...v3.8.0-beta.69) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.67...v3.8.0-beta.68) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.66...v3.8.0-beta.67) (2024-04-02) + + +### Features + +* **ViewportActionMenu:** window level per viewport / new patient info / colorbars/ 3D presets and 3D volume rendering ([#3963](https://github.com/OHIF/Viewers/issues/3963)) ([b7f90e3](https://github.com/OHIF/Viewers/commit/b7f90e3951845396f99b69f0a74fc56b2ffeada1)) + + + + + +# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28) + + +### Bug Fixes + +* **new layout:** address black screen bugs ([#4008](https://github.com/OHIF/Viewers/issues/4008)) ([158a181](https://github.com/OHIF/Viewers/commit/158a1816703e0ad66cae08cb9bd1ffb93bbd8d43)) + + + + + +# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27) + + +### Features + +* **toolbar:** new Toolbar to enable reactive state synchronization ([#3983](https://github.com/OHIF/Viewers/issues/3983)) ([566b25a](https://github.com/OHIF/Viewers/commit/566b25a54425399096864bd263193646556011a5)) + + + + + +# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18) + + +### Bug Fixes + +* **SR display:** and the token based navigation ([#3995](https://github.com/OHIF/Viewers/issues/3995)) ([feed230](https://github.com/OHIF/Viewers/commit/feed2304c124dc2facc7a7371ed9851548c223c5)) + + + + + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + + +### Bug Fixes + +* **demo:** Deploy issue ([#3951](https://github.com/OHIF/Viewers/issues/3951)) ([21e8a2b](https://github.com/OHIF/Viewers/commit/21e8a2bd0b7cc72f90a31e472d285d761be15d30)) + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + + +### Bug Fixes + +* Update CS3D to fix second render ([#3892](https://github.com/OHIF/Viewers/issues/3892)) ([d00a86b](https://github.com/OHIF/Viewers/commit/d00a86b022742ea089d246d06cfd691f43b64412)) + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + + +### Bug Fixes + +* **segmentation:** upgrade cs3d to fix various segmentation bugs ([#3885](https://github.com/OHIF/Viewers/issues/3885)) ([b1efe40](https://github.com/OHIF/Viewers/commit/b1efe40aa146e4052cc47b3f774cabbb47a8d1a6)) + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + + +### Features + +* **customizationService:** Enable saving and loading of private tags in SRs ([#3842](https://github.com/OHIF/Viewers/issues/3842)) ([e1f55e6](https://github.com/OHIF/Viewers/commit/e1f55e65f2d2a34136ad5d0b1ada77d337a0ea23)) + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + + +### Bug Fixes + +* **auth:** fix the issue with oauth at a non root path ([#3840](https://github.com/OHIF/Viewers/issues/3840)) ([6651008](https://github.com/OHIF/Viewers/commit/6651008fbb35dabd5991c7f61128e6ef324012df)) + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + + +### Bug Fixes + +* Update the CS3D packages to add the most recent HTJ2K TSUIDS ([#3806](https://github.com/OHIF/Viewers/issues/3806)) ([9d1884d](https://github.com/OHIF/Viewers/commit/9d1884d7d8b6b2a1cdc26965a96995838aa72682)) + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + + +### Features + +* **dicomJSON:** Add Loading Other Display Sets and JSON Metadata Generation script ([#3777](https://github.com/OHIF/Viewers/issues/3777)) ([43b1c17](https://github.com/OHIF/Viewers/commit/43b1c17209502e4876ad59bae09ed9442eda8024)) + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + + +### Bug Fixes + +* **sr:** dcm4chee requires the patient name for an SR to match what is in the original study ([#3739](https://github.com/OHIF/Viewers/issues/3739)) ([d98439f](https://github.com/OHIF/Viewers/commit/d98439fe7f3825076dbc87b664a1d1480ff414d3)) + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) **Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr diff --git a/extensions/cornerstone-dicom-sr/package.json b/extensions/cornerstone-dicom-sr/package.json index 40f26174c..27c0f03c6 100644 --- a/extensions/cornerstone-dicom-sr/package.json +++ b/extensions/cornerstone-dicom-sr/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone-dicom-sr", - "version": "3.7.0", + "version": "3.8.0-beta.93", "description": "OHIF extension for an SR Cornerstone Viewport", "author": "OHIF", "license": "MIT", @@ -23,6 +23,8 @@ "ohif-extension" ], "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "dev:cornerstone": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", @@ -32,11 +34,11 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0", - "@ohif/extension-cornerstone": "3.7.0", - "@ohif/extension-measurement-tracking": "3.7.0", - "@ohif/ui": "3.7.0", - "dcmjs": "^0.29.5", + "@ohif/core": "3.8.0-beta.93", + "@ohif/extension-cornerstone": "3.8.0-beta.93", + "@ohif/extension-measurement-tracking": "3.8.0-beta.93", + "@ohif/ui": "3.8.0-beta.93", + "dcmjs": "^0.29.12", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", @@ -44,9 +46,9 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^1.20.3", - "@cornerstonejs/core": "^1.20.3", - "@cornerstonejs/tools": "^1.20.3", + "@cornerstonejs/adapters": "^1.70.14", + "@cornerstonejs/core": "^1.70.14", + "@cornerstonejs/tools": "^1.70.14", "classnames": "^2.3.2" } } diff --git a/extensions/cornerstone-dicom-sr/src/commandsModule.js b/extensions/cornerstone-dicom-sr/src/commandsModule.js index ec68cabb9..2f355bc09 100644 --- a/extensions/cornerstone-dicom-sr/src/commandsModule.js +++ b/extensions/cornerstone-dicom-sr/src/commandsModule.js @@ -40,7 +40,9 @@ const _generateReport = (measurementData, additionalFindingTypes, options = {}) return dataset; }; -const commandsModule = ({}) => { +const commandsModule = props => { + const { servicesManager } = props; + const { customizationService } = servicesManager.services; const actions = { /** * @@ -95,7 +97,15 @@ const commandsModule = ({}) => { throw new Error('Invalid report, no content'); } - await dataSource.store.dicom(naturalizedReport); + const onBeforeDicomStore = + customizationService.getModeCustomization('onBeforeDicomStore')?.value; + + let dicomDict; + if (typeof onBeforeDicomStore === 'function') { + dicomDict = onBeforeDicomStore({ measurementData, naturalizedReport }); + } + + await dataSource.store.dicom(naturalizedReport, null, dicomDict); if (StudyInstanceUID) { dataSource.deleteStudyMetadataPromise(StudyInstanceUID); @@ -118,13 +128,9 @@ const commandsModule = ({}) => { const definitions = { downloadReport: { commandFn: actions.downloadReport, - storeContexts: [], - options: {}, }, storeMeasurements: { commandFn: actions.storeMeasurements, - storeContexts: [], - options: {}, }, }; diff --git a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts index 5cdf455f6..be946bcfb 100644 --- a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts +++ b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts @@ -1,6 +1,6 @@ import { SOPClassHandlerName, SOPClassHandlerId } from './id'; import { utils, classes, DisplaySetService, Types } from '@ohif/core'; -import addMeasurement from './utils/addMeasurement'; +import addDICOMSRDisplayAnnotation from './utils/addDICOMSRDisplayAnnotation'; import isRehydratable from './utils/isRehydratable'; import { adaptersSR } from '@cornerstonejs/adapters'; @@ -150,13 +150,37 @@ function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) return [displaySet]; } -function _load(displaySet, servicesManager, extensionManager) { +async function _load(displaySet, servicesManager, extensionManager) { const { displaySetService, measurementService } = servicesManager.services; const dataSources = extensionManager.getDataSources(); const dataSource = dataSources[0]; const { ContentSequence } = displaySet.instance; + async function retrieveBulkData(obj, parentObj = null, key = null) { + for (const prop in obj) { + if (typeof obj[prop] === 'object' && obj[prop] !== null) { + await retrieveBulkData(obj[prop], obj, prop); + } else if (Array.isArray(obj[prop])) { + await Promise.all(obj[prop].map(item => retrieveBulkData(item, obj, prop))); + } else if (prop === 'BulkDataURI') { + const value = await dataSource.retrieve.bulkDataURI({ + BulkDataURI: obj[prop], + StudyInstanceUID: displaySet.instance.StudyInstanceUID, + SeriesInstanceUID: displaySet.instance.SeriesInstanceUID, + SOPInstanceUID: displaySet.instance.SOPInstanceUID, + }); + if (parentObj && key) { + parentObj[key] = new Float32Array(value); + } + } + } + } + + if (displaySet.isLoaded !== true) { + await retrieveBulkData(ContentSequence); + } + displaySet.referencedImages = _getReferencedImagesList(ContentSequence); displaySet.measurements = _getMeasurements(ContentSequence); @@ -171,7 +195,12 @@ function _load(displaySet, servicesManager, extensionManager) { // Check currently added displaySets and add measurements if the sources exist. displaySetService.activeDisplaySets.forEach(activeDisplaySet => { - _checkIfCanAddMeasurementsToDisplaySet(displaySet, activeDisplaySet, dataSource); + _checkIfCanAddMeasurementsToDisplaySet( + displaySet, + activeDisplaySet, + dataSource, + servicesManager + ); }); // Subscribe to new displaySets as the source may come in after. @@ -180,12 +209,23 @@ function _load(displaySet, servicesManager, extensionManager) { // If there are still some measurements that have not yet been loaded into cornerstone, // See if we can load them onto any of the new displaySets. displaySetsAdded.forEach(newDisplaySet => { - _checkIfCanAddMeasurementsToDisplaySet(displaySet, newDisplaySet, dataSource); + _checkIfCanAddMeasurementsToDisplaySet( + displaySet, + newDisplaySet, + dataSource, + servicesManager + ); }); }); } -function _checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, newDisplaySet, dataSource) { +function _checkIfCanAddMeasurementsToDisplaySet( + srDisplaySet, + newDisplaySet, + dataSource, + servicesManager +) { + const { customizationService } = servicesManager.services; let unloadedMeasurements = srDisplaySet.measurements.filter( measurement => measurement.loaded === false ); @@ -195,12 +235,16 @@ function _checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, newDisplaySet, dat return; } - if (!newDisplaySet instanceof ImageSet) { + if ((!newDisplaySet) instanceof ImageSet) { // This also filters out _this_ displaySet, as it is not an ImageSet. return; } - const { sopClassUids, images } = newDisplaySet; + if (newDisplaySet.unsupported) { + return; + } + + const { sopClassUids } = newDisplaySet; // Check if any have the newDisplaySet is the correct SOPClass. unloadedMeasurements = unloadedMeasurements.filter(measurement => @@ -240,9 +284,37 @@ function _checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, newDisplaySet, dat if (SOPInstanceUIDs.includes(SOPInstanceUID)) { for (let j = unloadedMeasurements.length - 1; j >= 0; j--) { - const measurement = unloadedMeasurements[j]; + let measurement = unloadedMeasurements[j]; + + const onBeforeSRAddMeasurement = customizationService.getModeCustomization( + 'onBeforeSRAddMeasurement' + )?.value; + + if (typeof onBeforeSRAddMeasurement === 'function') { + measurement = onBeforeSRAddMeasurement({ + measurement, + StudyInstanceUID: srDisplaySet.StudyInstanceUID, + SeriesInstanceUID: srDisplaySet.SeriesInstanceUID, + }); + } + if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber)) { - addMeasurement(measurement, imageId, newDisplaySet.displaySetInstanceUID); + const frame = + (measurement.coords[0].ReferencedSOPSequence && + measurement.coords[0].ReferencedSOPSequence?.ReferencedFrameNumber) || + 1; + + /** Add DICOMSRDisplay annotation for the SR viewport (only) */ + addDICOMSRDisplayAnnotation(measurement, imageId, frame); + + /** Update measurement properties */ + measurement.loaded = true; + measurement.imageId = imageId; + measurement.displaySetInstanceUID = newDisplaySet.displaySetInstanceUID; + measurement.ReferencedSOPInstanceUID = + measurement.coords[0].ReferencedSOPSequence.ReferencedSOPInstanceUID; + measurement.frameNumber = frame; + delete measurement.coords; unloadedMeasurements.splice(j, 1); } @@ -258,7 +330,7 @@ function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frame // Standard. But for now, we will support only one ReferenceFrameNumber. const ReferencedFrameNumber = (measurement.coords[0].ReferencedSOPSequence && - measurement.coords[0].ReferencedSOPSequence[0]?.ReferencedFrameNumber) || + measurement.coords[0].ReferencedSOPSequence?.ReferencedFrameNumber) || 1; if (frameNumber && Number(frameNumber) !== Number(ReferencedFrameNumber)) { diff --git a/extensions/cornerstone-dicom-sr/src/index.tsx b/extensions/cornerstone-dicom-sr/src/index.tsx index 9b0700481..b0b5e7abb 100644 --- a/extensions/cornerstone-dicom-sr/src/index.tsx +++ b/extensions/cornerstone-dicom-sr/src/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import getSopClassHandlerModule from './getSopClassHandlerModule'; -import getHangingProtocolModule, { srProtocol } from './getHangingProtocolModule'; +import { srProtocol } from './getHangingProtocolModule'; import onModeEnter from './onModeEnter'; import getCommandsModule from './commandsModule'; import preRegistration from './init'; diff --git a/extensions/cornerstone-dicom-sr/src/init.ts b/extensions/cornerstone-dicom-sr/src/init.ts index 1ab3300d7..949b4de2e 100644 --- a/extensions/cornerstone-dicom-sr/src/init.ts +++ b/extensions/cornerstone-dicom-sr/src/init.ts @@ -1,5 +1,4 @@ import { - addTool, AngleTool, annotation, ArrowAnnotateTool, @@ -9,6 +8,7 @@ import { CircleROITool, LengthTool, PlanarFreehandROITool, + RectangleROITool, } from '@cornerstonejs/tools'; import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool'; import addToolInstance from './utils/addToolInstance'; @@ -19,24 +19,25 @@ import toolNames from './tools/toolNames'; * @param {object} configuration */ export default function init({ configuration = {} }: Types.Extensions.ExtensionParams): void { - addTool(DICOMSRDisplayTool); - addToolInstance(toolNames.SRLength, LengthTool, {}); + addToolInstance(toolNames.DICOMSRDisplay, DICOMSRDisplayTool); + addToolInstance(toolNames.SRLength, LengthTool); addToolInstance(toolNames.SRBidirectional, BidirectionalTool); addToolInstance(toolNames.SREllipticalROI, EllipticalROITool); addToolInstance(toolNames.SRCircleROI, CircleROITool); addToolInstance(toolNames.SRArrowAnnotate, ArrowAnnotateTool); addToolInstance(toolNames.SRAngle, AngleTool); + addToolInstance(toolNames.SRPlanarFreehandROI, PlanarFreehandROITool); + addToolInstance(toolNames.SRRectangleROI, RectangleROITool); + // TODO - fix the SR display of Cobb Angle, as it joins the two lines addToolInstance(toolNames.SRCobbAngle, CobbAngleTool); - // TODO - fix the rehydration of Freehand, as it throws an exception - // on a missing polyline. The fix is probably in CS3D - addToolInstance(toolNames.SRPlanarFreehandROI, PlanarFreehandROITool); // Modify annotation tools to use dashed lines on SR const dashedLine = { lineDash: '4,4', }; annotation.config.style.setToolGroupToolStyles('SRToolGroup', { + [toolNames.DICOMSRDisplay]: dashedLine, SRLength: dashedLine, SRBidirectional: dashedLine, SREllipticalROI: dashedLine, @@ -45,6 +46,7 @@ export default function init({ configuration = {} }: Types.Extensions.ExtensionP SRCobbAngle: dashedLine, SRAngle: dashedLine, SRPlanarFreehandROI: dashedLine, + SRRectangleROI: dashedLine, global: {}, }); } diff --git a/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts b/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts index 8160ea650..eaf3bf6ea 100644 --- a/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts +++ b/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts @@ -22,14 +22,14 @@ export default class DICOMSRDisplayTool extends AnnotationTool { } _getTextBoxLinesFromLabels(labels) { - // TODO -> max 3 for now (label + shortAxis + longAxis), need a generic solution for this! + // TODO -> max 5 for now (label + shortAxis + longAxis), need a generic solution for this! - const labelLength = Math.min(labels.length, 3); + const labelLength = Math.min(labels.length, 5); const lines = []; for (let i = 0; i < labelLength; i++) { const labelEntry = labels[i]; - lines.push(`${_labelToShorthand(labelEntry.label)}${labelEntry.value}`); + lines.push(`${_labelToShorthand(labelEntry.label)}: ${labelEntry.value}`); } return lines; @@ -65,7 +65,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool { // Filter toolData to only render the data for the active SR. const filteredAnnotations = annotations.filter(annotation => - trackingUniqueIdentifiers.includes(annotation.data?.cachedStats?.TrackingUniqueIdentifier) + trackingUniqueIdentifiers.includes(annotation.data?.TrackingUniqueIdentifier) ); if (!viewport._actors?.size) { @@ -77,20 +77,24 @@ export default class DICOMSRDisplayTool extends AnnotationTool { toolName: this.getToolName(), viewportId: enabledElement.viewport.id, }; + const { style: annotationStyle } = annotation.config; for (let i = 0; i < filteredAnnotations.length; i++) { const annotation = filteredAnnotations[i]; const annotationUID = annotation.annotationUID; - const { renderableData } = annotation.data.cachedStats; - const { cachedStats } = annotation.data; + const { renderableData, TrackingUniqueIdentifier } = annotation.data; const { referencedImageId } = annotation.metadata; styleSpecifier.annotationUID = annotationUID; + const groupStyle = annotationStyle.getToolGroupToolStyles(this.toolGroupId)[ + this.getToolName() + ]; + const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation); const lineDash = this.getStyle('lineDash', styleSpecifier, annotation); const color = - cachedStats.TrackingUniqueIdentifier === activeTrackingUniqueIdentifier + TrackingUniqueIdentifier === activeTrackingUniqueIdentifier ? 'rgb(0, 255, 0)' : this.getStyle('color', styleSpecifier, annotation); @@ -98,6 +102,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool { color, lineDash, lineWidth, + ...groupStyle, }; Object.keys(renderableData).forEach(GraphicType => { @@ -160,6 +165,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool { const drawingOptions = { color: options.color, width: options.lineWidth, + lineDash: options.lineDash, }; let allCanvasCoordinates = []; renderableData.map((data, index) => { @@ -307,6 +313,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool { { color: options.color, width: options.lineWidth, + lineDash: options.lineDash, } ); }); @@ -339,7 +346,9 @@ export default class DICOMSRDisplayTool extends AnnotationTool { const textLines = this._getTextBoxLinesFromLabels(label); const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates); - annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords); + if (!annotation.data?.handles?.textBox?.worldPosition) { + annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords); + } const textBoxPosition = viewport.worldToCanvas(annotation.data.handles.textBox.worldPosition); diff --git a/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts b/extensions/cornerstone-dicom-sr/src/utils/addDICOMSRDisplayAnnotation.ts similarity index 80% rename from extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts rename to extensions/cornerstone-dicom-sr/src/utils/addDICOMSRDisplayAnnotation.ts index e78b3c7f1..585a9183a 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/addDICOMSRDisplayAnnotation.ts @@ -1,15 +1,13 @@ import { vec3 } from 'gl-matrix'; import { Types, annotation } from '@cornerstonejs/tools'; import { metaData, utilities, Types as csTypes } from '@cornerstonejs/core'; + import toolNames from '../tools/toolNames'; import SCOORD_TYPES from '../constants/scoordTypes'; const EPSILON = 1e-4; -const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0']; - -export default function addMeasurement(measurement, imageId, displaySetInstanceUID) { - // TODO -> Render rotated ellipse . +export default function addDICOMSRDisplayAnnotation(measurement, imageId, frameNumber) { const toolName = toolNames.DICOMSRDisplay; const measurementData = { @@ -27,59 +25,43 @@ export default function addMeasurement(measurement, imageId, displaySetInstanceU } measurementData.renderableData[GraphicType].push( - _getRenderableData(GraphicType, GraphicData, imageId, measurement.TrackingIdentifier) + _getRenderableData(GraphicType, GraphicData, imageId) ); }); - // Use the metadata provider to grab its imagePlaneModule metadata const imagePlaneModule = metaData.get('imagePlaneModule', imageId); - const annotationManager = annotation.state.getAnnotationManager(); - - // Create Cornerstone3D Annotation from measurement - const frameNumber = - (measurement.coords[0].ReferencedSOPSequence && - measurement.coords[0].ReferencedSOPSequence[0]?.ReferencedFrameNumber) || - 1; - + /** + * This annotation (DICOMSRDisplay) is only used by the SR viewport. + * This is used before the annotation is hydrated. If hydrated the measurement will be added + * to the measurement service and will be available for the other viewports. + */ const SRAnnotation: Types.Annotation = { annotationUID: measurement.TrackingUniqueIdentifier, + highlighted: false, + isLocked: false, + invalidated: false, metadata: { - FrameOfReferenceUID: imagePlaneModule.frameOfReferenceUID, toolName: toolName, + FrameOfReferenceUID: imagePlaneModule.frameOfReferenceUID, referencedImageId: imageId, }, data: { label: measurement.labels, handles: { - textBox: {}, + textBox: measurement.textBox ?? {}, }, - cachedStats: { - TrackingUniqueIdentifier: measurementData.TrackingUniqueIdentifier, - renderableData: measurementData.renderableData, - }, - frameNumber: frameNumber, + cachedStats: {}, + TrackingUniqueIdentifier: measurementData.TrackingUniqueIdentifier, + renderableData: measurementData.renderableData, + frameNumber, }, }; - + const annotationManager = annotation.state.getAnnotationManager(); annotationManager.addAnnotation(SRAnnotation); - - measurement.loaded = true; - measurement.imageId = imageId; - measurement.displaySetInstanceUID = displaySetInstanceUID; - - // Remove the unneeded coord now its processed, but keep the SOPInstanceUID. - // NOTE: We assume that each SCOORD in the MeasurementGroup maps onto one frame, - // It'd be super weird if it didn't anyway as a SCOORD. - measurement.ReferencedSOPInstanceUID = - measurement.coords[0].ReferencedSOPSequence.ReferencedSOPInstanceUID; - measurement.frameNumber = frameNumber; - delete measurement.coords; } -function _getRenderableData(GraphicType, GraphicData, imageId, TrackingIdentifier) { - const [cornerstoneTag, toolName] = TrackingIdentifier.split(':'); - +function _getRenderableData(GraphicType, GraphicData, imageId) { let renderableData: csTypes.Point3[]; switch (GraphicType) { diff --git a/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js b/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js index 17216a844..7dc9e657a 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js +++ b/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js @@ -90,7 +90,7 @@ export default function hydrateStructuredReport( const datasetToUse = _mapLegacyDataSet(instance); // Use dcmjs to generate toolState. - const storedMeasurementByAnnotationType = MeasurementReport.generateToolState( + let storedMeasurementByAnnotationType = MeasurementReport.generateToolState( datasetToUse, // NOTE: we need to pass in the imageIds to dcmjs since the we use them // for the imageToWorld transformation. The following assumes that the order @@ -101,6 +101,16 @@ export default function hydrateStructuredReport( metaData ); + const onBeforeSRHydration = + customizationService.getModeCustomization('onBeforeSRHydration')?.value; + + if (typeof onBeforeSRHydration === 'function') { + storedMeasurementByAnnotationType = onBeforeSRHydration({ + storedMeasurementByAnnotationType, + displaySet, + }); + } + // Filter what is found by DICOM SR to measurements we support. const mappingDefinitions = mappings.map(m => m.annotationType); const hydratableMeasurementsInSR = {}; diff --git a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx b/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx index a1e728bdd..6158df331 100644 --- a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx +++ b/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx @@ -1,35 +1,31 @@ import PropTypes from 'prop-types'; import React, { useCallback, useContext, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import OHIF, { utils, ServicesManager, ExtensionManager } from '@ohif/core'; +import { ServicesManager, ExtensionManager } from '@ohif/core'; import { setTrackingUniqueIdentifiersForElement } from '../tools/modules/dicomSRModule'; -import { Icon, Tooltip, useViewportGrid, ViewportActionBar } from '@ohif/ui'; +import { Icon, Tooltip, useViewportGrid, ViewportActionArrows } from '@ohif/ui'; import hydrateStructuredReport from '../utils/hydrateStructuredReport'; import { useAppConfig } from '@state'; - -const { formatDate } = utils; +import createReferencedImageDisplaySet from '../utils/createReferencedImageDisplaySet'; const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking'; const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup'; function OHIFCornerstoneSRViewport(props) { - const { - children, - dataSource, - displaySets, - viewportLabel, - viewportOptions, - servicesManager, - extensionManager, - } = props; + const { children, dataSource, displaySets, viewportOptions, servicesManager, extensionManager } = + props; const [appConfig] = useAppConfig(); - const { displaySetService, cornerstoneViewportService, measurementService } = - servicesManager.services; + const { + displaySetService, + cornerstoneViewportService, + measurementService, + viewportActionCornersService, + } = servicesManager.services; const viewportId = viewportOptions.viewportId; @@ -48,6 +44,8 @@ function OHIFCornerstoneSRViewport(props) { const [element, setElement] = useState(null); const { viewports, activeViewportId } = viewportGrid; + const { t } = useTranslation('Common'); + // Optional hook into tracking extension, if present. let trackedMeasurements; let sendTrackedMeasurementsEvent; @@ -128,6 +126,10 @@ function OHIFCornerstoneSRViewport(props) { console.warn('More than one SOPClassUID in the same series is not yet supported.'); } + // if (!srDisplaySet.measurements || !srDisplaySet.measurements.length) { + // return; + // } + _getViewportReferencedDisplaySetData( srDisplaySet, newMeasurementSelected, @@ -202,7 +204,10 @@ function OHIFCornerstoneSRViewport(props) { // The positionIds for the viewport aren't meaningful for the child display sets positionIds: null, }} - onElementEnabled={onElementEnabled} + onElementEnabled={evt => { + props.onElementEnabled?.(evt); + onElementEnabled(evt); + }} initialImageIndex={initialImageIndex} isJumpToMeasurementDisabled={true} > @@ -213,18 +218,11 @@ function OHIFCornerstoneSRViewport(props) { direction => { let newMeasurementSelected = measurementSelected; - if (direction === 'right') { - newMeasurementSelected++; - - if (newMeasurementSelected >= measurementCount) { - newMeasurementSelected = 0; - } - } else { - newMeasurementSelected--; - - if (newMeasurementSelected < 0) { - newMeasurementSelected = measurementCount - 1; - } + newMeasurementSelected += direction; + if (newMeasurementSelected >= measurementCount) { + newMeasurementSelected = 0; + } else if (newMeasurementSelected < 0) { + newMeasurementSelected = measurementCount - 1; } setTrackingIdentifiers(newMeasurementSelected); @@ -263,12 +261,16 @@ function OHIFCornerstoneSRViewport(props) { * if it is hydrated we don't even use the SR viewport. */ useEffect(() => { - if (!srDisplaySet.isLoaded) { - srDisplaySet.load(); - } - const numMeasurements = srDisplaySet.measurements.length; - setMeasurementCount(numMeasurements); - }, [srDisplaySet]); + const loadSR = async () => { + if (!srDisplaySet.isLoaded) { + await srDisplaySet.load(); + } + const numMeasurements = srDisplaySet.measurements.length; + setMeasurementCount(numMeasurements); + updateViewport(measurementSelected); + }; + loadSR(); + }, [dataSource, srDisplaySet]); /** * Hook to update the tracking identifiers when the selected measurement changes or @@ -285,18 +287,50 @@ function OHIFCornerstoneSRViewport(props) { * Todo: what is this, not sure what it does regarding the react aspect, * it is updating a local variable? which is not state. */ - let isLocked = trackedMeasurements?.context?.trackedSeries?.length > 0; + const [isLocked, setIsLocked] = useState(trackedMeasurements?.context?.trackedSeries?.length > 0); useEffect(() => { - isLocked = trackedMeasurements?.context?.trackedSeries?.length > 0; + setIsLocked(trackedMeasurements?.context?.trackedSeries?.length > 0); }, [trackedMeasurements]); - /** - * Data fetching for the SR displaySet, which updates the measurements and - * also gets the referenced image displaySet that SR is based on. - */ useEffect(() => { - updateViewport(measurementSelected); - }, [dataSource, srDisplaySet]); + viewportActionCornersService.setComponents([ + { + viewportId, + id: 'viewportStatusComponent', + component: _getStatusComponent({ + srDisplaySet, + viewportId, + isRehydratable: srDisplaySet.isRehydratable, + isLocked, + sendTrackedMeasurementsEvent, + t, + }), + indexPriority: -100, + location: viewportActionCornersService.LOCATIONS.topLeft, + }, + { + viewportId, + id: 'viewportActionArrowsComponent', + index: 0, + component: ( + + ), + indexPriority: 0, + location: viewportActionCornersService.LOCATIONS.topRight, + }, + ]); + }, [ + isLocked, + onMeasurementChange, + sendTrackedMeasurementsEvent, + srDisplaySet, + t, + viewportActionCornersService, + viewportId, + ]); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ let childrenWithProps = null; @@ -317,57 +351,8 @@ function OHIFCornerstoneSRViewport(props) { }); } - const { - PatientID, - PatientName, - PatientSex, - PatientAge, - SliceThickness, - ManufacturerModelName, - StudyDate, - SeriesDescription, - SpacingBetweenSlices, - SeriesNumber, - } = referencedDisplaySetMetadata; - - // TODO -> disabled double click for now: onDoubleClick={_onDoubleClick} return ( <> - { - evt.stopPropagation(); - evt.preventDefault(); - }} - onArrowsClick={onMeasurementChange} - getStatusComponent={() => - _getStatusComponent({ - srDisplaySet, - viewportId, - isTracked: false, - isRehydratable: srDisplaySet.isRehydratable, - isLocked, - sendTrackedMeasurementsEvent, - }) - } - studyData={{ - label: viewportLabel, - useAltStyling: true, - studyDate: formatDate(StudyDate), - currentSeries: SeriesNumber, - seriesDescription: SeriesDescription || '', - patientInformation: { - patientName: PatientName ? OHIF.utils.formatPN(PatientName.Alphabetic) : '', - patientSex: PatientSex || '', - patientAge: PatientAge || '', - MRN: PatientID || '', - thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '', - spacing: - SpacingBetweenSlices !== undefined ? `${SpacingBetweenSlices.toFixed(2)}mm` : '', - scanner: ManufacturerModelName || '', - }, - }} - /> -
{getCornerstoneViewport()} {childrenWithProps} @@ -384,7 +369,6 @@ OHIFCornerstoneSRViewport.propTypes = { viewportLabel: PropTypes.string, customProps: PropTypes.object, viewportOptions: PropTypes.object, - viewportLabel: PropTypes.string, servicesManager: PropTypes.instanceOf(ServicesManager).isRequired, extensionManager: PropTypes.instanceOf(ExtensionManager).isRequired, }; @@ -402,6 +386,13 @@ async function _getViewportReferencedDisplaySetData( const measurement = measurements[measurementSelected]; const { displaySetInstanceUID } = measurement; + if (!displaySet.keyImageDisplaySet) { + // Create a new display set, and preserve a reference to it here, + // so that it can be re-displayed and shown inside the SR viewport. + // This is only for ease of redisplay - the display set is stored in the + // usual manner in the display set service. + displaySet.keyImageDisplaySet = createReferencedImageDisplaySet(displaySetService, displaySet); + } const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); @@ -429,6 +420,7 @@ function _getStatusComponent({ isRehydratable, isLocked, sendTrackedMeasurementsEvent, + t, }) { const handleMouseUp = () => { sendTrackedMeasurementsEvent('HYDRATE_SR', { @@ -437,7 +429,6 @@ function _getStatusComponent({ }); }; - const { t } = useTranslation('Common'); const loadStr = t('LOAD'); // 1 - Incompatible diff --git a/extensions/cornerstone-dynamic-volume/.webpack/webpack.dev.js b/extensions/cornerstone-dynamic-volume/.webpack/webpack.dev.js new file mode 100644 index 000000000..1ae308448 --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/.webpack/webpack.dev.js @@ -0,0 +1,8 @@ +const path = require('path'); +const webpackCommon = require('./../../../.webpack/webpack.commonjs.js'); +const SRC_DIR = path.join(__dirname, '../src'); +const DIST_DIR = path.join(__dirname, '../dist'); + +module.exports = (env, argv) => { + return webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); +}; diff --git a/extensions/cornerstone-dynamic-volume/.webpack/webpack.prod.js b/extensions/cornerstone-dynamic-volume/.webpack/webpack.prod.js new file mode 100644 index 000000000..66a3e9fc1 --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/.webpack/webpack.prod.js @@ -0,0 +1,54 @@ +const webpack = require('webpack'); +const { merge } = require('webpack-merge'); +const path = require('path'); +const webpackCommon = require('./../../../.webpack/webpack.base.js'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +const pkg = require('./../package.json'); + +const ROOT_DIR = path.join(__dirname, '../'); +const SRC_DIR = path.join(__dirname, '../src'); +const DIST_DIR = path.join(__dirname, '../dist'); +const ENTRY = { + app: `${SRC_DIR}/index.ts`, +}; + +const outputName = `ohif-${pkg.name.split('/').pop()}`; + +module.exports = (env, argv) => { + const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); + + return merge(commonConfig, { + stats: { + colors: true, + hash: true, + timings: true, + assets: true, + chunks: false, + chunkModules: false, + modules: false, + children: false, + warnings: true, + }, + optimization: { + minimize: true, + sideEffects: true, + }, + output: { + path: ROOT_DIR, + library: 'ohif-extension-cornerstone', + libraryTarget: 'umd', + filename: pkg.main, + }, + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], + plugins: [ + new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1, + }), + new MiniCssExtractPlugin({ + filename: `./dist/${outputName}.css`, + chunkFilename: `./dist/${outputName}.css`, + }), + ], + }); +}; diff --git a/extensions/cornerstone-dynamic-volume/CHANGELOG.md b/extensions/cornerstone-dynamic-volume/CHANGELOG.md new file mode 100644 index 000000000..f2b924226 --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/CHANGELOG.md @@ -0,0 +1,196 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.8.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.92...v3.8.0-beta.93) (2024-04-29) + + +### Bug Fixes + +* **toolbox:** Preserve user-specified tool state and streamline command execution ([#4063](https://github.com/OHIF/Viewers/issues/4063)) ([f1a736d](https://github.com/OHIF/Viewers/commit/f1a736d1934733a434cb87b2c284907a3122403f)) + + + + + +# [3.8.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.91...v3.8.0-beta.92) (2024-04-28) + + +### Bug Fixes + +* **bugs:** fix patient header for doc, track ball rotate resize observer and add segmentation button not being enabled on viewport data change ([#4068](https://github.com/OHIF/Viewers/issues/4068)) ([c09311d](https://github.com/OHIF/Viewers/commit/c09311d3b7df05fcd00a9f36a7233e9d7e5589d0)) + + + + + +# [3.8.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.90...v3.8.0-beta.91) (2024-04-25) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume + + + + + +# [3.8.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.89...v3.8.0-beta.90) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume + + + + + +# [3.8.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.88...v3.8.0-beta.89) (2024-04-22) + + +### Bug Fixes + +* **viewport-webworker-segmentation:** Resolve issues with viewport detection, webworker termination, and segmentation panel layout change ([#4059](https://github.com/OHIF/Viewers/issues/4059)) ([52a0c59](https://github.com/OHIF/Viewers/commit/52a0c59294a4161fcca0a6708855549034849951)) + + + + + +# [3.8.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.87...v3.8.0-beta.88) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume + + + + + +# [3.8.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.86...v3.8.0-beta.87) (2024-04-19) + + +### Features + +* **tmtv-mode:** Add Brush tools and move SUV peak calculation to web worker ([#4053](https://github.com/OHIF/Viewers/issues/4053)) ([8192e34](https://github.com/OHIF/Viewers/commit/8192e348eca993fec331d4963efe88f9a730eceb)) + + + + + +# [3.8.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.85...v3.8.0-beta.86) (2024-04-19) + + +### Bug Fixes + +* **layouts:** and fix thumbnail in touch and update migration guide for 3.8 release ([#4052](https://github.com/OHIF/Viewers/issues/4052)) ([d250d04](https://github.com/OHIF/Viewers/commit/d250d04580883446fcb8d748b2a97c5c198922af)) + + + + + +# [3.8.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.84...v3.8.0-beta.85) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume + + + + + +# [3.8.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.83...v3.8.0-beta.84) (2024-04-18) + + +### Bug Fixes + +* **bugs:** and replace seriesInstanceUID and seriesInstanceUIDs URL with seriesInstanceUIDs ([#4049](https://github.com/OHIF/Viewers/issues/4049)) ([da7c1a5](https://github.com/OHIF/Viewers/commit/da7c1a5d8c54bfa1d3f97bbc500386bf76e7fd9d)) + + + + + +# [3.8.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.82...v3.8.0-beta.83) (2024-04-18) + + +### Bug Fixes + +* **bugs:** enhancements and bug fixes - final ([#4048](https://github.com/OHIF/Viewers/issues/4048)) ([170bb96](https://github.com/OHIF/Viewers/commit/170bb96983082c39b22b7352e0c54aacf3e73b02)) + + + + + +# [3.8.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.81...v3.8.0-beta.82) (2024-04-17) + + +### Bug Fixes + +* **bugs:** enhancements and bug fixes - more ([#4043](https://github.com/OHIF/Viewers/issues/4043)) ([3754c22](https://github.com/OHIF/Viewers/commit/3754c224b4dab28182adb0a41e37d890942144d8)) + + + + + +# [3.8.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.80...v3.8.0-beta.81) (2024-04-16) + + +### Bug Fixes + +* **viewport:** Reset viewport state and fix CINE looping, thumbnail resolution, and dynamic tool settings ([#4037](https://github.com/OHIF/Viewers/issues/4037)) ([f99a0bf](https://github.com/OHIF/Viewers/commit/f99a0bfb31434aa137bbb3ed1f9eef1dfcc09025)) + + + + + +# [3.8.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.79...v3.8.0-beta.80) (2024-04-16) + + +### Bug Fixes + +* **bugs:** enhancements and bug fixes ([#4036](https://github.com/OHIF/Viewers/issues/4036)) ([e80fc6f](https://github.com/OHIF/Viewers/commit/e80fc6f47708e1d6b1a1e1de438196a4b74ec637)) + + + + + +# [3.8.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.78...v3.8.0-beta.79) (2024-04-10) + + +### Features + +* **SM:** remove SM measurements from measurement panel ([#4022](https://github.com/OHIF/Viewers/issues/4022)) ([df49a65](https://github.com/OHIF/Viewers/commit/df49a653be61a93f6e9fb3663aabe9775c31fd13)) + + + + + +# [3.8.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.77...v3.8.0-beta.78) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume + + + + + +# [3.8.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.76...v3.8.0-beta.77) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume + + + + + +# [3.8.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.75...v3.8.0-beta.76) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume + + + + + +# [3.8.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.74...v3.8.0-beta.75) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume + + + + + +# [3.8.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.73...v3.8.0-beta.74) (2024-04-10) + + +### Features + +* **4D:** Add 4D dynamic volume rendering and new pre-clinical 4d pt/ct mode ([#3664](https://github.com/OHIF/Viewers/issues/3664)) ([d57e8bc](https://github.com/OHIF/Viewers/commit/d57e8bc1571c6da4effaa492ee2d162c552365a2)) diff --git a/extensions/cornerstone-dynamic-volume/LICENSE b/extensions/cornerstone-dynamic-volume/LICENSE new file mode 100644 index 000000000..24728a704 --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/LICENSE @@ -0,0 +1,20 @@ +MIT License + +Copyright (c) 2023 cornerstone-dynamic-volume () + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/extensions/cornerstone-dynamic-volume/README.md b/extensions/cornerstone-dynamic-volume/README.md new file mode 100644 index 000000000..70949801a --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/README.md @@ -0,0 +1,8 @@ +# cornerstone-dynamic-volume +## Description + +## Author +OHIF + +## License +MIT diff --git a/extensions/cornerstone-dynamic-volume/babel.config.js b/extensions/cornerstone-dynamic-volume/babel.config.js new file mode 100644 index 000000000..325ca2a8e --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/babel.config.js @@ -0,0 +1 @@ +module.exports = require('../../babel.config.js'); diff --git a/extensions/cornerstone-dynamic-volume/package.json b/extensions/cornerstone-dynamic-volume/package.json new file mode 100644 index 000000000..58fea632a --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/package.json @@ -0,0 +1,50 @@ +{ + "name": "@ohif/extension-cornerstone-dynamic-volume", + "version": "3.8.0-beta.93", + "description": "OHIF extension for 4D volumes data", + "author": "OHIF", + "license": "MIT", + "repository": "OHIF/Viewers", + "main": "dist/ohif-extension-cornerstone-dynamic-volume.umd.js", + "module": "src/index.ts", + "exports": { + ".": "./src/index.ts", + "./types": "./src/types/index.ts" + }, + "files": [ + "dist", + "README.md" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", + "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", + "build:package": "yarn run build", + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", + "start": "yarn run dev", + "test:unit": "jest --watchAll", + "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" + }, + "peerDependencies": { + "@ohif/core": "3.8.0-beta.93", + "@ohif/extension-cornerstone": "3.8.0-beta.93", + "@ohif/extension-default": "3.8.0-beta.93", + "@ohif/i18n": "3.8.0-beta.93", + "@ohif/ui": "3.8.0-beta.93", + "dcmjs": "^0.29.5", + "dicom-parser": "^1.8.21", + "hammerjs": "^2.0.8", + "prop-types": "^15.6.2", + "react": "^17.0.2" + }, + "dependencies": { + "@babel/runtime": "^7.20.13", + "@cornerstonejs/core": "^1.70.14", + "@cornerstonejs/streaming-image-volume-loader": "^1.70.14", + "@cornerstonejs/tools": "^1.70.14", + "classnames": "^2.3.2" + } +} diff --git a/extensions/cornerstone-dynamic-volume/src/actions/index.ts b/extensions/cornerstone-dynamic-volume/src/actions/index.ts new file mode 100644 index 000000000..45d2adfc2 --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/src/actions/index.ts @@ -0,0 +1,3 @@ +import updateSegmentationsChartDisplaySet from './updateSegmentationsChartDisplaySet'; + +export { updateSegmentationsChartDisplaySet }; diff --git a/extensions/cornerstone-dynamic-volume/src/actions/updateSegmentationsChartDisplaySet.ts b/extensions/cornerstone-dynamic-volume/src/actions/updateSegmentationsChartDisplaySet.ts new file mode 100644 index 000000000..810d98488 --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/src/actions/updateSegmentationsChartDisplaySet.ts @@ -0,0 +1,281 @@ +import { DicomMetadataStore, utils } from '@ohif/core'; + +import * as cs from '@cornerstonejs/core'; +import * as csTools from '@cornerstonejs/tools'; + +const CHART_MODALITY = 'CHT'; +const SEG_CHART_INSTANCE_UID = utils.guid(); + +// Private SOPClassUid for chart data +const ChartDataSOPClassUid = '1.9.451.13215.7.3.2.7.6.1'; + +const { utilities: csToolsUtils } = csTools; + +function _getDateTimeStr() { + const now = new Date(); + const date = + now.getFullYear() + ('0' + now.getUTCMonth()).slice(-2) + ('0' + now.getUTCDate()).slice(-2); + const time = + ('0' + now.getUTCHours()).slice(-2) + + ('0' + now.getUTCMinutes()).slice(-2) + + ('0' + now.getUTCSeconds()).slice(-2); + + return { date, time }; +} + +function _getTimePointsDataByTagName(volume, timePointsTag) { + const uniqueTimePoints = volume.imageIds.reduce((timePoints, imageId) => { + const instance = DicomMetadataStore.getInstanceByImageId(imageId); + const timePointValue = instance[timePointsTag]; + + if (timePointValue !== undefined) { + timePoints.add(timePointValue); + } + + return timePoints; + }, new Set()); + + return Array.from(uniqueTimePoints).sort((a: number, b: number) => a - b); +} + +function _convertTimePointsUnit(timePoints, timePointsUnit) { + const validUnits = ['ms', 's', 'm', 'h']; + const divisors = [1000, 60, 60]; + const currentUnitIndex = validUnits.indexOf(timePointsUnit); + let divisor = 1; + + if (currentUnitIndex !== -1) { + for (let i = currentUnitIndex; i < validUnits.length - 1; i++) { + const newDivisor = divisor * divisors[i]; + const greaterThanDivisorCount = timePoints.filter(timePoint => timePoint > newDivisor).length; + + // Change the scale only if more than 50% of the time points are + // greater than the new divisor. + if (greaterThanDivisorCount <= timePoints.length / 2) { + break; + } + + divisor = newDivisor; + timePointsUnit = validUnits[i + 1]; + } + + if (divisor > 1) { + timePoints = timePoints.map(timePoint => timePoint / divisor); + } + } + + return { timePoints, timePointsUnit }; +} + +// It currently supports only one tag but a few other will be added soon +// Supported 4D Tags +// (0018,1060) Trigger Time [NOK] +// (0018,0081) Echo Time [NOK] +// (0018,0086) Echo Number [NOK] +// (0020,0100) Temporal Position Identifier [NOK] +// (0054,1300) FrameReferenceTime [OK] +function _getTimePointsData(volume) { + const timePointsTags = { + FrameReferenceTime: { + unit: 'ms', + }, + }; + + const timePointsTagNames = Object.keys(timePointsTags); + let timePoints; + let timePointsUnit; + + for (let i = 0; i < timePointsTagNames.length; i++) { + const tagName = timePointsTagNames[i]; + const curTimePoints = _getTimePointsDataByTagName(volume, tagName); + + if (curTimePoints.length) { + timePoints = curTimePoints; + timePointsUnit = timePointsTags[tagName].unit; + break; + } + } + + if (!timePoints.length) { + const concatTagNames = timePointsTagNames.join(', '); + + throw new Error(`Could not extract time points data for the following tags: ${concatTagNames}`); + } + + const convertedTimePoints = _convertTimePointsUnit(timePoints, timePointsUnit); + + timePoints = convertedTimePoints.timePoints; + timePointsUnit = convertedTimePoints.timePointsUnit; + + return { timePoints, timePointsUnit }; +} + +function _getSegmentationData(segmentation, volumesTimePointsCache, displaySetService) { + const displaySets = displaySetService.getActiveDisplaySets(); + + const dynamic4DDisplaySet = displaySets.find(displaySet => { + const anInstance = displaySet.instances?.[0]; + + if (anInstance) { + return ( + anInstance.FrameReferenceTime !== undefined || anInstance.NumberOfTimeSlices !== undefined + ); + } + + return false; + }); + + // const referencedDynamicVolume = cs.cache.getVolume(dynamic4DDisplaySet.displaySetInstanceUID); + let volumeCacheKey: string | undefined; + const volumeId = dynamic4DDisplaySet.displaySetInstanceUID; + + for (const [key] of cs.cache._volumeCache) { + if (key.includes(volumeId)) { + volumeCacheKey = key; + break; + } + } + + let referencedDynamicVolume; + if (volumeCacheKey) { + referencedDynamicVolume = cs.cache.getVolume(volumeCacheKey); + } + + const { StudyInstanceUID, StudyDescription } = DicomMetadataStore.getInstanceByImageId( + referencedDynamicVolume.imageIds[0] + ); + + const [timeData, _] = csToolsUtils.dynamicVolume.getDataInTime(referencedDynamicVolume, { + maskVolumeId: segmentation.id, + }) as number[][]; + + const pixelCount = timeData.length; + + if (pixelCount === 0) { + return []; + } + + // since we only use one segmentation representation per segmentationId + // it is fine to pick the first one + const segmentationRepresentations = csTools.segmentation.state.getSegmentationIdRepresentations( + segmentation.id + ); + + const segmentationRepresentationUID = + segmentationRepresentations[0].segmentationRepresentationUID; + + const toolGroupId = csTools.segmentation.state.getToolGroupIdFromSegmentationRepresentationUID( + segmentationRepresentationUID + ); + + // Todo: this is useless we should be able to grab color with just segRepUID and segmentIndex + const color = csTools.segmentation.config.color.getColorForSegmentIndex( + toolGroupId, + segmentationRepresentationUID, + 1 // segmentIndex + ); + + const hexColor = cs.utilities.color.rgbToHex(...color); + let timePointsData = volumesTimePointsCache.get(referencedDynamicVolume); + + if (!timePointsData) { + timePointsData = _getTimePointsData(referencedDynamicVolume); + volumesTimePointsCache.set(referencedDynamicVolume, timePointsData); + } + + const { timePoints, timePointsUnit } = timePointsData; + + if (timePoints.length !== timeData[0].length) { + throw new Error('Invalid number of time points returned'); + } + + const timepointsCount = timePoints.length; + const chartSeriesData = new Array(timepointsCount); + + for (let i = 0; i < timepointsCount; i++) { + const average = timeData.reduce((acc, cur) => acc + cur[i] / pixelCount, 0); + + chartSeriesData[i] = [timePoints[i], average]; + } + + return { + StudyInstanceUID, + StudyDescription, + chartData: { + series: { + label: segmentation.label, + points: chartSeriesData, + color: hexColor, + }, + axis: { + x: { + label: `Time (${timePointsUnit})`, + }, + y: { + label: `Vl (Bq/ml)`, + }, + }, + }, + }; +} + +function _getInstanceFromSegmentations(segmentations, displaySetService) { + if (!segmentations.length) { + return; + } + + const volumesTimePointsCache = new WeakMap(); + const segmentationsData = segmentations.map(segmentation => + _getSegmentationData(segmentation, volumesTimePointsCache, displaySetService) + ); + + const { date: seriesDate, time: seriesTime } = _getDateTimeStr(); + const series = segmentationsData.reduce((allSeries, curSegData) => { + return [...allSeries, curSegData.chartData.series]; + }, []); + + const instance = { + SOPClassUID: ChartDataSOPClassUid, + Modality: CHART_MODALITY, + SOPInstanceUID: utils.guid(), + SeriesDate: seriesDate, + SeriesTime: seriesTime, + SeriesInstanceUID: SEG_CHART_INSTANCE_UID, + StudyInstanceUID: segmentationsData[0].StudyInstanceUID, + StudyDescription: segmentationsData[0].StudyDescription, + SeriesNumber: 100, + SeriesDescription: 'Segmentation chart series data', + chartData: { + series, + axis: { ...segmentationsData[0].chartData.axis }, + }, + }; + + const seriesMetadata = { + StudyInstanceUID: instance.StudyInstanceUID, + StudyDescription: instance.StudyDescription, + SeriesInstanceUID: instance.SeriesInstanceUID, + SeriesDescription: instance.SeriesDescription, + SeriesNumber: instance.SeriesNumber, + SeriesTime: instance.SeriesTime, + SOPClassUID: instance.SOPClassUID, + Modality: instance.Modality, + }; + + return { seriesMetadata, instance }; +} + +function updateSegmentationsChartDisplaySet({ servicesManager }): void { + const { segmentationService, displaySetService } = servicesManager.services; + const segmentations = segmentationService.getSegmentations(); + const { seriesMetadata, instance } = + _getInstanceFromSegmentations(segmentations, displaySetService) ?? {}; + + if (seriesMetadata && instance) { + // An event is triggered after adding the instance and the displaySet is created + DicomMetadataStore.addSeriesMetadata([seriesMetadata], true); + DicomMetadataStore.addInstances([instance], true); + } +} + +export { updateSegmentationsChartDisplaySet as default }; diff --git a/extensions/cornerstone-dynamic-volume/src/commandsModule.ts b/extensions/cornerstone-dynamic-volume/src/commandsModule.ts new file mode 100644 index 000000000..a6cdf6cda --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/src/commandsModule.ts @@ -0,0 +1,407 @@ +import * as importedActions from './actions'; +import { utilities, Enums } from '@cornerstonejs/tools'; +import { cache } from '@cornerstonejs/core'; + +const LABELMAP = Enums.SegmentationRepresentations.Labelmap; + +const commandsModule = ({ commandsManager, servicesManager }) => { + const services = servicesManager.services; + const { displaySetService, viewportGridService, segmentationService } = services; + + const actions = { + ...importedActions, + getDynamic4DDisplaySet: () => { + const displaySets = displaySetService.getActiveDisplaySets(); + + const dynamic4DDisplaySet = displaySets.find(displaySet => { + const anInstance = displaySet.instances?.[0]; + + if (anInstance) { + return ( + anInstance.FrameReferenceTime !== undefined || + anInstance.NumberOfTimeSlices !== undefined || + anInstance.TemporalPositionIdentifier !== undefined + ); + } + + return false; + }); + + return dynamic4DDisplaySet; + }, + getComputedDisplaySets: () => { + const displaySetCache = displaySetService.getDisplaySetCache(); + const cachedDisplaySets = [...displaySetCache.values()]; + const computedDisplaySets = cachedDisplaySets.filter(displaySet => { + return displaySet.isDerived; + }); + return computedDisplaySets; + }, + exportTimeReportCSV: ({ segmentations, config, options, summaryStats }) => { + const dynamic4DDisplaySet = actions.getDynamic4DDisplaySet(); + + const volumeId = dynamic4DDisplaySet?.displaySetInstanceUID; + + // cache._volumeCache is a map that has a key that includes the volumeId + // it is not exactly the volumeId, but it is the key that includes the volumeId + // so we can't do cache._volumeCache.get(volumeId) we should iterate + // over the keys and find the one that includes the volumeId + let volumeCacheKey: string | undefined; + + for (const [key] of cache._volumeCache) { + if (key.includes(volumeId)) { + volumeCacheKey = key; + break; + } + } + + let dynamicVolume; + if (volumeCacheKey) { + dynamicVolume = cache.getVolume(volumeCacheKey); + } + + const instance = dynamic4DDisplaySet.instances[0]; + + const csv = []; + + // CSV header information with placeholder empty values for the metadata lines + csv.push(`Patient ID,${instance.PatientID},`); + csv.push(`Study Date,${instance.StudyDate},`); + csv.push(`StudyInstanceUID,${instance.StudyInstanceUID},`); + csv.push(`StudyDescription,${instance.StudyDescription},`); + csv.push(`SeriesInstanceUID,${instance.SeriesInstanceUID},`); + + // empty line + csv.push(''); + csv.push(''); + + // Helper function to calculate standard deviation + function calculateStandardDeviation(data) { + const n = data.length; + const mean = data.reduce((acc, value) => acc + value, 0) / n; + const squaredDifferences = data.map(value => (value - mean) ** 2); + const variance = squaredDifferences.reduce((acc, value) => acc + value, 0) / n; + const stdDeviation = Math.sqrt(variance); + return stdDeviation; + } + + // Iterate through each segmentation to get the timeData and ijkCoords + segmentations.forEach((segmentation, segmentationIndex) => { + const [timeData, ijkCoords] = utilities.dynamicVolume.getDataInTime(dynamicVolume, { + maskVolumeId: segmentation.id, + }) as number[][]; + + if (summaryStats) { + // Adding column headers for pixel identifier and segmentation label ids + let headers = 'Operation,Segmentation Label ID'; + const maxLength = dynamicVolume.numTimePoints; + for (let t = 0; t < maxLength; t++) { + headers += `,Time Point ${t}`; + } + csv.push(headers); + // // perform summary statistics on the timeData including for each time point, mean, median, min, max, and standard deviation for + // // all the voxels in the ROI + const mean = []; + const min = []; + const minIJK = []; + const max = []; + const maxIJK = []; + const std = []; + + const numVoxels = timeData.length; + // Helper function to calculate standard deviation + for (let timeIndex = 0; timeIndex < maxLength; timeIndex++) { + // for each voxel in the ROI, get the value at the current time point + const voxelValues = []; + for (let voxelIndex = 0; voxelIndex < numVoxels; voxelIndex++) { + voxelValues.push(timeData[voxelIndex][timeIndex]); + } + + mean.push(voxelValues.reduce((acc, value) => acc + value, 0) / numVoxels); + const minimum = Math.min(...voxelValues); + min.push(minimum); + minIJK.push(ijkCoords[voxelValues.indexOf(minimum)]); + const maximum = Math.max(...voxelValues); + max.push(maximum); + maxIJK.push(ijkCoords[voxelValues.indexOf(maximum)]); + std.push(calculateStandardDeviation(voxelValues)); + } + + let row = `Mean,${segmentation.label}`; + // Generate separate rows for each statistic + for (let t = 0; t < maxLength; t++) { + row += `,${mean[t]}`; + } + + csv.push(row); + + row = `Standard Deviation,${segmentation.label}`; + for (let t = 0; t < maxLength; t++) { + row += `,${std[t]}`; + } + + csv.push(row); + + row = `Min,${segmentation.label}`; + for (let t = 0; t < maxLength; t++) { + row += `,${min[t]}`; + } + + csv.push(row); + + row = `Max,${segmentation.label}`; + for (let t = 0; t < maxLength; t++) { + row += `,${max[t]}`; + } + + csv.push(row); + } else { + // Adding column headers for pixel identifier and segmentation label ids + let headers = 'Pixel Identifier (IJK),Segmentation Label ID'; + const maxLength = dynamicVolume.numTimePoints; + for (let t = 0; t < maxLength; t++) { + headers += `,Time Point ${t}`; + } + csv.push(headers); + // Assuming timeData and ijkCoords are of the same length + for (let i = 0; i < timeData.length; i++) { + // Generate the pixel identifier + const pixelIdentifier = `${ijkCoords[i][0]}_${ijkCoords[i][1]}_${ijkCoords[i][2]}`; + + // Start a new row for the current pixel + let row = `${pixelIdentifier},${segmentation.label}`; + + // Add time data points for this pixel + for (let t = 0; t < timeData[i].length; t++) { + row += `,${timeData[i][t]}`; + } + + // Append the row to the CSV array + csv.push(row); + } + } + }); + + // Convert to CSV string + const csvContent = csv.join('\n'); + + // Generate filename and trigger download + const filename = `${instance.PatientID}.csv`; + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const link = document.createElement('a'); + const url = URL.createObjectURL(blob); + link.setAttribute('href', url); + link.setAttribute('download', filename); + link.style.visibility = 'hidden'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }, + swapDynamicWithComputedDisplaySet: ({ displaySet }) => { + const computedDisplaySet = displaySet; + + const displaySetCache = displaySetService.getDisplaySetCache(); + const cachedDisplaySetKeys = [displaySetCache.keys()]; + const { displaySetInstanceUID } = computedDisplaySet; + // Check to see if computed display set is already in cache + if (!cachedDisplaySetKeys.includes(displaySetInstanceUID)) { + displaySetCache.set(displaySetInstanceUID, computedDisplaySet); + } + + // Get all viewports and their corresponding indices + const { viewports } = viewportGridService.getState(); + + // get the viewports in the grid + // iterate over them and find the ones that are showing a dynamic + // volume (displaySet), and replace that exact displaySet with the + // computed displaySet + + const dynamic4DDisplaySet = actions.getDynamic4DDisplaySet(); + + const viewportsToUpdate = []; + + for (const [key, value] of viewports) { + const viewport = value; + const viewportOptions = viewport.viewportOptions; + const { displaySetInstanceUIDs } = viewport; + const displaySetInstanceUIDIndex = displaySetInstanceUIDs.indexOf( + dynamic4DDisplaySet.displaySetInstanceUID + ); + if (displaySetInstanceUIDIndex !== -1) { + const newViewport = { + viewportId: viewport.viewportId, + // merge the other displaySetInstanceUIDs with the new one + displaySetInstanceUIDs: [ + ...displaySetInstanceUIDs.slice(0, displaySetInstanceUIDIndex), + displaySetInstanceUID, + ...displaySetInstanceUIDs.slice(displaySetInstanceUIDIndex + 1), + ], + viewportOptions: { + initialImageOptions: viewportOptions.initialImageOptions, + viewportType: 'volume', + orientation: viewportOptions.orientation, + background: viewportOptions.background, + }, + }; + viewportsToUpdate.push(newViewport); + } + } + + viewportGridService.setDisplaySetsForViewports(viewportsToUpdate); + }, + swapComputedWithDynamicDisplaySet: () => { + // Todo: this assumes there is only one dynamic display set in the viewer + const dynamicDisplaySet = actions.getDynamic4DDisplaySet(); + + const displaySetCache = displaySetService.getDisplaySetCache(); + const cachedDisplaySetKeys = [...displaySetCache.keys()]; // Fix: Spread to get the array + const { displaySetInstanceUID } = dynamicDisplaySet; + + // Check to see if dynamic display set is already in cache + if (!cachedDisplaySetKeys.includes(displaySetInstanceUID)) { + displaySetCache.set(displaySetInstanceUID, dynamicDisplaySet); + } + + // Get all viewports and their corresponding indices + const { viewports } = viewportGridService.getState(); + + // Get the computed 4D display set + const computed4DDisplaySet = actions.getComputedDisplaySets()[0]; + + const viewportsToUpdate = []; + + for (const [key, value] of viewports) { + const viewport = value; + const viewportOptions = viewport.viewportOptions; + const { displaySetInstanceUIDs } = viewport; + const displaySetInstanceUIDIndex = displaySetInstanceUIDs.indexOf( + computed4DDisplaySet.displaySetInstanceUID + ); + if (displaySetInstanceUIDIndex !== -1) { + const newViewport = { + viewportId: viewport.viewportId, + // merge the other displaySetInstanceUIDs with the new one + displaySetInstanceUIDs: [ + ...displaySetInstanceUIDs.slice(0, displaySetInstanceUIDIndex), + displaySetInstanceUID, + ...displaySetInstanceUIDs.slice(displaySetInstanceUIDIndex + 1), + ], + viewportOptions: { + initialImageOptions: viewportOptions.initialImageOptions, + viewportType: 'volume', + orientation: viewportOptions.orientation, + background: viewportOptions.background, + }, + }; + viewportsToUpdate.push(newViewport); + } + } + + viewportGridService.setDisplaySetsForViewports(viewportsToUpdate); + }, + createNewLabelMapForDynamicVolume: async ({ label }) => { + const { viewports, activeViewportId } = viewportGridService.getState(); + + // get the dynamic 4D display set + const dynamic4DDisplaySet = actions.getDynamic4DDisplaySet(); + const dynamic4DDisplaySetInstanceUID = dynamic4DDisplaySet.displaySetInstanceUID; + + // check if the dynamic 4D display set is in the display, if not we might have + // the computed volumes and we should choose them for the segmentation + // creation + + let referenceDisplaySet; + + const activeViewport = viewports.get(activeViewportId); + const activeDisplaySetInstanceUIDs = activeViewport.displaySetInstanceUIDs; + const dynamicIsInActiveViewport = activeDisplaySetInstanceUIDs.includes( + dynamic4DDisplaySetInstanceUID + ); + + if (dynamicIsInActiveViewport) { + referenceDisplaySet = dynamic4DDisplaySet; + } + + if (!referenceDisplaySet) { + // try to see if there is any derived displaySet in the active viewport + // which is referencing the dynamic 4D display set + + // Todo: this is wrong but I don't have time to fix it now + const cachedDisplaySets = displaySetService.getDisplaySetCache(); + for (const [key, displaySet] of cachedDisplaySets) { + if (displaySet.referenceDisplaySetUID === dynamic4DDisplaySetInstanceUID) { + referenceDisplaySet = displaySet; + break; + } + } + } + + if (!referenceDisplaySet) { + throw new Error('No reference display set found based on the dynamic data'); + } + + const segmentationId = await segmentationService.createSegmentationForDisplaySet( + referenceDisplaySet.displaySetInstanceUID, + { label } + ); + + // Add Segmentation to all toolGroupIds in the viewer + const toolGroupIds = Array.from( + viewports.values(), + viewport => viewport.viewportOptions.toolGroupId + ); + + const representationType = LABELMAP; + + for (const toolGroupId of toolGroupIds) { + const hydrateSegmentation = true; + await segmentationService.addSegmentationRepresentationToToolGroup( + toolGroupId, + segmentationId, + hydrateSegmentation, + representationType + ); + + segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId); + } + + return segmentationId; + }, + }; + + const definitions = { + updateSegmentationsChartDisplaySet: { + commandFn: actions.updateSegmentationsChartDisplaySet, + storeContexts: [], + options: {}, + }, + exportTimeReportCSV: { + commandFn: actions.exportTimeReportCSV, + storeContexts: [], + options: {}, + }, + swapDynamicWithComputedDisplaySet: { + commandFn: actions.swapDynamicWithComputedDisplaySet, + storeContexts: [], + options: {}, + }, + createNewLabelMapForDynamicVolume: { + commandFn: actions.createNewLabelMapForDynamicVolume, + storeContexts: [], + options: {}, + }, + swapComputedWithDynamicDisplaySet: { + commandFn: actions.swapComputedWithDynamicDisplaySet, + storeContexts: [], + options: {}, + }, + }; + + return { + actions, + definitions, + defaultContext: 'DYNAMIC-VOLUME:CORNERSTONE', + }; +}; + +export default commandsModule; diff --git a/extensions/cornerstone-dynamic-volume/src/getHangingProtocolModule.ts b/extensions/cornerstone-dynamic-volume/src/getHangingProtocolModule.ts new file mode 100644 index 000000000..0a8b6d520 --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/src/getHangingProtocolModule.ts @@ -0,0 +1,654 @@ +const DEFAULT_COLORMAP = '2hot'; +const toolGroupIds = { + pt: 'dynamic4D-pt', + fusion: 'dynamic4D-fusion', + ct: 'dynamic4D-ct', +}; + +function getPTOptions({ + colormap, + voiInverted, +}: { + colormap?: { + name: string; + opacity: + | number + | { + value: number; + opacity: number; + }[]; + }; + voiInverted?: boolean; +} = {}) { + return { + blendMode: 'MIP', + colormap, + voi: { + windowWidth: 5, + windowCenter: 2.5, + }, + voiInverted, + }; +} + +function getPTViewports() { + const ptOptionsParams = { + colormap: { + name: DEFAULT_COLORMAP, + opacity: [ + { value: 0, opacity: 0 }, + { value: 0.1, opacity: 1 }, + { value: 1, opacity: 1 }, + ], + }, + voiInverted: false, + }; + + return [ + { + viewportOptions: { + viewportId: 'ptAxial', + viewportType: 'volume', + orientation: 'axial', + toolGroupId: toolGroupIds.pt, + initialImageOptions: { + preset: 'middle', // 'first', 'last', 'middle' + }, + syncGroups: [ + { + type: 'cameraPosition', + id: 'axialSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ptWLSync', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'ptDisplaySet', + options: { ...getPTOptions(ptOptionsParams) }, + }, + ], + }, + { + viewportOptions: { + viewportId: 'ptSagittal', + viewportType: 'volume', + orientation: 'sagittal', + toolGroupId: toolGroupIds.pt, + initialImageOptions: { + preset: 'middle', // 'first', 'last', 'middle' + }, + syncGroups: [ + { + type: 'cameraPosition', + id: 'sagittalSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ptWLSync', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'ptDisplaySet', + options: { ...getPTOptions(ptOptionsParams) }, + }, + ], + }, + { + viewportOptions: { + viewportId: 'ptCoronal', + viewportType: 'volume', + orientation: 'coronal', + toolGroupId: toolGroupIds.pt, + initialImageOptions: { + preset: 'middle', // 'first', 'last', 'middle' + }, + syncGroups: [ + { + type: 'cameraPosition', + id: 'coronalSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ptWLSync', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'ptDisplaySet', + options: { ...getPTOptions(ptOptionsParams) }, + }, + ], + }, + ]; +} + +function getFusionViewports() { + const ptOptionsParams = { + colormap: { + name: DEFAULT_COLORMAP, + opacity: [ + { value: 0, opacity: 0 }, + { value: 0.1, opacity: 0.3 }, + { value: 1, opacity: 0.3 }, + ], + }, + }; + + return [ + { + viewportOptions: { + viewportId: 'fusionAxial', + viewportType: 'volume', + orientation: 'axial', + toolGroupId: toolGroupIds.fusion, + initialImageOptions: { + preset: 'middle', // 'first', 'last', 'middle' + }, + syncGroups: [ + { + type: 'cameraPosition', + id: 'axialSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ctWLSync', + source: false, + target: true, + }, + { + type: 'voi', + id: 'fusionWLSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ptFusionWLSync', + source: false, + target: true, + options: { + syncInvertState: false, + }, + }, + ], + }, + displaySets: [ + { + id: 'ctDisplaySet', + }, + { + options: { ...getPTOptions(ptOptionsParams) }, + id: 'ptDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'fusionSagittal', + viewportType: 'volume', + orientation: 'sagittal', + toolGroupId: toolGroupIds.fusion, + initialImageOptions: { + preset: 'middle', // 'first', 'last', 'middle' + }, + syncGroups: [ + { + type: 'cameraPosition', + id: 'sagittalSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ctWLSync', + source: false, + target: true, + }, + { + type: 'voi', + id: 'fusionWLSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ptFusionWLSync', + source: false, + target: true, + options: { + syncInvertState: false, + }, + }, + ], + }, + displaySets: [ + { + id: 'ctDisplaySet', + }, + { + options: { ...getPTOptions(ptOptionsParams) }, + id: 'ptDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'fusionCoronal', + viewportType: 'volume', + orientation: 'coronal', + toolGroupId: toolGroupIds.fusion, + initialImageOptions: { + preset: 'middle', // 'first', 'last', 'middle' + }, + syncGroups: [ + { + type: 'cameraPosition', + id: 'coronalSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ctWLSync', + source: false, + target: true, + }, + { + type: 'voi', + id: 'fusionWLSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ptFusionWLSync', + source: false, + target: true, + options: { + syncInvertState: false, + }, + }, + ], + }, + displaySets: [ + { + id: 'ctDisplaySet', + }, + { + options: { ...getPTOptions(ptOptionsParams) }, + id: 'ptDisplaySet', + }, + ], + }, + ]; +} + +function getSeriesChartViewport() { + return { + viewportOptions: { + viewportId: 'seriesChart', + }, + displaySets: [ + { + id: 'chartDisplaySet', + options: { + // This dataset does not require the download of any instance since it is pre-computed locally, + // but interleaveTopToBottom.ts was not loading any series because it consider that all viewports + // are a Cornerstone viewport which is not true in this case and it waits for all viewports to + // have called interleaveTopToBottom(...). + skipLoading: true, + }, + }, + ], + }; +} + +function getCTViewports() { + return [ + { + viewportOptions: { + viewportId: 'ctAxial', + viewportType: 'volume', + orientation: 'axial', + toolGroupId: toolGroupIds.ct, + initialImageOptions: { + preset: 'middle', // 'first', 'last', 'middle' + }, + syncGroups: [ + { + type: 'cameraPosition', + id: 'axialSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ctWLSync', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'ctDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'ctSagittal', + viewportType: 'volume', + orientation: 'sagittal', + toolGroupId: toolGroupIds.ct, + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'cameraPosition', + id: 'sagittalSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ctWLSync', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'ctDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'ctCoronal', + viewportType: 'volume', + orientation: 'coronal', + toolGroupId: toolGroupIds.ct, + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'cameraPosition', + id: 'coronalSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ctWLSync', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'ctDisplaySet', + }, + ], + }, + ]; +} + +const defaultProtocol = { + id: 'default4D', + locked: true, + // Don't store this hanging protocol as it applies to the currently active + // display set by default + // cacheId: null, + hasUpdatedPriorsInformation: false, + name: 'Default', + createdDate: '2023-01-01T00:00:00.000Z', + modifiedDate: '2023-01-01T00:00:00.000Z', + availableTo: {}, + editableBy: {}, + imageLoadStrategy: 'default', // "default" , "interleaveTopToBottom", "interleaveCenter" + protocolMatchingRules: [ + { + attribute: 'ModalitiesInStudy', + constraint: { + contains: ['CT', 'PT'], + }, + }, + ], + // -1 would be used to indicate active only, whereas other values are + // the number of required priors referenced - so 0 means active with + // 0 or more priors. + numberOfPriorsReferenced: -1, + displaySetSelectors: { + defaultDisplaySetId: { + // Unused currently + imageMatchingRules: [], + // Matches displaysets, NOT series + seriesMatchingRules: [ + // Try to match series with images by default, to prevent weird display + // on SEG/SR containing studies + { + attribute: 'numImageFrames', + constraint: { + greaterThan: { value: 0 }, + }, + }, + ], + // Can be used to select matching studies + // studyMatchingRules: [], + }, + ctDisplaySet: { + // Unused currently + imageMatchingRules: [], + // Matches displaysets, NOT series + seriesMatchingRules: [ + { + attribute: 'Modality', + constraint: { + equals: { + value: 'CT', + }, + }, + required: true, + }, + { + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + ], + // Can be used to select matching studies + // studyMatchingRules: [], + }, + ptDisplaySet: { + // Unused currently + imageMatchingRules: [], + // Matches displaysets, NOT series + seriesMatchingRules: [ + { + attribute: 'Modality', + constraint: { + equals: 'PT', + }, + required: true, + }, + { + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + { + attribute: 'SeriesDescription', + constraint: { + contains: 'Corrected', + }, + }, + { + weight: 2, + attribute: 'SeriesDescription', + constraint: { + doesNotContain: { + value: 'Uncorrected', + }, + }, + }, + + // Should we check if CorrectedImage contains ATTN? + // (0028,0051) (CorrectedImage): NORM\DTIM\ATTN\SCAT\RADL\DECY + ], + // Can be used to select matching studies + // studyMatchingRules: [], + }, + chartDisplaySet: { + // Unused currently + imageMatchingRules: [], + // Matches displaysets, NOT series + seriesMatchingRules: [ + { + attribute: 'Modality', + constraint: { + equals: { + value: 'CHT', + }, + }, + required: true, + }, + ], + }, + }, + stages: [ + { + id: 'dataPreparation', + name: 'Data Preparation', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 1, + columns: 3, + }, + }, + viewports: [...getPTViewports()], + createdDate: '2023-01-01T00:00:00.000Z', + }, + + { + id: 'registration', + name: 'Registration', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 3, + columns: 3, + }, + }, + viewports: [...getFusionViewports(), ...getCTViewports(), ...getPTViewports()], + createdDate: '2023-01-01T00:00:00.000Z', + }, + + { + id: 'roiQuantification', + name: 'ROI Quantification', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 1, + columns: 3, + }, + }, + viewports: [...getFusionViewports()], + createdDate: '2023-01-01T00:00:00.000Z', + }, + + { + id: 'kineticAnalysis', + name: 'Kinetic Analysis', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 2, + columns: 3, + layoutOptions: [ + { + x: 0, + y: 0, + width: 1 / 3, + height: 1 / 2, + }, + { + x: 1 / 3, + y: 0, + width: 1 / 3, + height: 1 / 2, + }, + { + x: 2 / 3, + y: 0, + width: 1 / 3, + height: 1 / 2, + }, + { + x: 0, + y: 1 / 2, + width: 1, + height: 1 / 2, + }, + ], + }, + }, + viewports: [...getFusionViewports(), getSeriesChartViewport()], + createdDate: '2023-01-01T00:00:00.000Z', + }, + ], +}; + +/** + * HangingProtocolModule should provide a list of hanging protocols that will be + * available in OHIF for Modes to use to decide on the structure of the viewports + * and also the series that hung in the viewports. Each hanging protocol is defined by + * { name, protocols}. Examples include the default hanging protocol provided by + * the default extension that shows 2x2 viewports. + */ + +function getHangingProtocolModule() { + return [ + { + name: defaultProtocol.id, + protocol: defaultProtocol, + }, + ]; +} + +export default getHangingProtocolModule; diff --git a/extensions/cornerstone-dynamic-volume/src/getPanelModule.tsx b/extensions/cornerstone-dynamic-volume/src/getPanelModule.tsx new file mode 100644 index 000000000..41f453847 --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/src/getPanelModule.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { DynamicDataPanel } from './panels'; +import { Toolbox } from '@ohif/ui'; +import DynamicExport from './panels/DynamicExport'; + +function getPanelModule({ commandsManager, extensionManager, servicesManager }) { + const wrappedDynamicDataPanel = () => { + return ( + + ); + }; + + const wrappedDynamicToolbox = () => { + return ( + <> + + + ); + }; + + const wrappedDynamicExport = () => { + return ( + <> + + + ); + }; + + return [ + { + name: 'dynamic-volume', + iconName: 'tab-4d', + iconLabel: '4D Workflow', + label: '4D Workflow', + component: wrappedDynamicDataPanel, + }, + { + name: 'dynamic-toolbox', + iconName: 'tab-4d', + iconLabel: '4D Workflow', + label: 'Dynamic Toolbox', + component: wrappedDynamicToolbox, + }, + { + name: 'dynamic-export', + iconName: 'tab-4d', + iconLabel: '4D Workflow', + label: '4D Workflow', + component: wrappedDynamicExport, + }, + ]; +} + +export default getPanelModule; diff --git a/extensions/cornerstone-dynamic-volume/src/id.js b/extensions/cornerstone-dynamic-volume/src/id.js new file mode 100644 index 000000000..b2dfe1809 --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/src/id.js @@ -0,0 +1,6 @@ +import packageJson from '../package.json'; + +const id = packageJson.name; +const SOPClassHandlerName = 'dynamic-volume'; + +export { id, SOPClassHandlerName }; diff --git a/extensions/cornerstone-dynamic-volume/src/index.ts b/extensions/cornerstone-dynamic-volume/src/index.ts new file mode 100644 index 000000000..625899efe --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/src/index.ts @@ -0,0 +1,57 @@ +import { id } from './id'; +import commandsModule from './commandsModule'; +import getPanelModule from './getPanelModule'; +import getHangingProtocolModule from './getHangingProtocolModule'; +import { cache } from '@cornerstonejs/core'; + +/** + * You can remove any of the following modules if you don't need them. + */ +const dynamicVolumeExtension = { + /** + * Only required property. Should be a unique value across all extensions. + * You ID can be anything you want, but it should be unique. + */ + id, + + /** + * Perform any pre-registration tasks here. This is called before the extension + * is registered. Usually we run tasks such as: configuring the libraries + * (e.g. cornerstone, cornerstoneTools, ...) or registering any services that + * this extension is providing. + */ + preRegistration: ({ servicesManager, commandsManager, configuration = {} }) => { + // TODO: look for the right fix + cache.setMaxCacheSize(5 * 1024 * 1024 * 1024); + }, + /** + * PanelModule should provide a list of panels that will be available in OHIF + * for Modes to consume and render. Each panel is defined by a {name, + * iconName, iconLabel, label, component} object. Example of a panel module + * is the StudyBrowserPanel that is provided by the default extension in OHIF. + */ + getPanelModule, + /** + * ViewportModule should provide a list of viewports that will be available in OHIF + * for Modes to consume and use in the viewports. Each viewport is defined by + * {name, component} object. Example of a viewport module is the CornerstoneViewport + * that is provided by the Cornerstone extension in OHIF. + */ + getHangingProtocolModule, + /** + * CommandsModule should provide a list of commands that will be available in OHIF + * for Modes to consume and use in the viewports. Each command is defined by + * an object of { actions, definitions, defaultContext } where actions is an + * object of functions, definitions is an object of available commands, their + * options, and defaultContext is the default context for the command to run against. + */ + getCommandsModule: ({ servicesManager, commandsManager, extensionManager }) => { + return commandsModule({ + servicesManager, + commandsManager, + extensionManager, + }); + }, +}; + +export { dynamicVolumeExtension as default }; diff --git a/extensions/cornerstone-dynamic-volume/src/panels/DynamicDataPanel.tsx b/extensions/cornerstone-dynamic-volume/src/panels/DynamicDataPanel.tsx new file mode 100644 index 000000000..d885d2e95 --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/src/panels/DynamicDataPanel.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import PanelGenerateImage from './PanelGenerateImage'; + +function DynamicDataPanel({ servicesManager, commandsManager }) { + return ( +
+ +
+ ); +} + +export default DynamicDataPanel; diff --git a/extensions/cornerstone-dynamic-volume/src/panels/DynamicExport.tsx b/extensions/cornerstone-dynamic-volume/src/panels/DynamicExport.tsx new file mode 100644 index 000000000..020278c4b --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/src/panels/DynamicExport.tsx @@ -0,0 +1,76 @@ +import React, { useState, useEffect } from 'react'; +import { ActionButtons } from '@ohif/ui'; +import { useTranslation } from 'react-i18next'; + +function DynamicExport({ commandsManager, servicesManager, extensionManager }) { + const { segmentationService } = servicesManager.services; + const { t } = useTranslation('dynamicExport'); + + const [segmentations, setSegmentations] = useState(() => segmentationService.getSegmentations()); + + const actions = [ + { + label: 'Export Time Data', + onClick: () => { + commandsManager.runCommand('exportTimeReportCSV', { + segmentations, + options: { + filename: 'TimeData.csv', + }, + }); + }, + disabled: !segmentations?.length, + }, + { + label: 'Export ROI Stats', + onClick: () => { + commandsManager.runCommand('exportTimeReportCSV', { + segmentations, + summaryStats: true, + options: { + filename: 'ROIStats.csv', + }, + }); + }, + disabled: !segmentations?.length, + }, + ]; + + /** + * Update UI based on segmentation changes (added, removed, updated) + */ + useEffect(() => { + // ~~ Subscription + const added = segmentationService.EVENTS.SEGMENTATION_ADDED; + const updated = segmentationService.EVENTS.SEGMENTATION_UPDATED; + const removed = segmentationService.EVENTS.SEGMENTATION_REMOVED; + const subscriptions = []; + + [added, updated, removed].forEach(evt => { + const { unsubscribe } = segmentationService.subscribe(evt, () => { + const segmentations = segmentationService.getSegmentations(); + setSegmentations(segmentations); + }); + subscriptions.push(unsubscribe); + }); + + return () => { + subscriptions.forEach(unsub => { + unsub(); + }); + }; + }, []); + + return ( +
+
+ +
+
+ ); +} + +export default DynamicExport; diff --git a/extensions/cornerstone-dynamic-volume/src/panels/DynamicVolumeControls.tsx b/extensions/cornerstone-dynamic-volume/src/panels/DynamicVolumeControls.tsx new file mode 100644 index 000000000..8a72dd593 --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/src/panels/DynamicVolumeControls.tsx @@ -0,0 +1,241 @@ +import React, { useEffect, useState } from 'react'; +import { + InputDoubleRange, + Button, + PanelSection, + ButtonGroup, + IconButton, + InputNumber, + Icon, + Tooltip, +} from '@ohif/ui'; + +import { Enums } from '@cornerstonejs/core'; + +const controlClassNames = { + sizeClassName: 'w-[58px] h-[28px]', + arrowsDirection: 'horizontal', + labelPosition: 'bottom', +}; + +const Header = ({ title, tooltip }) => ( +
+ {tooltip}
} + position="bottom-left" + tight={true} + tooltipBoxClassName="max-w-xs p-2" + > + + + {title} +
+); + +const DynamicVolumeControls = ({ + isPlaying, + onPlayPauseChange, + // fps + fps, + onFpsChange, + minFps, + maxFps, + // Frames + currentFrameIndex, + onFrameChange, + framesLength, + onGenerate, + onDoubleRangeChange, + onDynamicClick, +}) => { + const [computedView, setComputedView] = useState(false); + + const [computeViewMode, setComputeViewMode] = useState(Enums.DynamicOperatorType.SUM); + + const [sliderRangeValues, setSliderRangeValues] = useState([framesLength / 4, framesLength / 2]); + + useEffect(() => { + setSliderRangeValues([framesLength / 4, framesLength / 2]); + }, [framesLength]); + + const handleSliderChange = newValues => { + onDoubleRangeChange(newValues); + + if (newValues[0] === sliderRangeValues[0] && newValues[1] === sliderRangeValues[1]) { + return; + } + setSliderRangeValues(newValues); + }; + + return ( +
+ +
+
+ + + + +
+
+ +
+
+
+ Operation Buttons (SUM, AVERAGE, SUBTRACT): Select the mathematical operation to be + applied to the data set. +

Range Slider: Choose the numeric range within which the operation will be + performed. +

Generate Button: Execute the chosen operation on the specified range of + data.{' '} +
+ } + /> + + + + + +
+ +
+ +
+ +
+ ); +}; + +export default DynamicVolumeControls; + +function FrameControls({ + isPlaying, + onPlayPauseChange, + fps, + minFps, + maxFps, + onFpsChange, + framesLength, + onFrameChange, + currentFrameIndex, + computedView, +}) { + const getPlayPauseIconName = () => (isPlaying ? 'icon-pause' : 'icon-play'); + + return ( +
+
+ Play/Pause Button: Begin or pause the animation of the 4D visualization.

Frame + Selector: Navigate through individual frames of the 4D data.

FPS (Frames Per + Second) Selector: Adjust the playback speed of the animation. +
+ } + /> +
+ onPlayPauseChange(!isPlaying)} + > + + + + +
+
+ ); +} diff --git a/extensions/cornerstone-dynamic-volume/src/panels/GenerateVolume.tsx b/extensions/cornerstone-dynamic-volume/src/panels/GenerateVolume.tsx new file mode 100644 index 000000000..516502a67 --- /dev/null +++ b/extensions/cornerstone-dynamic-volume/src/panels/GenerateVolume.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { InputDoubleRange } from '@ohif/ui'; +import { Select } from '@ohif/ui'; +import { Button } from '@ohif/ui'; +import PropTypes from 'prop-types'; + +const GenerateVolume = ({ + rangeValues, + handleSliderChange, + operationsUI, + options, + handleGenerateOptionsChange, + onGenerateImage, + returnTo4D, + displayingComputedVolume, +}) => { + return ( + <> +
+
Computed Image
+ { + setAmbient(e.target.value); + onAmbientChange(); + }} + id="ambient" + max={1} + min={0} + type="range" + step={0.1} + style={{ + background: calculateBackground(ambient), + '--thumb-inner-color': '#5acce6', + '--thumb-outer-color': '#090c29', + }} + /> + )} +
+
+ + {diffuse !== null && ( + { + setDiffuse(e.target.value); + onDiffuseChange(); + }} + id="diffuse" + max={1} + min={0} + type="range" + step={0.1} + style={{ + background: calculateBackground(diffuse), + '--thumb-inner-color': '#5acce6', + '--thumb-outer-color': '#090c29', + }} + /> + )} +
+ +
+ + {specular !== null && ( + { + setSpecular(e.target.value); + onSpecularChange(); + }} + id="specular" + max={1} + min={0} + type="range" + step={0.1} + style={{ + background: calculateBackground(specular), + '--thumb-inner-color': '#5acce6', + '--thumb-outer-color': '#090c29', + }} + /> + )} +
+ + ); +} diff --git a/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingOptions.tsx b/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingOptions.tsx new file mode 100644 index 000000000..1e0901ad3 --- /dev/null +++ b/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingOptions.tsx @@ -0,0 +1,46 @@ +import React, { ReactElement } from 'react'; +import { AllInOneMenu } from '@ohif/ui'; +import { VolumeRenderingOptionsProps } from '../../types/ViewportPresets'; +import { VolumeRenderingQuality } from './VolumeRenderingQuality'; +import { VolumeShift } from './VolumeShift'; +import { VolumeLighting } from './VolumeLighting'; +import { VolumeShade } from './VolumeShade'; +export function VolumeRenderingOptions({ + viewportId, + commandsManager, + volumeRenderingQualityRange, + serviceManager, +}: VolumeRenderingOptionsProps): ReactElement { + return ( + + + + +
+
LIGHTING
+
+
+
+ +
+ +
+ ); +} diff --git a/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresets.tsx b/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresets.tsx new file mode 100644 index 000000000..bbf2c5705 --- /dev/null +++ b/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresets.tsx @@ -0,0 +1,38 @@ +import { AllInOneMenu, Icon } from '@ohif/ui'; +import React, { ReactElement } from 'react'; +import { VolumeRenderingPresetsProps } from '../../types/ViewportPresets'; +import { VolumeRenderingPresetsContent } from './VolumeRenderingPresetsContent'; + +export function VolumeRenderingPresets({ + viewportId, + serviceManager, + commandsManager, + volumeRenderingPresets, +}: VolumeRenderingPresetsProps): ReactElement { + const { uiModalService } = serviceManager.services; + + const onClickPresets = () => { + uiModalService.show({ + content: VolumeRenderingPresetsContent, + title: 'Rendering Presets', + movable: true, + contentProps: { + onClose: uiModalService.hide, + presets: volumeRenderingPresets, + viewportId, + commandsManager, + }, + containerDimensions: 'h-[543px] w-[460px]', + contentDimensions: 'h-[493px] w-[460px] pl-[12px] pr-[12px]', + }); + }; + + return ( + } + rightIcon={} + onClick={onClickPresets} + /> + ); +} diff --git a/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresetsContent.tsx b/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresetsContent.tsx new file mode 100644 index 000000000..07c9abcc9 --- /dev/null +++ b/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresetsContent.tsx @@ -0,0 +1,95 @@ +import { Icon } from '@ohif/ui'; +import { ButtonEnums } from '@ohif/ui'; +import React, { ReactElement, useState, useCallback } from 'react'; +import { Button, InputFilterText } from '@ohif/ui'; +import { ViewportPreset, VolumeRenderingPresetsContentProps } from '../../types/ViewportPresets'; + +export function VolumeRenderingPresetsContent({ + presets, + viewportId, + commandsManager, + onClose, +}: VolumeRenderingPresetsContentProps): ReactElement { + const [filteredPresets, setFilteredPresets] = useState(presets); + const [searchValue, setSearchValue] = useState(''); + const [selectedPreset, setSelectedPreset] = useState(null); + + const handleSearchChange = useCallback( + (value: string) => { + setSearchValue(value); + const filtered = value + ? presets.filter(preset => preset.name.toLowerCase().includes(value.toLowerCase())) + : presets; + setFilteredPresets(filtered); + }, + [presets] + ); + + const handleApply = useCallback( + props => { + commandsManager.runCommand('setViewportPreset', { + ...props, + }); + }, + [commandsManager] + ); + + const formatLabel = (label: string, maxChars: number) => { + return label.length > maxChars ? `${label.slice(0, maxChars)}...` : label; + }; + + return ( +
+
+
+
+ +
+
+
+
+ {filteredPresets.map((preset, index) => ( +
{ + setSelectedPreset(preset); + handleApply({ preset: preset.name, viewportId }); + }} + > + + +
+ ))} +
+
+
+
+
+ +
+
+
+ ); +} diff --git a/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingQuality.tsx b/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingQuality.tsx new file mode 100644 index 000000000..7a6fce9a6 --- /dev/null +++ b/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingQuality.tsx @@ -0,0 +1,73 @@ +import React, { ReactElement, useCallback, useState, useEffect } from 'react'; +import { VolumeRenderingQualityProps } from '../../types/ViewportPresets'; + +export function VolumeRenderingQuality({ + volumeRenderingQualityRange, + commandsManager, + serviceManager, + viewportId, +}: VolumeRenderingQualityProps): ReactElement { + const { cornerstoneViewportService } = serviceManager.services; + const { min, max, step } = volumeRenderingQualityRange; + const [quality, setQuality] = useState(null); + + const onChange = useCallback( + (value: number) => { + commandsManager.runCommand('setVolumeRenderingQulaity', { + viewportId, + volumeQuality: value, + }); + setQuality(value); + }, + [commandsManager, viewportId] + ); + + const calculateBackground = value => { + const percentage = ((value - 0) / (1 - 0)) * 100; + return `linear-gradient(to right, #5acce6 0%, #5acce6 ${percentage}%, #3a3f99 ${percentage}%, #3a3f99 100%)`; + }; + + useEffect(() => { + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + const { actor } = viewport.getActors()[0]; + const mapper = actor.getMapper(); + const image = mapper.getInputData(); + const spacing = image.getSpacing(); + const sampleDistance = mapper.getSampleDistance(); + const averageSpacing = spacing.reduce((a, b) => a + b) / 3.0; + if (sampleDistance === averageSpacing) { + setQuality(1); + } else { + setQuality(Math.sqrt(averageSpacing / (sampleDistance * 0.5))); + } + }, [cornerstoneViewportService, viewportId]); + return ( + <> +
+ + {quality !== null && ( + onChange(parseInt(e.target.value, 10))} + style={{ + background: calculateBackground((quality - min) / (max - min)), + '--thumb-inner-color': '#5acce6', + '--thumb-outer-color': '#090c29', + }} + /> + )} +
+ + ); +} diff --git a/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShade.tsx b/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShade.tsx new file mode 100644 index 000000000..319158dab --- /dev/null +++ b/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShade.tsx @@ -0,0 +1,39 @@ +import React, { ReactElement, useCallback, useEffect, useState } from 'react'; +import { SwitchButton } from '@ohif/ui'; +import { VolumeShadeProps } from '../../types/ViewportPresets'; + +export function VolumeShade({ + commandsManager, + viewportId, + serviceManager, +}: VolumeShadeProps): ReactElement { + const { cornerstoneViewportService } = serviceManager.services; + const [shade, setShade] = useState(true); + const [key, setKey] = useState(0); + + const onShadeChange = useCallback( + (checked: boolean) => { + commandsManager.runCommand('setVolumeLighting', { viewportId, options: { shade: checked } }); + }, + [commandsManager, viewportId] + ); + useEffect(() => { + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + const { actor } = viewport.getActors()[0]; + const shade = actor.getProperty().getShade(); + setShade(shade); + setKey(key + 1); + }, [viewportId, cornerstoneViewportService]); + + return ( + { + setShade(!shade); + onShadeChange(!shade); + }} + /> + ); +} diff --git a/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShift.tsx b/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShift.tsx new file mode 100644 index 000000000..274f71599 --- /dev/null +++ b/extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShift.tsx @@ -0,0 +1,93 @@ +import React, { ReactElement, useCallback, useEffect, useState, useRef } from 'react'; +import { VolumeShiftProps } from '../../types/ViewportPresets'; + +export function VolumeShift({ + viewportId, + commandsManager, + serviceManager, +}: VolumeShiftProps): ReactElement { + const { cornerstoneViewportService } = serviceManager.services; + const [minShift, setMinShift] = useState(null); + const [maxShift, setMaxShift] = useState(null); + const [shift, setShift] = useState( + cornerstoneViewportService.getCornerstoneViewport(viewportId)?.shiftedBy || 0 + ); + const [step, setStep] = useState(null); + const [isBlocking, setIsBlocking] = useState(false); + + const prevShiftRef = useRef(shift); + + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + const { actor } = viewport.getActors()[0]; + const ofun = actor.getProperty().getScalarOpacity(0); + + useEffect(() => { + if (isBlocking) { + return; + } + const range = ofun.getRange(); + + const transferFunctionWidth = range[1] - range[0]; + + const minShift = -transferFunctionWidth; + const maxShift = transferFunctionWidth; + + setMinShift(minShift); + setMaxShift(maxShift); + setStep(Math.pow(10, Math.floor(Math.log10(transferFunctionWidth / 500)))); + }, [cornerstoneViewportService, viewportId, actor, ofun, isBlocking]); + + const onChangeRange = useCallback( + newShift => { + const shiftDifference = newShift - prevShiftRef.current; + prevShiftRef.current = newShift; + viewport.shiftedBy = newShift; + commandsManager.runCommand('shiftVolumeOpacityPoints', { + viewportId, + shift: shiftDifference, + }); + }, + [commandsManager, viewportId, viewport] + ); + + const calculateBackground = value => { + const percentage = ((value - 0) / (1 - 0)) * 100; + return `linear-gradient(to right, #5acce6 0%, #5acce6 ${percentage}%, #3a3f99 ${percentage}%, #3a3f99 100%)`; + }; + + return ( + <> +
+ + {step !== null && ( + { + const shiftValue = parseInt(e.target.value, 10); + setShift(shiftValue); + onChangeRange(shiftValue); + }} + id="shift" + onMouseDown={() => setIsBlocking(true)} + onMouseUp={() => setIsBlocking(false)} + max={maxShift} + min={minShift} + type="range" + step={step} + style={{ + background: calculateBackground((shift - minShift) / (maxShift - minShift)), + '--thumb-inner-color': '#5acce6', + '--thumb-outer-color': '#090c29', + }} + /> + )} +
+ + ); +} diff --git a/extensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevel.tsx b/extensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevel.tsx new file mode 100644 index 000000000..eda52d5bb --- /dev/null +++ b/extensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevel.tsx @@ -0,0 +1,57 @@ +import React, { ReactElement, useCallback } from 'react'; +import { AllInOneMenu } from '@ohif/ui'; +import { WindowLevelPreset } from '../../types/WindowLevel'; +import { CommandsManager } from '@ohif/core'; +import { useTranslation } from 'react-i18next'; + +export type WindowLevelProps = { + viewportId: string; + presets: Array>>; + commandsManager: CommandsManager; +}; + +export function WindowLevel({ + viewportId, + commandsManager, + presets, +}: WindowLevelProps): ReactElement { + const { t } = useTranslation('WindowLevelActionMenu'); + + const onSetWindowLevel = useCallback( + props => { + commandsManager.run({ + commandName: 'setViewportWindowLevel', + commandOptions: { + ...props, + viewportId, + }, + context: 'CORNERSTONE', + }); + }, + [commandsManager, viewportId] + ); + + return ( + + {presets.map((modalityPresets, modalityIndex) => ( + + {Object.entries(modalityPresets).map(([modality, presetsArray]) => ( + + + {t('Modality Presets', { modality })} + + {presetsArray.map((preset, index) => ( + onSetWindowLevel(preset)} + /> + ))} + + ))} + + ))} + + ); +} diff --git a/extensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevelActionMenu.tsx b/extensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevelActionMenu.tsx new file mode 100644 index 000000000..c6f20158f --- /dev/null +++ b/extensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevelActionMenu.tsx @@ -0,0 +1,198 @@ +import React, { ReactElement, useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import classNames from 'classnames'; +import { AllInOneMenu, useViewportGrid } from '@ohif/ui'; +import { CommandsManager, ServicesManager } from '@ohif/core'; +import { Colormap } from './Colormap'; +import { Colorbar } from './Colorbar'; +import { setViewportColorbar } from './Colorbar'; +import { WindowLevelPreset } from '../../types/WindowLevel'; +import { ColorbarProperties } from '../../types/Colorbar'; +import { VolumeRenderingQualityRange } from '../../types/ViewportPresets'; +import { WindowLevel } from './WindowLevel'; +import { VolumeRenderingPresets } from './VolumeRenderingPresets'; +import { VolumeRenderingOptions } from './VolumeRenderingOptions'; +import { ViewportPreset } from '../../types/ViewportPresets'; +import { VolumeViewport3D } from '@cornerstonejs/core'; +import { utilities } from '@cornerstonejs/core'; + +export type WindowLevelActionMenuProps = { + viewportId: string; + element: HTMLElement; + presets: Array>>; + verticalDirection: AllInOneMenu.VerticalDirection; + horizontalDirection: AllInOneMenu.HorizontalDirection; + commandsManager: CommandsManager; + serviceManager: ServicesManager; + colorbarProperties: ColorbarProperties; + displaySets: Array; + volumeRenderingPresets: Array; + volumeRenderingQualityRange: VolumeRenderingQualityRange; +}; + +export function WindowLevelActionMenu({ + viewportId, + element, + presets, + verticalDirection, + horizontalDirection, + commandsManager, + serviceManager, + colorbarProperties, + displaySets, + volumeRenderingPresets, + volumeRenderingQualityRange, +}: WindowLevelActionMenuProps): ReactElement { + const { + colormaps, + colorbarContainerPosition, + colorbarInitialColormap, + colorbarTickPosition, + width: colorbarWidth, + } = colorbarProperties; + const { colorbarService, cornerstoneViewportService } = serviceManager.services; + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + const backgroundColor = viewportInfo.getViewportOptions().background; + const isLight = backgroundColor ? utilities.isEqual(backgroundColor, [1, 1, 1]) : false; + + const nonImageModalities = ['SR', 'SEG', 'SM', 'RTSTRUCT', 'RTPLAN', 'RTDOSE']; + + const { t } = useTranslation('WindowLevelActionMenu'); + + const [viewportGrid] = useViewportGrid(); + const { activeViewportId } = viewportGrid; + + const [vpHeight, setVpHeight] = useState(element?.clientHeight); + const [menuKey, setMenuKey] = useState(0); + const [is3DVolume, setIs3DVolume] = useState(false); + + const onSetColorbar = useCallback(() => { + setViewportColorbar(viewportId, displaySets, commandsManager, serviceManager, { + colormaps, + ticks: { + position: colorbarTickPosition, + }, + width: colorbarWidth, + position: colorbarContainerPosition, + activeColormapName: colorbarInitialColormap, + }); + }, [commandsManager]); + + useEffect(() => { + const newVpHeight = element?.clientHeight; + if (vpHeight !== newVpHeight) { + setVpHeight(newVpHeight); + } + }, [element, vpHeight]); + + useEffect(() => { + if (!colorbarService.hasColorbar(viewportId)) { + return; + } + window.setTimeout(() => { + colorbarService.removeColorbar(viewportId); + onSetColorbar(); + }, 0); + }, [viewportId, displaySets, viewport]); + + useEffect(() => { + setMenuKey(menuKey + 1); + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + if (viewport instanceof VolumeViewport3D) { + setIs3DVolume(true); + } else { + setIs3DVolume(false); + } + }, [ + displaySets, + viewportId, + presets, + volumeRenderingQualityRange, + volumeRenderingPresets, + colorbarProperties, + activeViewportId, + viewportGrid, + ]); + + return ( + { + setVpHeight(element.clientHeight); + }} + menuKey={menuKey} + > + + {!is3DVolume && ( + !nonImageModalities.includes(ds.Modality))} + commandsManager={commandsManager} + serviceManager={serviceManager} + colorbarProperties={colorbarProperties} + /> + )} + + {colormaps && !is3DVolume && ( + + !nonImageModalities.includes(ds.Modality))} + commandsManager={commandsManager} + serviceManager={serviceManager} + /> + + )} + + {presets && presets.length > 0 && !is3DVolume && ( + + + + )} + + {volumeRenderingPresets && is3DVolume && ( + + )} + + {volumeRenderingQualityRange && is3DVolume && ( + + + + )} + + + ); +} diff --git a/extensions/cornerstone/src/components/WindowLevelActionMenu/defaultWindowLevelPresets.ts b/extensions/cornerstone/src/components/WindowLevelActionMenu/defaultWindowLevelPresets.ts new file mode 100644 index 000000000..f12a7530b --- /dev/null +++ b/extensions/cornerstone/src/components/WindowLevelActionMenu/defaultWindowLevelPresets.ts @@ -0,0 +1,23 @@ +// The following are the default window level presets and can be further +// configured via the customization service. +const defaultWindowLevelPresets = { + CT: [ + { description: 'Soft tissue', window: '400', level: '40' }, + { description: 'Lung', window: '1500', level: '-600' }, + { description: 'Liver', window: '150', level: '90' }, + { description: 'Bone', window: '2500', level: '480' }, + { description: 'Brain', window: '80', level: '40' }, + ], + + PT: [ + { description: 'Default', window: '5', level: '2.5' }, + { description: 'SUV', window: '0', level: '3' }, + { description: 'SUV', window: '0', level: '5' }, + { description: 'SUV', window: '0', level: '7' }, + { description: 'SUV', window: '0', level: '8' }, + { description: 'SUV', window: '0', level: '10' }, + { description: 'SUV', window: '0', level: '15' }, + ], +}; + +export default defaultWindowLevelPresets; diff --git a/extensions/cornerstone/src/components/WindowLevelActionMenu/getWindowLevelActionMenu.tsx b/extensions/cornerstone/src/components/WindowLevelActionMenu/getWindowLevelActionMenu.tsx new file mode 100644 index 000000000..b07fa7899 --- /dev/null +++ b/extensions/cornerstone/src/components/WindowLevelActionMenu/getWindowLevelActionMenu.tsx @@ -0,0 +1,42 @@ +import React, { ReactNode } from 'react'; +import { WindowLevelActionMenu } from './WindowLevelActionMenu'; + +export function getWindowLevelActionMenu({ + viewportId, + element, + displaySets, + servicesManager, + commandsManager, + verticalDirection, + horizontalDirection, +}): ReactNode { + const { customizationService } = servicesManager.services; + + const { presets } = customizationService.get('cornerstone.windowLevelPresets'); + const colorbarProperties = customizationService.get('cornerstone.colorbar'); + const { volumeRenderingPresets, volumeRenderingQualityRange } = customizationService.get( + 'cornerstone.3dVolumeRendering' + ); + + const displaySetPresets = displaySets + .filter(displaySet => presets[displaySet.Modality]) + .map(displaySet => { + return { [displaySet.Modality]: presets[displaySet.Modality] }; + }); + + return ( + + ); +} diff --git a/extensions/cornerstone/src/contextProviders/ViewportActionCornersProvider.tsx b/extensions/cornerstone/src/contextProviders/ViewportActionCornersProvider.tsx new file mode 100644 index 000000000..37dab07e6 --- /dev/null +++ b/extensions/cornerstone/src/contextProviders/ViewportActionCornersProvider.tsx @@ -0,0 +1,159 @@ +import React, { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useReducer, +} from 'react'; +import PropTypes from 'prop-types'; + +import { Types, ViewportActionCornersLocations } from '@ohif/ui'; +import ViewportActionCornersService, { + ActionComponentInfo, +} from '../services/ViewportActionCornersService/ViewportActionCornersService'; + +interface StateComponentInfo extends Types.ViewportActionCornersComponentInfo { + indexPriority: number; +} + +type State = Record>>; + +const DEFAULT_STATE: State = { + // default here is the viewportId of the default viewport + default: { + [ViewportActionCornersLocations.topLeft]: [], + [ViewportActionCornersLocations.topRight]: [], + [ViewportActionCornersLocations.bottomLeft]: [], + [ViewportActionCornersLocations.bottomRight]: [], + }, + // [anotherViewportId]: { ..... } +}; + +export const ViewportActionCornersContext = createContext(DEFAULT_STATE); + +export function ViewportActionCornersProvider({ children, service }) { + const viewportActionCornersReducer = (state, action) => { + switch (action.type) { + case 'SET_ACTION_COMPONENT': { + const { viewportId, id, component, location, indexPriority = 0 } = action.payload; + // Get the components at the specified location of the specified viewport. + let locationComponents = state?.[viewportId]?.[location] + ? [...state[viewportId][location]] + : []; + + // If the component (id) already exists at the location specified in the payload, + // then it must be replaced with the component in the payload so first + // remove it from that location. + const deletionIndex = locationComponents.findIndex(component => component.id === id); + if (deletionIndex !== -1) { + locationComponents = [ + ...locationComponents.slice(0, deletionIndex), + ...locationComponents.slice(deletionIndex + 1), + ]; + } + + // Insert the component from the payload but + // do not insert an undefined or null component. + if (component) { + const insertionIndex = locationComponents.findIndex( + component => indexPriority <= component.indexPriority + ); + locationComponents = [ + ...locationComponents.slice(0, insertionIndex), + { + id, + component, + indexPriority, + }, + ...locationComponents.slice(insertionIndex + 1), + ]; + } + + return { + ...state, + ...{ + [viewportId]: { + ...state[viewportId], + [location]: locationComponents, + }, + }, + }; + } + case 'CLEAR_ACTION_COMPONENTS': { + const viewportId = action.payload; + const nextState = { ...state }; + delete nextState[viewportId]; + return nextState; + } + default: + return { ...state }; + } + }; + + const [viewportActionCornersState, dispatch] = useReducer( + viewportActionCornersReducer, + DEFAULT_STATE + ); + + const getState = useCallback(() => { + return viewportActionCornersState; + }, [viewportActionCornersState]); + + const setComponent = useCallback( + (actionComponentInfo: ActionComponentInfo) => { + dispatch({ type: 'SET_ACTION_COMPONENT', payload: actionComponentInfo }); + }, + [dispatch] + ); + + const setComponents = useCallback( + (actionComponentInfos: Array) => { + actionComponentInfos.forEach(actionComponentInfo => + dispatch({ type: 'SET_ACTION_COMPONENT', payload: actionComponentInfo }) + ); + }, + [dispatch] + ); + + const clear = useCallback( + (viewportId: string) => dispatch({ type: 'CLEAR_ACTION_COMPONENTS', payload: viewportId }), + [dispatch] + ); + useEffect(() => { + if (service) { + service.setServiceImplementation({ + getState, + setComponent, + setComponents, + clear, + }); + } + }, [getState, service, setComponent, setComponents]); + + // run many of the calls through the service itself since we want to publish events + const api = { + getState, + setComponent: props => service.setComponent(props), + setComponents: props => service.setComponents(props), + clear: props => service.clear(props), + }; + + const contextValue = useMemo( + () => [viewportActionCornersState, api], + [viewportActionCornersState, api] + ); + + return ( + + {children} + + ); +} + +ViewportActionCornersProvider.propTypes = { + children: PropTypes.node, + service: PropTypes.instanceOf(ViewportActionCornersService).isRequired, +}; + +export const useViewportActionCornersContext = () => useContext(ViewportActionCornersContext); diff --git a/extensions/cornerstone/src/getCustomizationModule.ts b/extensions/cornerstone/src/getCustomizationModule.ts index acb064244..ea07e9694 100644 --- a/extensions/cornerstone/src/getCustomizationModule.ts +++ b/extensions/cornerstone/src/getCustomizationModule.ts @@ -1,6 +1,12 @@ import { Enums } from '@cornerstonejs/tools'; import { toolNames } from './initCornerstoneTools'; import DicomUpload from './components/DicomUpload/DicomUpload'; +import defaultWindowLevelPresets from './components/WindowLevelActionMenu/defaultWindowLevelPresets'; +import { colormaps } from './utils/colormaps'; +import { CONSTANTS } from '@cornerstonejs/core'; + +const DefaultColormap = 'Grayscale'; +const { VIEWPORT_PRESETS } = CONSTANTS; const tools = { active: [ @@ -37,6 +43,157 @@ function getCustomizationModule() { id: 'cornerstone.overlayViewportTools', tools, }, + { + id: 'cornerstone.windowLevelPresets', + presets: defaultWindowLevelPresets, + }, + { + id: 'cornerstone.colorbar', + width: '16px', + colorbarTickPosition: 'left', + colormaps, + colorbarContainerPosition: 'right', + colorbarInitialColormap: DefaultColormap, + }, + { + id: 'cornerstone.3dVolumeRendering', + volumeRenderingPresets: VIEWPORT_PRESETS, + volumeRenderingQualityRange: { + min: 1, + max: 4, + step: 1, + }, + }, + { + id: 'cornerstone.measurements', + Angle: { + displayText: [], + report: [], + }, + CobbAngle: { + displayText: [], + report: [], + }, + ArrowAnnotate: { + displayText: [], + report: [], + }, + RectangleROi: { + displayText: [], + report: [], + }, + CircleROI: { + displayText: [], + report: [], + }, + EllipticalROI: { + displayText: [], + report: [], + }, + Bidirectional: { + displayText: [], + report: [], + }, + Length: { + displayText: [], + report: [], + }, + LivewireContour: { + displayText: [], + report: [], + }, + SplineROI: { + displayText: [ + { + displayName: 'Area', + value: 'area', + type: 'value', + }, + { + value: 'areaUnit', + for: ['area'], + type: 'unit', + }, + /** + { + displayName: 'Modality', + value: 'Modality', + type: 'value', + }, + */ + ], + report: [ + { + displayName: 'Area', + value: 'area', + type: 'value', + }, + { + displayName: 'Unit', + value: 'areaUnit', + type: 'value', + }, + ], + }, + PlanarFreehandROI: { + displayText: [ + { + displayName: 'Mean', + value: 'mean', + type: 'value', + }, + { + displayName: 'Max', + value: 'max', + type: 'value', + }, + { + displayName: 'Area', + value: 'area', + type: 'value', + }, + { + value: 'modalityUnit', + for: ['mean', 'max' /** 'stdDev **/], + type: 'unit', + }, + { + value: 'areaUnit', + for: ['area'], + type: 'unit', + }, + /** + { + displayName: 'Std Dev', + value: 'stdDev', + type: 'value', + }, + */ + ], + report: [ + { + displayName: 'Mean', + value: 'mean', + type: 'value', + }, + { + displayName: 'Max', + value: 'max', + type: 'value', + }, + { + displayName: 'Area', + value: 'area', + type: 'value', + }, + { + displayName: 'Unit', + value: 'unit', + type: 'value', + }, + ], + }, + }, ], }, ]; diff --git a/extensions/cornerstone/src/getHangingProtocolModule.ts b/extensions/cornerstone/src/getHangingProtocolModule.ts index 81c2132de..4f2b3ecec 100644 --- a/extensions/cornerstone/src/getHangingProtocolModule.ts +++ b/extensions/cornerstone/src/getHangingProtocolModule.ts @@ -1,295 +1,10 @@ -import { Types } from '@ohif/core'; - -const mpr: Types.HangingProtocol.Protocol = { - id: 'mpr', - name: 'Multi-Planar Reconstruction', - locked: true, - createdDate: '2021-02-23', - modifiedDate: '2023-08-15', - availableTo: {}, - editableBy: {}, - // Unknown number of priors referenced - so just match any study - numberOfPriorsReferenced: 0, - protocolMatchingRules: [], - imageLoadStrategy: 'nth', - callbacks: { - // Switches out of MPR mode when the layout change button is used - onLayoutChange: [ - { - commandName: 'toggleHangingProtocol', - commandOptions: { protocolId: 'mpr' }, - context: 'DEFAULT', - }, - ], - // Turns off crosshairs when switching out of MPR mode - onProtocolExit: [ - { - commandName: 'cleanUpCrosshairs', - }, - ], - }, - displaySetSelectors: { - activeDisplaySet: { - seriesMatchingRules: [ - { - weight: 1, - attribute: 'isReconstructable', - constraint: { - equals: { - value: true, - }, - }, - required: true, - }, - ], - }, - }, - stages: [ - { - name: 'MPR 1x3', - viewportStructure: { - layoutType: 'grid', - properties: { - rows: 1, - columns: 3, - layoutOptions: [ - { - x: 0, - y: 0, - width: 1 / 3, - height: 1, - }, - { - x: 1 / 3, - y: 0, - width: 1 / 3, - height: 1, - }, - { - x: 2 / 3, - y: 0, - width: 1 / 3, - height: 1, - }, - ], - }, - }, - viewports: [ - { - viewportOptions: { - viewportId: 'mpr-axial', - toolGroupId: 'mpr', - viewportType: 'volume', - orientation: 'axial', - initialImageOptions: { - preset: 'middle', - }, - syncGroups: [ - { - type: 'voi', - id: 'mpr', - source: true, - target: true, - }, - ], - }, - displaySets: [ - { - id: 'activeDisplaySet', - }, - ], - }, - { - viewportOptions: { - viewportId: 'mpr-sagittal', - toolGroupId: 'mpr', - viewportType: 'volume', - orientation: 'sagittal', - initialImageOptions: { - preset: 'middle', - }, - syncGroups: [ - { - type: 'voi', - id: 'mpr', - source: true, - target: true, - }, - ], - }, - displaySets: [ - { - id: 'activeDisplaySet', - }, - ], - }, - { - viewportOptions: { - viewportId: 'mpr-coronal', - toolGroupId: 'mpr', - viewportType: 'volume', - orientation: 'coronal', - initialImageOptions: { - preset: 'middle', - }, - syncGroups: [ - { - type: 'voi', - id: 'mpr', - source: true, - target: true, - }, - ], - }, - displaySets: [ - { - id: 'activeDisplaySet', - }, - ], - }, - ], - }, - ], -}; - -const mprAnd3DVolumeViewport = { - id: 'mprAnd3DVolumeViewport', - locked: true, - name: 'mpr', - createdDate: '2023-03-15T10:29:44.894Z', - modifiedDate: '2023-03-15T10:29:44.894Z', - availableTo: {}, - editableBy: {}, - protocolMatchingRules: [], - imageLoadStrategy: 'interleaveCenter', - displaySetSelectors: { - mprDisplaySet: { - seriesMatchingRules: [ - { - weight: 1, - attribute: 'isReconstructable', - constraint: { - equals: { - value: true, - }, - }, - required: true, - }, - { - attribute: 'Modality', - constraint: { - equals: { - value: 'CT', - }, - }, - required: true, - }, - ], - }, - }, - stages: [ - { - id: 'mpr3Stage', - name: 'mpr', - viewportStructure: { - layoutType: 'grid', - properties: { - rows: 2, - columns: 2, - }, - }, - viewports: [ - { - viewportOptions: { - toolGroupId: 'mpr', - viewportType: 'volume', - orientation: 'axial', - initialImageOptions: { - preset: 'middle', - }, - syncGroups: [ - { - type: 'voi', - id: 'mpr', - source: true, - target: true, - }, - ], - }, - displaySets: [ - { - id: 'mprDisplaySet', - }, - ], - }, - { - viewportOptions: { - toolGroupId: 'volume3d', - viewportType: 'volume3d', - orientation: 'coronal', - customViewportProps: { - hideOverlays: true, - }, - }, - displaySets: [ - { - id: 'mprDisplaySet', - options: { - displayPreset: 'CT-Bone', - }, - }, - ], - }, - { - viewportOptions: { - toolGroupId: 'mpr', - viewportType: 'volume', - orientation: 'coronal', - initialImageOptions: { - preset: 'middle', - }, - syncGroups: [ - { - type: 'voi', - id: 'mpr', - source: true, - target: true, - }, - ], - }, - displaySets: [ - { - id: 'mprDisplaySet', - }, - ], - }, - { - viewportOptions: { - toolGroupId: 'mpr', - viewportType: 'volume', - orientation: 'sagittal', - initialImageOptions: { - preset: 'middle', - }, - syncGroups: [ - { - type: 'voi', - id: 'mpr', - source: true, - target: true, - }, - ], - }, - displaySets: [ - { - id: 'mprDisplaySet', - }, - ], - }, - ], - }, - ], -}; +import { fourUp } from './hps/fourUp'; +import { main3D } from './hps/main3D'; +import { mpr } from './hps/mpr'; +import { mprAnd3DVolumeViewport } from './hps/mprAnd3DVolumeViewport'; +import { only3D } from './hps/only3D'; +import { primary3D } from './hps/primary3D'; +import { primaryAxial } from './hps/primaryAxial'; function getHangingProtocolModule() { return [ @@ -301,6 +16,26 @@ function getHangingProtocolModule() { name: mprAnd3DVolumeViewport.id, protocol: mprAnd3DVolumeViewport, }, + { + name: fourUp.id, + protocol: fourUp, + }, + { + name: main3D.id, + protocol: main3D, + }, + { + name: primaryAxial.id, + protocol: primaryAxial, + }, + { + name: only3D.id, + protocol: only3D, + }, + { + name: primary3D.id, + protocol: primary3D, + }, ]; } diff --git a/extensions/cornerstone/src/getToolbarModule.tsx b/extensions/cornerstone/src/getToolbarModule.tsx new file mode 100644 index 000000000..61a6428ed --- /dev/null +++ b/extensions/cornerstone/src/getToolbarModule.tsx @@ -0,0 +1,303 @@ +import { Enums } from '@cornerstonejs/tools'; + +const getToggledClassName = (isToggled: boolean) => { + return isToggled + ? '!text-primary-active' + : '!text-common-bright hover:!bg-primary-dark hover:text-primary-light'; +}; + +export default function getToolbarModule({ commandsManager, servicesManager }) { + const { + toolGroupService, + toolbarService, + syncGroupService, + cornerstoneViewportService, + hangingProtocolService, + displaySetService, + viewportGridService, + } = servicesManager.services; + + return [ + // functions/helpers to be used by the toolbar buttons to decide if they should + // enabled or not + { + name: 'evaluate.cornerstoneTool', + evaluate: ({ viewportId, button, toolNames, disabledText }) => { + const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); + + if (!toolGroup) { + return; + } + + const toolName = toolbarService.getToolNameForButton(button); + + if (!toolGroup || (!toolGroup.hasTool(toolName) && !toolNames)) { + return { + disabled: true, + className: '!text-common-bright ohif-disabled', + disabledText: disabledText ?? 'Not available on the current viewport', + }; + } + + const isPrimaryActive = toolNames + ? toolNames.includes(toolGroup.getActivePrimaryMouseButtonTool()) + : toolGroup.getActivePrimaryMouseButtonTool() === toolName; + + return { + disabled: false, + className: isPrimaryActive + ? '!text-black bg-primary-light rounded' + : '!text-common-bright hover:!bg-primary-dark hover:!text-primary-light rounded', + // Todo: isActive right now is used for nested buttons where the primary + // button needs to be fully rounded (vs partial rounded) when active + // otherwise it does not have any other use + isActive: isPrimaryActive, + }; + }, + }, + { + name: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList', + evaluate: ({ viewportId, button, itemId }) => { + const { items } = button.props; + + const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); + + if (!toolGroup) { + return { + primary: button.props.primary, + items, + }; + } + + const activeToolName = toolGroup.getActivePrimaryMouseButtonTool(); + + // check if the active toolName is part of the items then we need + // to move it to the primary button + const activeToolIndex = items.findIndex(item => { + const toolName = toolbarService.getToolNameForButton(item); + return toolName === activeToolName; + }); + + // if there is an active tool in the items dropdown bound to the primary mouse/touch + // we should show that no matter what + if (activeToolIndex > -1) { + return { + primary: items[activeToolIndex], + items, + }; + } + + if (!itemId) { + return { + primary: button.props.primary, + items, + }; + } + + // other wise we can move the clicked tool to the primary button + const clickedItemProps = items.find(item => item.id === itemId || item.itemId === itemId); + + return { + primary: clickedItemProps, + items, + }; + }, + }, + { + name: 'evaluate.action', + evaluate: ({ viewportId, button }) => { + return { + className: '!text-common-bright hover:!bg-primary-dark hover:text-primary-light', + }; + }, + }, + { + name: 'evaluate.cornerstoneTool.toggle.ifStrictlyDisabled', + evaluate: ({ viewportId, button, disabledText }) => + _evaluateToggle({ + viewportId, + button, + toolbarService, + disabledText, + offModes: [Enums.ToolModes.Disabled], + toolGroupService, + }), + }, + { + name: 'evaluate.cornerstoneTool.toggle', + evaluate: ({ viewportId, button, disabledText }) => + _evaluateToggle({ + viewportId, + button, + toolbarService, + disabledText, + offModes: [Enums.ToolModes.Disabled, Enums.ToolModes.Passive], + toolGroupService, + }), + }, + { + name: 'evaluate.cornerstone.synchronizer', + evaluate: ({ viewportId, button }) => { + let synchronizers = syncGroupService.getSynchronizersForViewport(viewportId); + + if (!synchronizers?.length) { + return { + className: getToggledClassName(false), + }; + } + + const isArray = Array.isArray(button.commands); + + const synchronizerType = isArray + ? button.commands?.[0].commandOptions.type + : button.commands?.commandOptions.type; + + synchronizers = syncGroupService.getSynchronizersOfType(synchronizerType); + + if (!synchronizers?.length) { + return { + className: getToggledClassName(false), + }; + } + + // Todo: we need a better way to find the synchronizers based on their + // type, but for now we just check the first one and see if it is + // enabled + const synchronizer = synchronizers[0]; + + const isEnabled = synchronizer?._enabled; + + return { + className: getToggledClassName(isEnabled), + }; + }, + }, + { + name: 'evaluate.not3D', + evaluate: ({ viewportId, disabledText }) => { + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + + if (viewport?.type === 'volume3d') { + return { + disabled: true, + className: '!text-common-bright ohif-disabled', + disabledText: disabledText ?? 'Not available on the current viewport', + }; + } + }, + }, + { + name: 'evaluate.isUS', + evaluate: ({ viewportId, disabledText }) => { + const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId); + + if (!displaySetUIDs?.length) { + return; + } + + const displaySets = displaySetUIDs.map(displaySetService.getDisplaySetByUID); + const isUS = displaySets.some(displaySet => displaySet?.Modality === 'US'); + if (!isUS) { + return { + disabled: true, + className: '!text-common-bright ohif-disabled', + disabledText: disabledText ?? 'Not available on the current viewport', + }; + } + }, + }, + { + name: 'evaluate.viewportProperties.toggle', + evaluate: ({ viewportId, button }) => { + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + + if (!viewport || viewport.isDisabled) { + return; + } + + const propId = button.id; + + const properties = viewport.getProperties(); + const camera = viewport.getCamera(); + + const prop = camera?.[propId] || properties?.[propId]; + + if (!prop) { + return { + disabled: false, + className: '!text-common-bright hover:!bg-primary-dark hover:text-primary-light', + }; + } + + const isToggled = prop; + + return { + className: getToggledClassName(isToggled), + }; + }, + }, + { + name: 'evaluate.mpr', + evaluate: ({ viewportId, disabledText = 'Selected viewport is not reconstructable' }) => { + const { protocol } = hangingProtocolService.getActiveProtocol(); + + const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId); + + if (!displaySetUIDs?.length) { + return; + } + + const displaySets = displaySetUIDs.map(displaySetService.getDisplaySetByUID); + + const areReconstructable = displaySets.every(displaySet => { + return displaySet.isReconstructable; + }); + + if (!areReconstructable) { + return { + disabled: true, + className: '!text-common-bright ohif-disabled', + disabledText: disabledText ?? 'Not available on the current viewport', + }; + } + + const isMpr = protocol?.id === 'mpr'; + + return { + disabled: false, + className: getToggledClassName(isMpr), + }; + }, + }, + ]; +} + +function _evaluateToggle({ + viewportId, + toolbarService, + button, + disabledText, + offModes, + toolGroupService, +}) { + const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); + + if (!toolGroup) { + return; + } + const toolName = toolbarService.getToolNameForButton(button); + + if (!toolGroup.hasTool(toolName)) { + return { + disabled: true, + className: '!text-common-bright ohif-disabled', + disabledText: disabledText ?? 'Not available on the current viewport', + }; + } + + const isOff = offModes.includes(toolGroup.getToolOptions(toolName).mode); + + return { + className: getToggledClassName(!isOff), + }; +} diff --git a/extensions/cornerstone/src/hps/fourUp.ts b/extensions/cornerstone/src/hps/fourUp.ts new file mode 100644 index 000000000..df4056854 --- /dev/null +++ b/extensions/cornerstone/src/hps/fourUp.ts @@ -0,0 +1,144 @@ +export const fourUp = { + id: 'fourUp', + locked: true, + name: '3D four up', + icon: 'layout-advanced-3d-four-up', + isPreset: true, + createdDate: '2023-03-15T10:29:44.894Z', + modifiedDate: '2023-03-15T10:29:44.894Z', + availableTo: {}, + editableBy: {}, + protocolMatchingRules: [], + imageLoadStrategy: 'interleaveCenter', + displaySetSelectors: { + mprDisplaySet: { + seriesMatchingRules: [ + { + weight: 1, + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + ], + }, + }, + stages: [ + { + id: 'fourUpStage', + name: 'fourUp', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 2, + columns: 2, + }, + }, + viewports: [ + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'axial', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'volume3d', + viewportType: 'volume3d', + orientation: 'coronal', + customViewportProps: { + hideOverlays: true, + }, + }, + displaySets: [ + { + id: 'mprDisplaySet', + options: { + displayPreset: { + CT: 'CT-Bone', + MR: 'MR-Default', + default: 'CT-Bone', + }, + }, + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'coronal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'sagittal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + ], + }, + ], +}; diff --git a/extensions/cornerstone/src/hps/main3D.ts b/extensions/cornerstone/src/hps/main3D.ts new file mode 100644 index 000000000..5976dae2c --- /dev/null +++ b/extensions/cornerstone/src/hps/main3D.ts @@ -0,0 +1,170 @@ +export const main3D = { + id: 'main3D', + locked: true, + name: '3D main', + icon: 'layout-advanced-3d-main', + isPreset: true, + createdDate: '2023-03-15T10:29:44.894Z', + modifiedDate: '2023-03-15T10:29:44.894Z', + availableTo: {}, + editableBy: {}, + protocolMatchingRules: [], + imageLoadStrategy: 'interleaveCenter', + displaySetSelectors: { + mprDisplaySet: { + seriesMatchingRules: [ + { + weight: 1, + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + ], + }, + }, + stages: [ + { + id: 'main3DStage', + name: 'main3D', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 2, + columns: 3, + layoutOptions: [ + { + x: 0, + y: 0, + width: 1, + height: 1 / 2, + }, + { + x: 0, + y: 1 / 2, + width: 1 / 3, + height: 1 / 2, + }, + { + x: 1 / 3, + y: 1 / 2, + width: 1 / 3, + height: 1 / 2, + }, + { + x: 2 / 3, + y: 1 / 2, + width: 1 / 3, + height: 1 / 2, + }, + ], + }, + }, + viewports: [ + { + viewportOptions: { + toolGroupId: 'volume3d', + viewportType: 'volume3d', + orientation: 'coronal', + customViewportProps: { + hideOverlays: true, + }, + }, + displaySets: [ + { + id: 'mprDisplaySet', + options: { + displayPreset: { + CT: 'CT-Bone', + MR: 'MR-Default', + default: 'CT-Bone', + }, + }, + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'axial', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'coronal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'sagittal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + ], + }, + ], +}; diff --git a/extensions/cornerstone/src/hps/mpr.ts b/extensions/cornerstone/src/hps/mpr.ts new file mode 100644 index 000000000..fa54ffde6 --- /dev/null +++ b/extensions/cornerstone/src/hps/mpr.ts @@ -0,0 +1,149 @@ +import { Types } from '@ohif/core'; + +export const mpr: Types.HangingProtocol.Protocol = { + id: 'mpr', + name: 'MPR', + locked: true, + icon: 'layout-advanced-mpr', + isPreset: true, + createdDate: '2021-02-23', + modifiedDate: '2023-08-15', + availableTo: {}, + editableBy: {}, + // Unknown number of priors referenced - so just match any study + numberOfPriorsReferenced: 0, + protocolMatchingRules: [], + imageLoadStrategy: 'nth', + callbacks: {}, + displaySetSelectors: { + activeDisplaySet: { + seriesMatchingRules: [ + { + weight: 1, + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + ], + }, + }, + stages: [ + { + name: 'MPR 1x3', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 1, + columns: 3, + layoutOptions: [ + { + x: 0, + y: 0, + width: 1 / 3, + height: 1, + }, + { + x: 1 / 3, + y: 0, + width: 1 / 3, + height: 1, + }, + { + x: 2 / 3, + y: 0, + width: 1 / 3, + height: 1, + }, + ], + }, + }, + viewports: [ + { + viewportOptions: { + viewportId: 'mpr-axial', + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'axial', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'activeDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'mpr-sagittal', + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'sagittal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'activeDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'mpr-coronal', + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'coronal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'activeDisplaySet', + }, + ], + }, + ], + }, + ], +}; diff --git a/extensions/cornerstone/src/hps/mprAnd3DVolumeViewport.ts b/extensions/cornerstone/src/hps/mprAnd3DVolumeViewport.ts new file mode 100644 index 000000000..53289f829 --- /dev/null +++ b/extensions/cornerstone/src/hps/mprAnd3DVolumeViewport.ts @@ -0,0 +1,151 @@ +export const mprAnd3DVolumeViewport = { + id: 'mprAnd3DVolumeViewport', + locked: true, + name: 'mpr', + createdDate: '2023-03-15T10:29:44.894Z', + modifiedDate: '2023-03-15T10:29:44.894Z', + availableTo: {}, + editableBy: {}, + protocolMatchingRules: [], + imageLoadStrategy: 'interleaveCenter', + displaySetSelectors: { + mprDisplaySet: { + seriesMatchingRules: [ + { + weight: 1, + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + { + attribute: 'Modality', + constraint: { + equals: { + value: 'CT', + }, + }, + required: true, + }, + ], + }, + }, + stages: [ + { + id: 'mpr3Stage', + name: 'mpr', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 2, + columns: 2, + }, + }, + viewports: [ + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'axial', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'volume3d', + viewportType: 'volume3d', + orientation: 'coronal', + customViewportProps: { + hideOverlays: true, + }, + }, + displaySets: [ + { + id: 'mprDisplaySet', + options: { + displayPreset: { + CT: 'CT-Bone', + MR: 'MR-Default', + default: 'CT-Bone', + }, + }, + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'coronal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'sagittal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + ], + }, + ], +}; diff --git a/extensions/cornerstone/src/hps/only3D.ts b/extensions/cornerstone/src/hps/only3D.ts new file mode 100644 index 000000000..17d6ceecd --- /dev/null +++ b/extensions/cornerstone/src/hps/only3D.ts @@ -0,0 +1,66 @@ +export const only3D = { + id: 'only3D', + locked: true, + name: '3D only', + icon: 'layout-advanced-3d-only', + isPreset: true, + createdDate: '2023-03-15T10:29:44.894Z', + modifiedDate: '2023-03-15T10:29:44.894Z', + availableTo: {}, + editableBy: {}, + protocolMatchingRules: [], + imageLoadStrategy: 'interleaveCenter', + displaySetSelectors: { + mprDisplaySet: { + seriesMatchingRules: [ + { + weight: 1, + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + ], + }, + }, + stages: [ + { + id: 'only3DStage', + name: 'only3D', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 1, + columns: 1, + }, + }, + viewports: [ + { + viewportOptions: { + toolGroupId: 'volume3d', + viewportType: 'volume3d', + orientation: 'coronal', + customViewportProps: { + hideOverlays: true, + }, + }, + displaySets: [ + { + id: 'mprDisplaySet', + options: { + displayPreset: { + CT: 'CT-Bone', + MR: 'MR-Default', + default: 'CT-Bone', + }, + }, + }, + ], + }, + ], + }, + ], +}; diff --git a/extensions/cornerstone/src/hps/primary3D.ts b/extensions/cornerstone/src/hps/primary3D.ts new file mode 100644 index 000000000..ebb8a59d9 --- /dev/null +++ b/extensions/cornerstone/src/hps/primary3D.ts @@ -0,0 +1,170 @@ +export const primary3D = { + id: 'primary3D', + locked: true, + name: '3D primary', + icon: 'layout-advanced-3d-primary', + isPreset: true, + createdDate: '2023-03-15T10:29:44.894Z', + modifiedDate: '2023-03-15T10:29:44.894Z', + availableTo: {}, + editableBy: {}, + protocolMatchingRules: [], + imageLoadStrategy: 'interleaveCenter', + displaySetSelectors: { + mprDisplaySet: { + seriesMatchingRules: [ + { + weight: 1, + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + ], + }, + }, + stages: [ + { + id: 'primary3DStage', + name: 'primary3D', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 3, + columns: 3, + layoutOptions: [ + { + x: 0, + y: 0, + width: 2 / 3, + height: 1, + }, + { + x: 2 / 3, + y: 0, + width: 1 / 3, + height: 1 / 3, + }, + { + x: 2 / 3, + y: 1 / 3, + width: 1 / 3, + height: 1 / 3, + }, + { + x: 2 / 3, + y: 2 / 3, + width: 1 / 3, + height: 1 / 3, + }, + ], + }, + }, + viewports: [ + { + viewportOptions: { + toolGroupId: 'volume3d', + viewportType: 'volume3d', + orientation: 'coronal', + customViewportProps: { + hideOverlays: true, + }, + }, + displaySets: [ + { + id: 'mprDisplaySet', + options: { + displayPreset: { + CT: 'CT-Bone', + MR: 'MR-Default', + default: 'CT-Bone', + }, + }, + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'axial', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'coronal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'sagittal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + ], + }, + ], +}; diff --git a/extensions/cornerstone/src/hps/primaryAxial.ts b/extensions/cornerstone/src/hps/primaryAxial.ts new file mode 100644 index 000000000..430dcbf16 --- /dev/null +++ b/extensions/cornerstone/src/hps/primaryAxial.ts @@ -0,0 +1,142 @@ +export const primaryAxial = { + id: 'primaryAxial', + locked: true, + name: 'Axial Primary', + icon: 'layout-advanced-axial-primary', + isPreset: true, + createdDate: '2023-03-15T10:29:44.894Z', + modifiedDate: '2023-03-15T10:29:44.894Z', + availableTo: {}, + editableBy: {}, + protocolMatchingRules: [], + imageLoadStrategy: 'interleaveCenter', + displaySetSelectors: { + mprDisplaySet: { + seriesMatchingRules: [ + { + weight: 1, + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + ], + }, + }, + stages: [ + { + id: 'primaryAxialStage', + name: 'primaryAxial', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 2, + columns: 3, + layoutOptions: [ + { + x: 0, + y: 0, + width: 2 / 3, + height: 1, + }, + { + x: 2 / 3, + y: 0, + width: 1 / 3, + height: 1 / 2, + }, + { + x: 2 / 3, + y: 1 / 2, + width: 1 / 3, + height: 1 / 2, + }, + ], + }, + }, + viewports: [ + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'axial', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'sagittal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'coronal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + options: { + syncColormap: true, + }, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + ], + }, + ], +}; diff --git a/extensions/cornerstone/src/index.tsx b/extensions/cornerstone/src/index.tsx index 10b4ce026..d2b8cf24b 100644 --- a/extensions/cornerstone/src/index.tsx +++ b/extensions/cornerstone/src/index.tsx @@ -6,6 +6,7 @@ import { imageLoadPoolManager, imageRetrievalPoolManager, } from '@cornerstonejs/core'; +import * as csStreamingImageVolumeLoader from '@cornerstonejs/streaming-image-volume-loader'; import { Enums as cs3DToolsEnums } from '@cornerstonejs/tools'; import { ServicesManager, Types } from '@ohif/core'; @@ -13,11 +14,13 @@ import init from './init'; import getCustomizationModule from './getCustomizationModule'; import getCommandsModule from './commandsModule'; import getHangingProtocolModule from './getHangingProtocolModule'; +import getToolbarModule from './getToolbarModule'; import ToolGroupService from './services/ToolGroupService'; import SyncGroupService from './services/SyncGroupService'; import SegmentationService from './services/SegmentationService'; import CornerstoneCacheService from './services/CornerstoneCacheService'; import CornerstoneViewportService from './services/ViewportService/CornerstoneViewportService'; +import ColorbarService from './services/ColorbarService'; import * as CornerstoneExtensionTypes from './types'; import { toolNames } from './initCornerstoneTools'; @@ -26,9 +29,16 @@ import dicomLoaderService from './utils/dicomLoaderService'; import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement'; import { id } from './id'; -import * as csWADOImageLoader from './initWADOImageLoader.js'; import { measurementMappingUtils } from './utils/measurementServiceMappings'; import type { PublicViewportOptions } from './services/ViewportService/Viewport'; +import ImageOverlayViewerTool from './tools/ImageOverlayViewerTool'; +import { showLabelAnnotationPopup } from './utils/callInputDialog'; +import ViewportActionCornersService from './services/ViewportActionCornersService/ViewportActionCornersService'; +import { ViewportActionCornersProvider } from './contextProviders/ViewportActionCornersProvider'; +import ActiveViewportWindowLevel from './components/ActiveViewportWindowLevel'; + +const { helpers: volumeLoaderHelpers } = csStreamingImageVolumeLoader; +const { getDynamicVolumeInfo } = volumeLoaderHelpers ?? {}; const Component = React.lazy(() => { return import(/* webpackPrefetch: true */ './Viewport/OHIFCornerstoneViewport'); @@ -51,7 +61,26 @@ const cornerstoneExtension: Types.Extensions.Extension = { */ id, - onModeExit: (): void => { + onModeEnter: ({ servicesManager }): void => { + const { cornerstoneViewportService, toolbarService, segmentationService } = + servicesManager.services; + toolbarService.registerEventForToolbarUpdate(cornerstoneViewportService, [ + cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED, + ]); + + toolbarService.registerEventForToolbarUpdate(segmentationService, [ + segmentationService.EVENTS.SEGMENTATION_ADDED, + segmentationService.EVENTS.SEGMENTATION_REMOVED, + segmentationService.EVENTS.SEGMENTATION_UPDATED, + ]); + + toolbarService.registerEventForToolbarUpdate(cornerstone.eventTarget, [ + cornerstoneTools.Enums.Events.TOOL_ACTIVATED, + ]); + }, + + onModeExit: ({ servicesManager }): void => { + const { cineService } = servicesManager.services; // Empty out the image load and retrieval pools to prevent memory leaks // on the mode exits Object.values(cs3DEnums.RequestType).forEach(type => { @@ -59,6 +88,8 @@ const cornerstoneExtension: Types.Extensions.Extension = { imageRetrievalPoolManager.clearRequestStack(type); }); + cineService.setIsCineEnabled(false); + enabledElementReset(); }, @@ -68,16 +99,33 @@ const cornerstoneExtension: Types.Extensions.Extension = { * @param configuration.csToolsConfig - Passed directly to `initCornerstoneTools` */ preRegistration: function (props: Types.Extensions.ExtensionParams): Promise { - const { servicesManager } = props; + const { servicesManager, serviceProvidersManager } = props; servicesManager.registerService(CornerstoneViewportService.REGISTRATION); servicesManager.registerService(ToolGroupService.REGISTRATION); servicesManager.registerService(SyncGroupService.REGISTRATION); servicesManager.registerService(SegmentationService.REGISTRATION); servicesManager.registerService(CornerstoneCacheService.REGISTRATION); + servicesManager.registerService(ViewportActionCornersService.REGISTRATION); + servicesManager.registerService(ColorbarService.REGISTRATION); + serviceProvidersManager.registerProvider( + ViewportActionCornersService.REGISTRATION.name, + ViewportActionCornersProvider + ); return init.call(this, props); }, + getToolbarModule, + getPanelModule({ servicesManager }) { + return [ + { + name: 'activeViewportWindowLevel', + component: () => { + return ; + }, + }, + ]; + }, getHangingProtocolModule, getViewportModule({ servicesManager, commandsManager }) { const ExtendedOHIFCornerstoneViewport = props => { @@ -115,6 +163,7 @@ const cornerstoneExtension: Types.Extensions.Extension = { }, getEnabledElement, dicomLoaderService, + showLabelAnnotationPopup, }, }, { @@ -130,6 +179,12 @@ const cornerstoneExtension: Types.Extensions.Extension = { Enums: cs3DToolsEnums, }, }, + { + name: 'volumeLoader', + exports: { + getDynamicVolumeInfo, + }, + }, ]; }, }; @@ -140,5 +195,6 @@ export { CornerstoneExtensionTypes as Types, toolNames, getActiveViewportEnabledElement, + ImageOverlayViewerTool, }; export default cornerstoneExtension; diff --git a/extensions/cornerstone/src/init.tsx b/extensions/cornerstone/src/init.tsx index 99755fa73..52993059b 100644 --- a/extensions/cornerstone/src/init.tsx +++ b/extensions/cornerstone/src/init.tsx @@ -1,4 +1,4 @@ -import OHIF, { Types } from '@ohif/core'; +import OHIF, { Types, errorHandler } from '@ohif/core'; import React from 'react'; import * as cornerstone from '@cornerstonejs/core'; @@ -10,12 +10,15 @@ import { metaData, volumeLoader, imageLoadPoolManager, + getEnabledElement, Settings, utilities as csUtilities, Enums as csEnums, } from '@cornerstonejs/core'; -import { Enums } from '@cornerstonejs/tools'; -import { cornerstoneStreamingImageVolumeLoader } from '@cornerstonejs/streaming-image-volume-loader'; +import { + cornerstoneStreamingImageVolumeLoader, + cornerstoneStreamingDynamicImageVolumeLoader, +} from '@cornerstonejs/streaming-image-volume-loader'; import initWADOImageLoader from './initWADOImageLoader'; import initCornerstoneTools from './initCornerstoneTools'; @@ -29,6 +32,11 @@ import initContextMenu from './initContextMenu'; import initDoubleClick from './initDoubleClick'; import { CornerstoneServices } from './types'; import initViewTiming from './utils/initViewTiming'; +import { colormaps } from './utils/colormaps'; + +import { debounce } from 'lodash'; + +const { registerColormap } = csUtilities.colormap; // TODO: Cypress tests are currently grabbing this from the window? window.cornerstone = cornerstone; @@ -40,7 +48,6 @@ export default async function init({ servicesManager, commandsManager, extensionManager, - configuration, appConfig, }: Types.Extensions.ExtensionParams): Promise { // Note: this should run first before initializing the cornerstone @@ -59,8 +66,8 @@ export default async function init({ await cs3DInit({ rendering: { - preferSizeOverAccuracy: Boolean(appConfig.use16BitDataType), - useNorm16Texture: Boolean(appConfig.use16BitDataType), + preferSizeOverAccuracy: Boolean(appConfig.preferSizeOverAccuracy), + useNorm16Texture: Boolean(appConfig.useNorm16Texture), }, }); @@ -90,11 +97,8 @@ export default async function init({ customizationService, uiModalService, uiNotificationService, - cineService, cornerstoneViewportService, hangingProtocolService, - toolGroupService, - toolbarService, viewportGridService, stateSyncService, } = servicesManager.services as CornerstoneServices; @@ -124,6 +128,9 @@ export default async function init({ // an OHIFCornerstoneViewport can be redisplayed with the same LUT stateSyncService.register('lutPresentationStore', { clearOnModeExit: true }); + // Stores synchronizers state to be restored + stateSyncService.register('synchronizersStore', { clearOnModeExit: true }); + // Stores a map from `positionPresentationId` to a Presentation object so that // an OHIFCornerstoneViewport can be redisplayed with the same position stateSyncService.register('positionPresentationStore', { @@ -139,7 +146,7 @@ export default async function init({ const labelmapRepresentation = cornerstoneTools.Enums.SegmentationRepresentations.Labelmap; cornerstoneTools.segmentation.config.setGlobalRepresentationConfig(labelmapRepresentation, { - fillAlpha: 0.3, + fillAlpha: 0.5, fillAlphaInactive: 0.2, outlineOpacity: 1, outlineOpacityInactive: 0.65, @@ -152,6 +159,11 @@ export default async function init({ cornerstoneStreamingImageVolumeLoader ); + volumeLoader.registerVolumeLoader( + 'cornerstoneStreamingDynamicImageVolume', + cornerstoneStreamingDynamicImageVolumeLoader + ); + hangingProtocolService.registerImageLoadStrategy('interleaveCenter', interleaveCenterLoader); hangingProtocolService.registerImageLoadStrategy('interleaveTopToBottom', interleaveTopToBottom); hangingProtocolService.registerImageLoadStrategy('nth', nthLoader); @@ -175,7 +187,7 @@ export default async function init({ /* Measurement Service */ this.measurementServiceSource = connectToolsToMeasurementService(servicesManager); - initCineService(cineService); + initCineService(servicesManager); // When a custom image load is performed, update the relevant viewports hangingProtocolService.subscribe( @@ -199,6 +211,16 @@ export default async function init({ } ); + // resize the cornerstone viewport service when the grid size changes + // IMPORTANT: this should happen outside of the OHIFCornerstoneViewport + // since it will trigger a rerender of each viewport and each resizing + // the offscreen canvas which would result in a performance hit, this should + // done only once per grid resize here. Doing it once here, allows us to reduce + // the refreshRage(in ms) to 10 from 50. I tried with even 1 or 5 ms it worked fine + viewportGridService.subscribe(viewportGridService.EVENTS.GRID_SIZE_CHANGED, () => { + cornerstoneViewportService.resize(true); + }); + initContextMenu({ cornerstoneViewportService, customizationService, @@ -211,88 +233,39 @@ export default async function init({ }); /** - * When a viewport gets a new display set, this call will go through all the - * active tools in the toolbar, and call any commands registered in the - * toolbar service with a callback to re-enable on displaying the viewport. + * Runs error handler for failed requests. + * @param event */ - const toolbarEventListener = evt => { - const { element } = evt.detail; - const activeTools = toolbarService.getActiveTools(); - - activeTools.forEach(tool => { - const toolData = toolbarService.getNestedButton(tool); - const commands = toolData?.listeners?.[evt.type]; - commandsManager.run(commands, { element, evt }); - }); - }; - - /** Listens for active viewport events and fires the toolbar listeners */ - const activeViewportEventListener = evt => { - const { viewportId } = evt; - const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); - - const activeTools = toolbarService.getActiveTools(); - - activeTools.forEach(tool => { - if (!toolGroup?._toolInstances?.[tool]) { - return; - } - - // check if tool is active on the new viewport - const toolEnabled = toolGroup._toolInstances[tool].mode === Enums.ToolModes.Enabled; - - if (!toolEnabled) { - return; - } - - const button = toolbarService.getNestedButton(tool); - const commands = button?.listeners?.[evt.type]; - commandsManager.run(commands, { viewportId, evt }); - }); - }; - - const resetCrosshairs = evt => { - const { element } = evt.detail; - const { viewportId, renderingEngineId } = cornerstone.getEnabledElement(element); - - const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroupForViewport( - viewportId, - renderingEngineId - ); - - if (!toolGroup || !toolGroup._toolInstances?.['Crosshairs']) { - return; - } - - const mode = toolGroup._toolInstances['Crosshairs'].mode; - - if (mode === Enums.ToolModes.Active) { - toolGroup.setToolActive('Crosshairs'); - } else if (mode === Enums.ToolModes.Passive) { - toolGroup.setToolPassive('Crosshairs'); - } else if (mode === Enums.ToolModes.Enabled) { - toolGroup.setToolEnabled('Crosshairs'); - } + const imageLoadFailedHandler = ({ detail }) => { + const handler = errorHandler.getHTTPErrorHandler(); + handler(detail.error); }; eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, evt => { const { element } = evt.detail; cornerstoneTools.utilities.stackContextPrefetch.enable(element); }); + eventTarget.addEventListener(EVENTS.IMAGE_LOAD_FAILED, imageLoadFailedHandler); + eventTarget.addEventListener(EVENTS.IMAGE_LOAD_ERROR, imageLoadFailedHandler); function elementEnabledHandler(evt) { const { element } = evt.detail; - element.addEventListener(EVENTS.CAMERA_RESET, resetCrosshairs); - eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, toolbarEventListener); + element.addEventListener(EVENTS.CAMERA_RESET, evt => { + const { element } = evt.detail; + const { viewportId } = getEnabledElement(element); + commandsManager.runCommand('resetCrosshairs', { viewportId }); + }); - initViewTiming({ element, eventTarget }); + // eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, toolbarEventListener); + + initViewTiming({ element }); } function elementDisabledHandler(evt) { const { element } = evt.detail; - element.removeEventListener(EVENTS.CAMERA_RESET, resetCrosshairs); + // element.removeEventListener(EVENTS.CAMERA_RESET, resetCrosshairs); // TODO - consider removing the callback when all elements are gone // eventTarget.removeEventListener( @@ -304,12 +277,21 @@ export default async function init({ eventTarget.addEventListener(EVENTS.ELEMENT_ENABLED, elementEnabledHandler.bind(null)); eventTarget.addEventListener(EVENTS.ELEMENT_DISABLED, elementDisabledHandler.bind(null)); + colormaps.forEach(registerColormap); - viewportGridService.subscribe( - viewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED, - activeViewportEventListener - ); - } + // Event listener + eventTarget.addEventListenerDebounced( + EVENTS.ERROR_EVENT, + ({ detail }) => { + uiNotificationService.show({ + title: detail.type, + message: detail.message, + type: 'error', + }); + }, + 1000 + ); +} function CPUModal() { return ( diff --git a/extensions/cornerstone/src/initCineService.ts b/extensions/cornerstone/src/initCineService.ts index 09d6d6296..5e3246135 100644 --- a/extensions/cornerstone/src/initCineService.ts +++ b/extensions/cornerstone/src/initCineService.ts @@ -1,15 +1,73 @@ +import { cache } from '@cornerstonejs/core'; import { utilities } from '@cornerstonejs/tools'; -function initCineService(cineService) { +function _getVolumesFromViewport(viewport) { + return viewport ? viewport.getActors().map(actor => cache.getVolume(actor.uid)) : []; +} + +function _getVolumeFromViewport(viewport) { + const volumes = _getVolumesFromViewport(viewport); + const dynamicVolume = volumes.find(volume => volume.isDynamicVolume()); + + return dynamicVolume ?? volumes[0]; +} + +/** + * Return all viewports that needs to be synchronized with the source + * viewport passed as parameter when cine is updated. + * @param servicesManager ServiceManager + * @param srcViewportIndex Source viewport index + * @returns array with viewport information. + */ +function _getSyncedViewports(servicesManager, srcViewportId) { + const { viewportGridService, cornerstoneViewportService } = servicesManager.services; + + const { viewports: viewportsStates } = viewportGridService.getState(); + const srcViewportState = viewportsStates.get(srcViewportId); + + if (srcViewportState?.viewportOptions?.viewportType !== 'volume') { + return []; + } + + const srcViewport = cornerstoneViewportService.getCornerstoneViewport(srcViewportId); + + const srcVolume = srcViewport ? _getVolumeFromViewport(srcViewport) : null; + + if (!srcVolume?.isDynamicVolume()) { + return []; + } + + const { volumeId: srcVolumeId } = srcVolume; + + return Array.from(viewportsStates.values()) + .filter(({ viewportId }) => { + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + + return viewportId !== srcViewportId && viewport?.hasVolumeId(srcVolumeId); + }) + .map(({ viewportId }) => ({ viewportId })); +} + +function initCineService(servicesManager) { + const { cineService } = servicesManager.services; + + const getSyncedViewports = viewportId => { + return _getSyncedViewports(servicesManager, viewportId); + }; + const playClip = (element, playClipOptions) => { return utilities.cine.playClip(element, playClipOptions); }; - const stopClip = element => { - return utilities.cine.stopClip(element); + const stopClip = (element, stopClipOptions) => { + return utilities.cine.stopClip(element, stopClipOptions); }; - cineService.setServiceImplementation({ playClip, stopClip }); + cineService.setServiceImplementation({ + getSyncedViewports, + playClip, + stopClip, + }); } export default initCineService; diff --git a/extensions/cornerstone/src/initCornerstoneTools.js b/extensions/cornerstone/src/initCornerstoneTools.js index 767b607a7..cdbf662d4 100644 --- a/extensions/cornerstone/src/initCornerstoneTools.js +++ b/extensions/cornerstone/src/initCornerstoneTools.js @@ -8,6 +8,7 @@ import { MIPJumpToClickTool, LengthTool, RectangleROITool, + RectangleROIThresholdTool, EllipticalROITool, CircleROITool, BidirectionalTool, @@ -16,18 +17,25 @@ import { ProbeTool, AngleTool, CobbAngleTool, - PlanarFreehandROITool, MagnifyTool, CrosshairsTool, SegmentationDisplayTool, + RectangleScissorsTool, + SphereScissorsTool, + CircleScissorsTool, + BrushTool, + PaintFillTool, init, addTool, annotation, ReferenceLinesTool, TrackballRotateTool, - CircleScissorsTool, - RectangleScissorsTool, - SphereScissorsTool, + AdvancedMagnifyTool, + UltrasoundDirectionalTool, + PlanarFreehandROITool, + SplineROITool, + LivewireContourTool, + OrientationMarkerTool, } from '@cornerstonejs/tools'; import CalibrationLineTool from './tools/CalibrationLineTool'; @@ -36,6 +44,7 @@ import ImageOverlayViewerTool from './tools/ImageOverlayViewerTool'; export default function initCornerstoneTools(configuration = {}) { CrosshairsTool.isAnnotation = false; ReferenceLinesTool.isAnnotation = false; + AdvancedMagnifyTool.isAnnotation = false; init(configuration); addTool(PanTool); @@ -48,6 +57,7 @@ export default function initCornerstoneTools(configuration = {}) { addTool(MIPJumpToClickTool); addTool(LengthTool); addTool(RectangleROITool); + addTool(RectangleROIThresholdTool); addTool(EllipticalROITool); addTool(CircleROITool); addTool(BidirectionalTool); @@ -55,17 +65,24 @@ export default function initCornerstoneTools(configuration = {}) { addTool(DragProbeTool); addTool(AngleTool); addTool(CobbAngleTool); - addTool(PlanarFreehandROITool); addTool(MagnifyTool); addTool(CrosshairsTool); addTool(SegmentationDisplayTool); + addTool(RectangleScissorsTool); + addTool(SphereScissorsTool); + addTool(CircleScissorsTool); + addTool(BrushTool); + addTool(PaintFillTool); addTool(ReferenceLinesTool); addTool(CalibrationLineTool); addTool(TrackballRotateTool); - addTool(CircleScissorsTool); - addTool(RectangleScissorsTool); - addTool(SphereScissorsTool); addTool(ImageOverlayViewerTool); + addTool(AdvancedMagnifyTool); + addTool(UltrasoundDirectionalTool); + addTool(PlanarFreehandROITool); + addTool(SplineROITool); + addTool(LivewireContourTool); + addTool(OrientationMarkerTool); // Modify annotation tools to use dashed lines on SR const annotationStyle = { @@ -95,15 +112,17 @@ const toolNames = { DragProbe: DragProbeTool.toolName, Probe: ProbeTool.toolName, RectangleROI: RectangleROITool.toolName, + RectangleROIThreshold: RectangleROIThresholdTool.toolName, EllipticalROI: EllipticalROITool.toolName, CircleROI: CircleROITool.toolName, Bidirectional: BidirectionalTool.toolName, Angle: AngleTool.toolName, CobbAngle: CobbAngleTool.toolName, - PlanarFreehandROI: PlanarFreehandROITool.toolName, Magnify: MagnifyTool.toolName, Crosshairs: CrosshairsTool.toolName, SegmentationDisplay: SegmentationDisplayTool.toolName, + Brush: BrushTool.toolName, + PaintFill: PaintFillTool.toolName, ReferenceLines: ReferenceLinesTool.toolName, CalibrationLine: CalibrationLineTool.toolName, TrackballRotateTool: TrackballRotateTool.toolName, @@ -111,6 +130,12 @@ const toolNames = { RectangleScissors: RectangleScissorsTool.toolName, SphereScissors: SphereScissorsTool.toolName, ImageOverlayViewer: ImageOverlayViewerTool.toolName, + AdvancedMagnify: AdvancedMagnifyTool.toolName, + UltrasoundDirectional: UltrasoundDirectionalTool.toolName, + SplineROI: SplineROITool.toolName, + LivewireContour: LivewireContourTool.toolName, + PlanarFreehandROI: PlanarFreehandROITool.toolName, + OrientationMarker: OrientationMarkerTool.toolName, }; export { toolNames }; diff --git a/extensions/cornerstone/src/initMeasurementService.js b/extensions/cornerstone/src/initMeasurementService.js index 2ab5cce55..79d94f78f 100644 --- a/extensions/cornerstone/src/initMeasurementService.js +++ b/extensions/cornerstone/src/initMeasurementService.js @@ -17,7 +17,8 @@ const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1'; const initMeasurementService = ( measurementService, displaySetService, - cornerstoneViewportService + cornerstoneViewportService, + customizationService ) => { /* Initialization */ const { @@ -30,10 +31,15 @@ const initMeasurementService = ( CobbAngle, RectangleROI, PlanarFreehandROI, + SplineROI, + LivewireContour, + Probe, + UltrasoundDirectional, } = measurementServiceMappingsFactory( measurementService, displaySetService, - cornerstoneViewportService + cornerstoneViewportService, + customizationService ); const csTools3DVer1MeasurementSource = measurementService.createSource( CORNERSTONE_3D_TOOLS_SOURCE_NAME, @@ -49,6 +55,20 @@ const initMeasurementService = ( Length.toMeasurement ); + measurementService.addMapping( + csTools3DVer1MeasurementSource, + 'Crosshairs', + Length.matchingCriteria, + () => { + console.warn('Crosshairs mapping not implemented.'); + return {}; + }, + () => { + console.warn('Crosshairs mapping not implemented.'); + return {}; + } + ); + measurementService.addMapping( csTools3DVer1MeasurementSource, 'Bidirectional', @@ -113,6 +133,14 @@ const initMeasurementService = ( PlanarFreehandROI.toMeasurement ); + measurementService.addMapping( + csTools3DVer1MeasurementSource, + 'SplineROI', + SplineROI.matchingCriteria, + SplineROI.toAnnotation, + SplineROI.toMeasurement + ); + // On the UI side, the Calibration Line tool will work almost the same as the // Length tool measurementService.addMapping( @@ -123,16 +151,45 @@ const initMeasurementService = ( Length.toMeasurement ); + measurementService.addMapping( + csTools3DVer1MeasurementSource, + 'LivewireContour', + LivewireContour.matchingCriteria, + LivewireContour.toAnnotation, + LivewireContour.toMeasurement + ); + + measurementService.addMapping( + csTools3DVer1MeasurementSource, + 'Probe', + Probe.matchingCriteria, + Probe.toAnnotation, + Probe.toMeasurement + ); + + measurementService.addMapping( + csTools3DVer1MeasurementSource, + 'UltrasoundDirectionalTool', + UltrasoundDirectional.matchingCriteria, + UltrasoundDirectional.toAnnotation, + UltrasoundDirectional.toMeasurement + ); + return csTools3DVer1MeasurementSource; }; const connectToolsToMeasurementService = servicesManager => { - const { measurementService, displaySetService, cornerstoneViewportService } = - servicesManager.services; + const { + measurementService, + displaySetService, + cornerstoneViewportService, + customizationService, + } = servicesManager.services; const csTools3DVer1MeasurementSource = initMeasurementService( measurementService, displaySetService, - cornerstoneViewportService + cornerstoneViewportService, + customizationService ); connectMeasurementServiceToTools( measurementService, @@ -357,6 +414,10 @@ const connectMeasurementServiceToTools = ( imageId = dataSource.getImageIdsForInstance({ instance }); } + /** + * This annotation is used by the cornerstone viewport. + * This is not the read-only annotation rendered by the SR viewport. + */ const annotationManager = annotation.state.getAnnotationManager(); annotationManager.addAnnotation({ annotationUID: measurement.uid, @@ -369,11 +430,16 @@ const connectMeasurementServiceToTools = ( referencedImageId: imageId, }, data: { + /** + * Don't remove this destructuring of data here. + * This is used to pass annotation specific data forward e.g. contour + */ + ...(data.annotation.data || {}), text: data.annotation.data.text, handles: { ...data.annotation.data.handles }, cachedStats: { ...data.annotation.data.cachedStats }, label: data.annotation.data.label, - frameNumber: frameNumber, + frameNumber, }, }); } diff --git a/extensions/cornerstone/src/initWADOImageLoader.js b/extensions/cornerstone/src/initWADOImageLoader.js index 1beda3885..5a200849f 100644 --- a/extensions/cornerstone/src/initWADOImageLoader.js +++ b/extensions/cornerstone/src/initWADOImageLoader.js @@ -1,6 +1,9 @@ import * as cornerstone from '@cornerstonejs/core'; import { volumeLoader } from '@cornerstonejs/core'; -import { cornerstoneStreamingImageVolumeLoader } from '@cornerstonejs/streaming-image-volume-loader'; +import { + cornerstoneStreamingImageVolumeLoader, + cornerstoneStreamingDynamicImageVolumeLoader, +} from '@cornerstonejs/streaming-image-volume-loader'; import dicomImageLoader, { webWorkerManager } from '@cornerstonejs/dicom-image-loader'; import dicomParser from 'dicom-parser'; import { errorHandler, utils } from '@ohif/core'; @@ -41,6 +44,11 @@ export default function initWADOImageLoader( registerVolumeLoader('cornerstoneStreamingImageVolume', cornerstoneStreamingImageVolumeLoader); + registerVolumeLoader( + 'cornerstoneStreamingDynamicImageVolume', + cornerstoneStreamingDynamicImageVolumeLoader + ); + dicomImageLoader.configure({ decodeConfig: { // !! IMPORTANT !! @@ -49,7 +57,8 @@ export default function initWADOImageLoader( // Until the default is set to true (which is the case for cornerstone3D), // we should set this flag to false. convertFloatPixelDataToInt: false, - use16BitDataType: Boolean(appConfig.use16BitDataType), + use16BitDataType: + Boolean(appConfig.useNorm16Texture) || Boolean(appConfig.preferSizeOverAccuracy), }, beforeSend: function (xhr) { //TODO should be removed in the future and request emitted by DicomWebDataSource diff --git a/extensions/cornerstone/src/services/ColorbarService/ColorbarService.ts b/extensions/cornerstone/src/services/ColorbarService/ColorbarService.ts new file mode 100644 index 000000000..a8a4620ac --- /dev/null +++ b/extensions/cornerstone/src/services/ColorbarService/ColorbarService.ts @@ -0,0 +1,266 @@ +import { PubSubService } from '@ohif/core'; +import { RENDERING_ENGINE_ID } from '../ViewportService/constants'; +import { StackViewport, VolumeViewport, getRenderingEngine } from '@cornerstonejs/core'; +import { utilities } from '@cornerstonejs/tools'; +import { ColorbarOptions, ChangeTypes } from '../../types/Colorbar'; +const { ViewportColorbar } = utilities.voi.colorbar; + +export default class ColorbarService extends PubSubService { + static EVENTS = { + STATE_CHANGED: 'event::ColorbarService:stateChanged', + }; + + static defaultStyles = { + position: 'absolute', + boxSizing: 'border-box', + border: 'solid 1px #555', + cursor: 'initial', + }; + + static positionStyles = { + left: { left: '5%' }, + right: { right: '5%' }, + top: { top: '5%' }, + bottom: { bottom: '5%' }, + }; + + static defaultTickStyles = { + position: 'left', + style: { + font: '12px Arial', + color: '#fff', + maxNumTicks: 8, + tickSize: 5, + tickWidth: 1, + labelMargin: 3, + }, + }; + + public static REGISTRATION = { + name: 'colorbarService', + create: () => { + return new ColorbarService(); + }, + }; + colorbars = {}; + + constructor() { + super(ColorbarService.EVENTS); + } + + /** + * Adds a colorbar to a specific viewport identified by `viewportId`, using the provided `displaySetInstanceUIDs` and `options`. + * This method sets up the colorbar, associates it with the viewport, and applies initial configurations based on the provided options. + * + * @param viewportId The identifier for the viewport where the colorbar will be added. + * @param displaySetInstanceUIDs An array of display set instance UIDs to associate with the colorbar. + * @param options Configuration options for the colorbar, including position, colormaps, active colormap name, ticks, and width. + */ + public addColorbar(viewportId, displaySetInstanceUIDs, options = {} as ColorbarOptions) { + const renderingEngine = getRenderingEngine(RENDERING_ENGINE_ID); + const viewport = renderingEngine.getViewport(viewportId); + const { element } = viewport; + const actorEntries = viewport.getActors(); + const { position, width: thickness, activeColormapName, colormaps } = options; + + const numContainers = displaySetInstanceUIDs.length; + + const containers = this.createContainers( + numContainers, + element, + position, + thickness, + viewportId + ); + + displaySetInstanceUIDs.forEach((displaySetInstanceUID, index) => { + const actorEntry = actorEntries.find(entry => entry.uid.includes(displaySetInstanceUID)); + const volumeId = actorEntry?.uid; + const properties = viewport?.getProperties(volumeId); + const colormap = properties?.colormap; + // if there's an initial colormap set, and no colormap on the viewport, set it + if (activeColormapName && !colormap) { + this.setViewportColormap( + viewportId, + displaySetInstanceUID, + colormaps[activeColormapName], + true + ); + } + + const colorbarContainer = containers[index]; + + const colorbar = new ViewportColorbar({ + id: `ctColorbar-${viewportId}-${index}`, + element, + colormaps: options.colormaps || {}, + // if there's an existing colormap set, we use it, otherwise we use the activeColormapName, otherwise, grayscale + activeColormapName: colormap?.name || options?.activeColormapName || 'Grayscale', + container: colorbarContainer, + ticks: { + ...ColorbarService.defaultTickStyles, + ...options.ticks, + }, + volumeId: viewport instanceof VolumeViewport ? volumeId : undefined, + }); + if (this.colorbars[viewportId]) { + this.colorbars[viewportId].push({ colorbar, container: colorbarContainer }); + } else { + this.colorbars[viewportId] = [{ colorbar, container: colorbarContainer }]; + } + }); + + this._broadcastEvent(ColorbarService.EVENTS.STATE_CHANGED, { + viewportId, + changeType: ChangeTypes.Added, + }); + } + + /** + * Removes the colorbar associated with a given viewport ID. This involves cleaning up any created DOM elements and internal references. + * + * @param viewportId The identifier for the viewport from which the colorbar will be removed. + */ + public removeColorbar(viewportId) { + const colorbarInfo = this.colorbars[viewportId]; + if (!colorbarInfo) { + return; + } + + colorbarInfo.forEach(({ colorbar, container }) => { + container.parentNode.removeChild(container); + }); + + delete this.colorbars[viewportId]; + + this._broadcastEvent(ColorbarService.EVENTS.STATE_CHANGED, { + viewportId, + changeType: ChangeTypes.Removed, + }); + } + + /** + * Checks whether a colorbar is associated with a given viewport ID. + * + * @param viewportId The identifier for the viewport to check. + * @returns `true` if a colorbar exists for the specified viewport, otherwise `false`. + */ + public hasColorbar(viewportId) { + return this.colorbars[viewportId] ? true : false; + } + + /** + * Retrieves the current state of colorbars, including all active colorbars and their configurations. + * + * @returns An object representing the current state of all colorbars managed by this service. + */ + public getState() { + return this.colorbars; + } + + /** + * Retrieves colorbar information for a specific viewport ID. + * + * @param viewportId The identifier for the viewport to retrieve colorbar information for. + * @returns The colorbar information associated with the specified viewport, if available. + */ + public getViewportColorbar(viewportId) { + return this.colorbars[viewportId]; + } + + /** + * Handles the cleanup and removal of all colorbars from the viewports. This is typically called + * when exiting the mode or context in which the colorbars are used, ensuring that no DOM + * elements or references are left behind. + */ + public onModeExit() { + const viewportIds = Object.keys(this.colorbars); + viewportIds.forEach(viewportId => { + this.removeColorbar(viewportId); + }); + } + + /** + * Sets the colormap for a viewport. This function is used internally to update the colormap the viewport + * + * @param viewportId The identifier of the viewport to update. + * @param displaySetInstanceUID The display set instance UID associated with the viewport. + * @param colormap The colormap object to set on the viewport. + * @param immediate A boolean indicating whether the viewport should be re-rendered immediately after setting the colormap. + */ + private setViewportColormap(viewportId, displaySetInstanceUID, colormap, immediate = false) { + const renderingEngine = getRenderingEngine(RENDERING_ENGINE_ID); + const viewport = renderingEngine.getViewport(viewportId); + const actorEntries = viewport?.getActors(); + if (!viewport || !actorEntries || actorEntries.length === 0) { + return; + } + const setViewportProperties = (viewport, uid) => { + const actorEntry = actorEntries.find(entry => entry.uid.includes(uid)); + const { actor: volumeActor, uid: volumeId } = actorEntry; + viewport.setProperties({ colormap, volumeActor }, volumeId); + }; + + if (viewport instanceof StackViewport) { + setViewportProperties(viewport, viewportId); + } + + if (viewport instanceof VolumeViewport) { + setViewportProperties(viewport, displaySetInstanceUID); + } + + if (immediate) { + viewport.render(); + } + } + + /** + * Creates the container elements for colorbars based on the specified parameters. This function dynamically + * generates and styles DOM elements to host the colorbars, positioning them according to the specified options. + * + * @param numContainers The number of containers to create, typically corresponding to the number of colorbars. + * @param element The DOM element within which the colorbar containers will be placed. + * @param position The position of the colorbar containers (e.g., 'top', 'bottom', 'left', 'right'). + * @param thickness The thickness of the colorbar containers, affecting their width or height depending on their position. + * @param viewportId The identifier of the viewport for which the containers are being created. + * @returns An array of the created container DOM elements. + */ + private createContainers(numContainers, element, position, thickness, viewportId) { + const containers = []; + const dimensions = { + 1: 50, + 2: 33, + }; + const dimension = dimensions[numContainers] || 50 / numContainers; + + Array.from({ length: numContainers }).forEach((_, i) => { + const colorbarContainer = document.createElement('div'); + colorbarContainer.id = `ctColorbarContainer-${viewportId}-${i + 1}`; + + Object.assign(colorbarContainer.style, ColorbarService.defaultStyles); + + if (['top', 'bottom'].includes(position)) { + Object.assign(colorbarContainer.style, { + width: `${dimension}%`, + height: thickness || '2.5%', + left: `${(i + 1) * dimension}%`, + transform: 'translateX(-50%)', + ...ColorbarService.positionStyles[position], + }); + } else if (['left', 'right'].includes(position)) { + Object.assign(colorbarContainer.style, { + height: `${dimension}%`, + width: thickness || '2.5%', + top: `${(i + 1) * dimension}%`, + transform: 'translateY(-50%)', + ...ColorbarService.positionStyles[position], + }); + } + + element.appendChild(colorbarContainer); + containers.push(colorbarContainer); + }); + + return containers; + } +} diff --git a/extensions/cornerstone/src/services/ColorbarService/index.ts b/extensions/cornerstone/src/services/ColorbarService/index.ts new file mode 100644 index 000000000..32bd65056 --- /dev/null +++ b/extensions/cornerstone/src/services/ColorbarService/index.ts @@ -0,0 +1,2 @@ +import ColorbarService from './ColorbarService'; +export default ColorbarService; diff --git a/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts b/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts index a3db943ab..74f5d7ac3 100644 --- a/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts +++ b/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts @@ -45,7 +45,11 @@ class CornerstoneCacheService { // as a reference volume, if so, we should hang a volume viewport // instead of a stack viewport if (this._shouldRenderSegmentation(displaySets)) { - viewportType = 'volume'; + // if the viewport type is volume 3D, we should let it be as it is + // Todo: in future here we should kick start the conversion of the + // segmentation to closed surface + viewportType = + viewportType === Enums.ViewportType.VOLUME_3D ? Enums.ViewportType.VOLUME_3D : 'volume'; // update viewportOptions to reflect the new viewport type viewportOptions.viewportType = viewportType; @@ -94,7 +98,21 @@ class CornerstoneCacheService { const volume = cs3DCache.getVolume(volumeId); if (volume) { - cs3DCache.removeVolumeLoadObject(volumeId); + if (volume.imageIds) { + // also for each imageId in the volume, remove the imageId from the cache + // since that will hold the old metadata as well + + volume.imageIds.forEach(imageId => { + if (cs3DCache.getImageLoadObject(imageId)) { + cs3DCache.removeImageLoadObject(imageId); + } + }); + } + + // this shouldn't be via removeVolumeLoadObject, since that will + // remove the texture as well, but here we really just need a remove + // from registry so that we load it again + cs3DCache._volumeCache.delete(volumeId); this.volumeImageIds.delete(volumeId); } @@ -200,6 +218,7 @@ class CornerstoneCacheService { volume, volumeId, imageIds: volumeImageIds, + isDynamicVolume: displaySet.isDynamicVolume, }); } @@ -228,7 +247,7 @@ class CornerstoneCacheService { const shouldDisplaySeg = segmentationService.shouldRenderSegmentation( viewportDisplaySetInstanceUIDs, - instance.FrameOfReferenceUID + instance?.FrameOfReferenceUID || segDisplaySet.FrameOfReferenceUID ); if (shouldDisplaySeg) { diff --git a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts index eda99b7b2..355bcd0f5 100644 --- a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts +++ b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts @@ -1,5 +1,3 @@ -import cloneDeep from 'lodash.clonedeep'; - import { Types as OhifTypes, ServicesManager, PubSubService } from '@ohif/core'; import { cache, @@ -11,7 +9,6 @@ import { volumeLoader, } from '@cornerstonejs/core'; import { - CONSTANTS as cstConstants, Enums as csToolsEnums, segmentation as cstSegmentation, Types as cstTypes, @@ -23,7 +20,6 @@ import { easeInOutBell, reverseEaseInOutBell } from '../../utils/transitions'; import { Segment, Segmentation, SegmentationConfig } from './SegmentationServiceTypes'; import { mapROIContoursToRTStructData } from './RTSTRUCT/mapROIContoursToRTStructData'; -const { COLOR_LUT } = cstConstants; const LABELMAP = csToolsEnums.SegmentationRepresentations.Labelmap; const CONTOUR = csToolsEnums.SegmentationRepresentations.Contour; @@ -40,7 +36,7 @@ const EVENTS = { SEGMENTATION_CONFIGURATION_CHANGED: 'event::segmentation_configuration_changed', // fired when the active segment is loaded in SEG or RTSTRUCT SEGMENT_LOADING_COMPLETE: 'event::segment_loading_complete', - // for all segments + // loading completed for all segments SEGMENTATION_LOADING_COMPLETE: 'event::segmentation_loading_complete', }; @@ -465,15 +461,6 @@ class SegmentationService extends PubSubService { }, ]); - // if first segmentation, we can use the default colorLUT, otherwise - // we need to generate a new one and use a new colorLUT - const colorLUTIndex = 0; - if (Object.keys(this.segmentations).length !== 0) { - const newColorLUT = this.generateNewColorLUT(); - const colorLUTIndex = this.getNextColorLUTIndex(); - cstSegmentation.config.color.addColorLUT(newColorLUT, colorLUTIndex); - } - this.segmentations[segmentationId] = { ...segmentation, label: segmentation.label || '', @@ -482,7 +469,6 @@ class SegmentationService extends PubSubService { segmentCount: segmentation.segmentCount ?? 0, isActive: false, isVisible: true, - colorLUTIndex, }; cachedSegmentation = this.segmentations[segmentationId]; @@ -976,7 +962,7 @@ class SegmentationService extends PubSubService { // Force use of a Uint8Array SharedArrayBuffer for the segmentation to save space and so // it is easily compressible in worker thread. - await volumeLoader.createAndCacheDerivedVolume(volumeId, { + await volumeLoader.createAndCacheDerivedSegmentationVolume(volumeId, { volumeId: segmentationId, targetBuffer: { type: 'Uint8Array', @@ -1002,6 +988,7 @@ class SegmentationService extends PubSubService { referencedVolumeId: volumeId, // Todo: this is so ugly }, }, + description: `S${displaySet.SeriesNumber}: ${displaySet.SeriesDescription}`, }; this.addOrUpdateSegmentation(segmentation); @@ -1037,8 +1024,6 @@ class SegmentationService extends PubSubService { segmentation.hydrated = true; } - const { colorLUTIndex } = segmentation; - // Based on the segmentationId, set the colorLUTIndex. const segmentationRepresentationUIDs = await cstSegmentation.addSegmentationRepresentations( toolGroupId, @@ -1057,12 +1042,6 @@ class SegmentationService extends PubSubService { segmentationRepresentationUIDs[0] ); - cstSegmentation.config.color.setColorLUT( - toolGroupId, - segmentationRepresentationUIDs[0], - colorLUTIndex - ); - // add the segmentation segments properly for (const segment of segmentation.segments) { if (segment === null || segment === undefined) { @@ -1168,6 +1147,10 @@ class SegmentationService extends PubSubService { displaySet.isHydrated = isHydrated; displaySetService.setDisplaySetMetadataInvalidated(displaySetUID, false); + + this._broadcastEvent(this.EVENTS.SEGMENTATION_UPDATED, { + segmentation: this.getSegmentation(displaySetUID), + }); } private _highlightLabelmap( @@ -1310,7 +1293,7 @@ class SegmentationService extends PubSubService { } const { colorLUTIndex } = segmentation; - this._removeSegmentationFromCornerstone(segmentationId); + const { updatedToolGroupIds } = this._removeSegmentationFromCornerstone(segmentationId); // Delete associated colormap // Todo: bring this back @@ -1330,7 +1313,9 @@ class SegmentationService extends PubSubService { if (remainingHydratedSegmentations.length) { const { id } = remainingHydratedSegmentations[0]; - this._setActiveSegmentationForToolGroup(id, this._getApplicableToolGroupId(), false); + updatedToolGroupIds.forEach(toolGroupId => { + this._setActiveSegmentationForToolGroup(id, toolGroupId, false); + }); } } @@ -1390,8 +1375,6 @@ class SegmentationService extends PubSubService { public setConfiguration = (configuration: SegmentationConfig): void => { const { - brushSize, - brushThresholdGate, fillAlpha, fillAlphaInactive, outlineWidthActive, @@ -1483,7 +1466,6 @@ class SegmentationService extends PubSubService { segmentInfo.label = label; if (suppressEvents === false) { - // this._setSegmentationModified(segmentationId); this._broadcastEvent(this.EVENTS.SEGMENTATION_UPDATED, { segmentation, }); @@ -1530,7 +1512,6 @@ class SegmentationService extends PubSubService { segments: [], isVisible: true, isActive: false, - colorLUTIndex: 0, }; } @@ -1991,6 +1972,7 @@ class SegmentationService extends PubSubService { const removeFromCache = true; const segmentationState = cstSegmentation.state; const sourceSegState = segmentationState.getSegmentation(segmentationId); + const updatedToolGroupIds: Set = new Set(); if (!sourceSegState) { return; @@ -2006,6 +1988,7 @@ class SegmentationService extends PubSubService { segmentationRepresentations.forEach(representation => { if (representation.segmentationId === segmentationId) { UIDsToRemove.push(representation.segmentationRepresentationUID); + updatedToolGroupIds.add(toolGroupId); } }); @@ -2023,6 +2006,8 @@ class SegmentationService extends PubSubService { if (removeFromCache && cache.getVolumeLoadObject(segmentationId)) { cache.removeVolumeLoadObject(segmentationId); } + + return { updatedToolGroupIds: Array.from(updatedToolGroupIds) }; } private _updateCornerstoneSegmentations({ segmentationId, notYetUpdatedAtSource }) { @@ -2124,23 +2109,6 @@ class SegmentationService extends PubSubService { return viewportInfo.getToolGroupId(); }; - private getNextColorLUTIndex = (): number => { - let i = 0; - while (true) { - if (cstSegmentation.state.getColorLUT(i) === undefined) { - return i; - } - - i++; - } - }; - - private generateNewColorLUT() { - const newColorLUT = cloneDeep(COLOR_LUT); - - return newColorLUT; - } - /** * Converts object of objects to array. * diff --git a/extensions/cornerstone/src/services/SegmentationService/SegmentationServiceTypes.ts b/extensions/cornerstone/src/services/SegmentationService/SegmentationServiceTypes.ts index d6d231fd0..cb65154d2 100644 --- a/extensions/cornerstone/src/services/SegmentationService/SegmentationServiceTypes.ts +++ b/extensions/cornerstone/src/services/SegmentationService/SegmentationServiceTypes.ts @@ -20,6 +20,8 @@ type Segment = { isVisible: boolean; // whether the segment is locked isLocked: boolean; + // display texts + displayText?: string[]; }; type Segmentation = { @@ -67,4 +69,4 @@ type SegmentationRepresentationData = { LABELMAP?: LabelmapSegmentationData; }; -export { SegmentationConfig, Segment, Segmentation }; +export type { SegmentationConfig, Segment, Segmentation }; diff --git a/extensions/cornerstone/src/services/SyncGroupService/SyncGroupService.ts b/extensions/cornerstone/src/services/SyncGroupService/SyncGroupService.ts index 0b1c22fb2..b9c9be682 100644 --- a/extensions/cornerstone/src/services/SyncGroupService/SyncGroupService.ts +++ b/extensions/cornerstone/src/services/SyncGroupService/SyncGroupService.ts @@ -1,4 +1,5 @@ import { synchronizers, SynchronizerManager, Synchronizer } from '@cornerstonejs/tools'; +import { getRenderingEngines, utilities } from '@cornerstonejs/core'; import { pubSubServiceInterface, Types, ServicesManager } from '@ohif/core'; @@ -25,6 +26,7 @@ const POSITION = 'cameraposition'; const VOI = 'voi'; const ZOOMPAN = 'zoompan'; const STACKIMAGE = 'stackimage'; +const IMAGE_SLICE = 'imageslice'; const asSyncGroup = (syncGroup: string | SyncGroup): SyncGroup => typeof syncGroup === 'string' ? { type: syncGroup } : syncGroup; @@ -45,9 +47,14 @@ export default class SyncGroupService { [POSITION]: synchronizers.createCameraPositionSynchronizer, [VOI]: synchronizers.createVOISynchronizer, [ZOOMPAN]: synchronizers.createZoomPanSynchronizer, - [STACKIMAGE]: synchronizers.createStackImageSynchronizer, + // todo: remove stack image since it is legacy now and the image_slice + // handles both stack and volume viewports + [STACKIMAGE]: synchronizers.createImageSliceSynchronizer, + [IMAGE_SLICE]: synchronizers.createImageSliceSynchronizer, }; + synchronizersByType: { [key: string]: Synchronizer[] } = {}; + constructor(serviceManager: ServicesManager) { this.servicesManager = serviceManager; this.listeners = {}; @@ -57,14 +64,27 @@ export default class SyncGroupService { } private _createSynchronizer(type: string, id: string, options): Synchronizer | undefined { + // Initialize if not already done + this.synchronizersByType[type] = this.synchronizersByType[type] || []; + const syncCreator = this.synchronizerCreators[type.toLowerCase()]; + if (syncCreator) { - return syncCreator(id, options); + const synchronizer = syncCreator(id, options); + + if (synchronizer) { + this.synchronizersByType[type].push(synchronizer); + return synchronizer; + } } else { - console.warn('Unknown synchronizer type', type, id); + console.warn(`Unknown synchronizer type: ${type}, id: ${id}`); } } + public getSyncCreatorForType(type: string): SyncCreator { + return this.synchronizerCreators[type.toLowerCase()]; + } + /** * Creates a synchronizer type. * @param type is the type of the synchronizer to create @@ -74,6 +94,19 @@ export default class SyncGroupService { this.synchronizerCreators[type.toLowerCase()] = creator; } + public getSynchronizer(id: string): Synchronizer | void { + return SynchronizerManager.getSynchronizer(id); + } + + /** + * Retrieves an array of synchronizers of a specific type. + * @param type - The type of synchronizers to retrieve. + * @returns An array of synchronizers of the specified type. + */ + public getSynchronizersOfType(type: string): Synchronizer[] { + return this.synchronizersByType[type]; + } + protected _getOrCreateSynchronizer( type: string, id: string, @@ -121,6 +154,20 @@ export default class SyncGroupService { SynchronizerManager.destroy(); } + public getSynchronizersForViewport(viewportId: string): Synchronizer[] { + const renderingEngine = + getRenderingEngines().find(re => { + return re.getViewports().find(vp => vp.id === viewportId); + }) || getRenderingEngines()[0]; + + const synchronizers = SynchronizerManager.getAllSynchronizers(); + return synchronizers.filter( + s => + s.hasSourceViewport(renderingEngine.id, viewportId) || + s.hasTargetViewport(renderingEngine.id, viewportId) + ); + } + public removeViewportFromSyncGroup( viewportId: string, renderingEngineId: string, @@ -137,6 +184,11 @@ export default class SyncGroupService { return; } + // Only image slice synchronizer register spatial registration + if (this.isImageSliceSyncronizer(synchronizer)) { + this.unRegisterSpatialRegistration(synchronizer); + } + synchronizer.remove({ viewportId, renderingEngineId, @@ -151,4 +203,45 @@ export default class SyncGroupService { } }); } + /** + * Clean up the spatial registration metadata created by synchronizer + * This is needed to be able to re-sync images slices if needed + * @param synchronizer + */ + unRegisterSpatialRegistration(synchronizer: Synchronizer) { + const sourceViewports = synchronizer.getSourceViewports().map(vp => vp.viewportId); + const targetViewports = synchronizer.getTargetViewports().map(vp => vp.viewportId); + + // Create an array of pair of viewports to remove from spatialRegistrationMetadataProvider + // All sourceViewports combined with all targetViewports + const toUnregister = sourceViewports + .map((sourceViewportId: string) => { + return targetViewports.map(targetViewportId => [targetViewportId, sourceViewportId]); + }) + .reduce((acc, c) => acc.concat(c), []); + + toUnregister.forEach(viewportIdPair => { + utilities.spatialRegistrationMetadataProvider.add(viewportIdPair, undefined); + }); + } + /** + * Check if the synchronizer type is IMAGE_SLICE + * Need to convert to lowercase here because the types are lowercase + * e.g: synchronizerCreators + * @param synchronizer + */ + isImageSliceSyncronizer(synchronizer: Synchronizer) { + return this.getSynchronizerType(synchronizer).toLowerCase() === IMAGE_SLICE; + } + /** + * Returns the syncronizer type + * @param synchronizer + */ + getSynchronizerType(synchronizer: Synchronizer): string { + const synchronizerTypes = Object.keys(this.synchronizersByType); + const syncType = synchronizerTypes.find(syncType => + this.getSynchronizersOfType(syncType).includes(synchronizer) + ); + return syncType; + } } diff --git a/extensions/cornerstone/src/services/ToolGroupService/ToolGroupService.ts b/extensions/cornerstone/src/services/ToolGroupService/ToolGroupService.ts index 3a85342b3..a6926ad2c 100644 --- a/extensions/cornerstone/src/services/ToolGroupService/ToolGroupService.ts +++ b/extensions/cornerstone/src/services/ToolGroupService/ToolGroupService.ts @@ -1,4 +1,5 @@ import { ToolGroupManager, Enums, Types } from '@cornerstonejs/tools'; +import { eventTarget } from '@cornerstonejs/core'; import { Types as OhifTypes, pubSubServiceInterface } from '@ohif/core'; import getActiveViewportEnabledElement from '../../utils/getActiveViewportEnabledElement'; @@ -6,6 +7,8 @@ import getActiveViewportEnabledElement from '../../utils/getActiveViewportEnable const EVENTS = { VIEWPORT_ADDED: 'event::cornerstone::toolgroupservice:viewportadded', TOOLGROUP_CREATED: 'event::cornerstone::toolgroupservice:toolgroupcreated', + TOOL_ACTIVATED: 'event::cornerstone::toolgroupservice:toolactivated', + PRIMARY_TOOL_ACTIVATED: 'event::cornerstone::toolgroupservice:primarytoolactivated', }; type Tool = { @@ -38,18 +41,26 @@ export default class ToolGroupService { EVENTS: { [key: string]: string }; constructor(serviceManager) { - const { cornerstoneViewportService, viewportGridService } = serviceManager.services; + const { cornerstoneViewportService, viewportGridService, uiNotificationService } = + serviceManager.services; this.cornerstoneViewportService = cornerstoneViewportService; this.viewportGridService = viewportGridService; + this.uiNotificationService = uiNotificationService; this.listeners = {}; this.EVENTS = EVENTS; Object.assign(this, pubSubServiceInterface); + + this._init(); } onModeExit() { this.destroy(); } + private _init() { + eventTarget.addEventListener(Enums.Events.TOOL_ACTIVATED, this._onToolActivated); + } + /** * Retrieves a tool group from the ToolGroupManager by tool group ID. * If no tool group ID is provided, it retrieves the tool group of the active viewport. @@ -105,12 +116,14 @@ export default class ToolGroupService { return toolGroup.getActivePrimaryMouseButtonTool(); } - public destroy() { + public destroy(): void { ToolGroupManager.destroy(); this.toolGroupIds = new Set(); + + eventTarget.removeEventListener(Enums.Events.TOOL_ACTIVATED, this._onToolActivated); } - public destroyToolGroup(toolGroupId: string) { + public destroyToolGroup(toolGroupId: string): void { ToolGroupManager.destroyToolGroup(toolGroupId); this.toolGroupIds.delete(toolGroupId); } @@ -189,19 +202,6 @@ export default class ToolGroupService { this.addToolsToToolGroup(toolGroupId, tools); return toolGroup; } - - /** - private changeConfigurationIfNecessary(toolGroup, volumeUID) { - // handle specific assignment for volumeUID (e.g., fusion) - const toolInstances = toolGroup._toolInstances; - // Object.values(toolInstances).forEach(toolInstance => { - // if (toolInstance.configuration) { - // toolInstance.configuration.volumeUID = volumeUID; - // } - // }); - } - */ - /** * Get the tool's configuration based on the tool name and tool group id * @param toolGroupId - The id of the tool group that the tool instance belongs to. @@ -235,6 +235,10 @@ export default class ToolGroupService { toolInstance.configuration = config; } + public getActivePrimaryMouseButtonTool(toolGroupId?: string): string { + return this.getToolGroup(toolGroupId)?.getActivePrimaryMouseButtonTool(); + } + private _setToolsMode(toolGroup, tools) { const { active, passive, enabled, disabled } = tools; @@ -292,4 +296,23 @@ export default class ToolGroupService { addTools(tools.disabled); } } + + private _onToolActivated = (evt: Types.EventTypes.ToolActivatedEventType) => { + const { toolGroupId, toolName, toolBindingsOptions } = evt.detail; + const isPrimaryTool = toolBindingsOptions.bindings?.some( + binding => binding.mouseButton === Enums.MouseBindings.Primary + ); + + const callbackProps = { + toolGroupId, + toolName, + toolBindingsOptions, + }; + + this._broadcastEvent(EVENTS.TOOL_ACTIVATED, callbackProps); + + if (isPrimaryTool) { + this._broadcastEvent(EVENTS.PRIMARY_TOOL_ACTIVATED, callbackProps); + } + }; } diff --git a/extensions/cornerstone/src/services/ViewportActionCornersService/ViewportActionCornersService.ts b/extensions/cornerstone/src/services/ViewportActionCornersService/ViewportActionCornersService.ts new file mode 100644 index 000000000..baf155764 --- /dev/null +++ b/extensions/cornerstone/src/services/ViewportActionCornersService/ViewportActionCornersService.ts @@ -0,0 +1,71 @@ +import { PubSubService } from '@ohif/core'; +import { ViewportActionCornersLocations } from '@ohif/ui'; +import { ReactNode } from 'react'; + +export type ActionComponentInfo = { + viewportId: string; + id: string; + component: ReactNode; + location: ViewportActionCornersLocations; + indexPriority: number; +}; + +class ViewportActionCornersService extends PubSubService { + public static readonly EVENTS = {}; + public static readonly LOCATIONS = ViewportActionCornersLocations; + + public static REGISTRATION = { + name: 'viewportActionCornersService', + altName: 'ViewportActionCornersService', + create: ({ configuration = {} }) => { + return new ViewportActionCornersService(); + }, + }; + + serviceImplementation = {}; + + public LOCATIONS = ViewportActionCornersService.LOCATIONS; + + constructor() { + super(ViewportActionCornersService.EVENTS); + this.serviceImplementation = {}; + } + + public setServiceImplementation({ + getState: getStateImplementation, + setComponent: setComponentImplementation, + setComponents: setComponentsImplementation, + clear: clearImplementation, + }): void { + if (getStateImplementation) { + this.serviceImplementation._getState = getStateImplementation; + } + if (setComponentImplementation) { + this.serviceImplementation._setComponent = setComponentImplementation; + } + if (setComponentsImplementation) { + this.serviceImplementation._setComponents = setComponentsImplementation; + } + if (clearImplementation) { + this.serviceImplementation._clear = clearImplementation; + } + } + + public getState() { + return this.serviceImplementation._getState(); + } + + public setComponent(component: ActionComponentInfo) { + this.serviceImplementation._setComponent(component); + } + + public setComponents(components: Array) { + this.serviceImplementation._setComponents(components); + } + + public clear(viewportId: string) { + this.serviceImplementation._clear(viewportId); + } +} + +export default ViewportActionCornersService; diff --git a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts index 497df2d54..9fd4f3ce5 100644 --- a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts +++ b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts @@ -10,6 +10,7 @@ import { VolumeViewport3D, cache, Enums as csEnums, + BaseVolumeViewport, } from '@cornerstonejs/core'; import { utilities as csToolsUtils, Enums as csToolsEnums } from '@cornerstonejs/tools'; @@ -17,12 +18,13 @@ import { IViewportService } from './IViewportService'; import { RENDERING_ENGINE_ID } from './constants'; import ViewportInfo, { DisplaySetOptions, PublicViewportOptions } from './Viewport'; import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService'; -import { Presentation, Presentations } from '../../types/Presentation'; +import { LutPresentation, PositionPresentation, Presentations } from '../../types/Presentation'; import JumpPresets from '../../utils/JumpPresets'; const EVENTS = { VIEWPORT_DATA_CHANGED: 'event::cornerstoneViewportService:viewportDataChanged', + VIEWPORT_VOLUMES_CHANGED: 'event::cornerstoneViewportService:viewportVolumesChanged', }; /** @@ -44,6 +46,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi viewportsById: Map = new Map(); viewportGridResizeObserver: ResizeObserver | null; viewportsDisplaySets: Map = new Map(); + beforeResizePositionPresentations: Map = new Map(); // Some configs enableResizeDetector: true; @@ -51,6 +54,11 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi resizeRefreshMode: 'debounce'; servicesManager = null; + resizeQueue = []; + viewportResizeTimer = null; + gridResizeDelay = 50; + gridResizeTimeOut = null; + constructor(servicesManager: ServicesManager) { super(EVENTS); this.renderingEngine = null; @@ -94,14 +102,28 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi } /** - * It triggers the resize on the rendering engine. + * It triggers the resize on the rendering engine, and renders the viewports + * + * @param isGridResize - if the resize is triggered by a grid resize + * this is used to avoid double resize of the viewports since if the + * grid is resized, all viewports will be resized so there is no need + * to resize them individually which will get triggered by their + * individual resize observers */ - public resize() { - const immediate = true; - const keepCamera = true; - - this.renderingEngine.resize(immediate, keepCamera); - this.renderingEngine.render(); + public resize(isGridResize = false) { + // if there is a grid resize happening, it means the viewport grid + // has been manipulated (e.g., panels closed, added, etc.) and we need + // to resize all viewports, so we will add a timeout here to make sure + // we don't double resize the viewports when viewports in the grid are + // resized individually + if (isGridResize) { + this.performResize(); + this.resetGridResizeTimeout(); + this.resizeQueue = []; + clearTimeout(this.viewportResizeTimer); + } else { + this.enqueueViewportResizeRequest(); + } } /** @@ -138,74 +160,237 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi this.viewportsDisplaySets.delete(viewportId); } - public setPresentations(viewport, presentations?: Presentations): void { - const properties = presentations?.lutPresentation?.properties; - if (properties) { - viewport.setProperties(properties); + /** + * Sets the presentations for a given viewport. Presentations is an object + * that can define the lut or position for a viewport. + * + * @param viewportId - The ID of the viewport. + * @param presentations - The presentations to apply to the viewport. + */ + public setPresentations(viewportId: string, presentations?: Presentations): void { + const viewport = this.getCornerstoneViewport(viewportId) as + | Types.IStackViewport + | Types.IVolumeViewport; + + if (!viewport) { + return; } - const camera = presentations?.positionPresentation?.camera; - if (camera) { - viewport.setCamera(camera); + + if (!presentations) { + return; + } + + const { lutPresentation, positionPresentation } = presentations; + if (lutPresentation) { + const { presentation } = lutPresentation; + if (viewport instanceof BaseVolumeViewport) { + if (presentation instanceof Map) { + presentation.forEach((properties, volumeId) => { + viewport.setProperties(properties, volumeId); + }); + } else { + viewport.setProperties(presentation); + } + } else { + viewport.setProperties(presentation); + } + } + + if (positionPresentation) { + const { viewPlaneNormal, viewUp, zoom, pan } = positionPresentation.presentation; + viewport.setCamera({ viewPlaneNormal, viewUp }); + + if (zoom !== undefined) { + viewport.setZoom(zoom); + } + + if (pan !== undefined) { + viewport.setPan(pan); + } } } - public getPresentation(viewportId: string): Presentation { + /** + * Retrieves the position presentation information for a given viewport. + * @param viewportId The ID of the viewport. + * @returns The position presentation object containing various properties + * such as ID, viewport type, initial image index, view plane normal, view up, zoom, and pan. + */ + public getPositionPresentation(viewportId: string): PositionPresentation { const viewportInfo = this.viewportsById.get(viewportId); if (!viewportInfo) { return; } - const { viewportType, presentationIds } = viewportInfo.getViewportOptions(); + + const presentationIds = viewportInfo.getPresentationIds(); + + if (!presentationIds) { + return; + } + + const { positionPresentationId } = presentationIds; const csViewport = this.getCornerstoneViewport(viewportId); if (!csViewport) { return; } - const properties = csViewport.getProperties(); - if (properties.isComputedVOI) { - delete properties.voiRange; - delete properties.VOILUTFunction; - } - const initialImageIndex = csViewport.getCurrentImageIdIndex(); - const camera = csViewport.getCamera(); + const { viewPlaneNormal, viewUp } = csViewport.getCamera(); + const initialImageIndex = csViewport.getCurrentImageIdIndex() || 0; + const zoom = csViewport.getZoom(); + const pan = csViewport.getPan(); + return { - presentationIds, - viewportType: !viewportType || viewportType === 'stack' ? 'stack' : 'volume', - properties, - initialImageIndex, - camera, + id: positionPresentationId, + viewportType: viewportInfo.getViewportType(), + presentation: { + initialImageIndex, + viewUp, + viewPlaneNormal, + zoom, + pan, + }, }; } - public storePresentation({ viewportId }) { - const stateSyncService = this.servicesManager.services.stateSyncService; - let presentation; - try { - presentation = this.getPresentation(viewportId); - } catch (error) { - console.warn(error); - } - - if (!presentation || !presentation.presentationIds) { + /** + * Retrieves the LUT (Lookup Table) presentation for a given viewport. + * @param viewportId The ID of the viewport. + * @returns The LUT presentation object, or undefined if the viewport does not exist. + */ + public getLutPresentation(viewportId: string): LutPresentation { + const viewportInfo = this.viewportsById.get(viewportId); + if (!viewportInfo) { return; } - const { lutPresentationStore, positionPresentationStore } = stateSyncService.getState(); - const { presentationIds } = presentation; - const { lutPresentationId, positionPresentationId } = presentationIds || {}; - const storeState = {}; + + const presentationIds = viewportInfo.getPresentationIds(); + + if (!presentationIds) { + return; + } + + const { lutPresentationId } = presentationIds; + + const csViewport = this.getCornerstoneViewport(viewportId) as + | Types.IStackViewport + | Types.IVolumeViewport; + + if (!csViewport) { + return; + } + + const cleanProperties = properties => { + if (properties.isComputedVOI) { + delete properties.voiRange; + delete properties.VOILUTFunction; + } + return properties; + }; + + const presentation = + csViewport instanceof BaseVolumeViewport + ? new Map() + : cleanProperties(csViewport.getProperties()); + + if (presentation instanceof Map) { + csViewport.getActors().forEach(({ uid: volumeId }) => { + const properties = cleanProperties(csViewport.getProperties(volumeId)); + presentation.set(volumeId, properties); + }); + } + + return { + id: lutPresentationId, + viewportType: viewportInfo.getViewportType(), + presentation, + }; + } + + /** + * Retrieves the presentations for a given viewport. + * @param viewportId - The ID of the viewport. + * @returns The presentations for the viewport. + */ + public getPresentations(viewportId: string): Presentations { + const viewportInfo = this.viewportsById.get(viewportId); + if (!viewportInfo) { + return; + } + + const positionPresentation = this.getPositionPresentation(viewportId); + const lutPresentation = this.getLutPresentation(viewportId); + + return { + positionPresentation, + lutPresentation, + }; + } + + /** + * Stores the presentation state for a given viewport inside the + * stateSyncService. This is used to persist the presentation state + * across different scenarios e.g., when the viewport is changing the + * display set, or when the viewport is moving to a different layout. + * + * @param viewportId The ID of the viewport. + */ + public storePresentation({ viewportId }) { + let presentations = null as Presentations; + try { + presentations = this.getPresentations(viewportId); + if (!presentations?.positionPresentation && !presentations?.lutPresentation) { + return; + } + } catch (error) { + console.warn(error); + return; + } + + const { stateSyncService, syncGroupService } = this.servicesManager.services; + + const synchronizers = syncGroupService.getSynchronizersForViewport(viewportId); + + const { positionPresentationStore, synchronizersStore, lutPresentationStore } = + stateSyncService.getState(); + + const { lutPresentation, positionPresentation } = presentations; + const { id: positionPresentationId } = positionPresentation; + const { id: lutPresentationId } = lutPresentation; + + const updateStore = (store, id, value) => ({ ...store, [id]: value }); + + const newState = {} as { [key: string]: any }; + if (lutPresentationId) { - storeState.lutPresentationStore = { - ...lutPresentationStore, - [lutPresentationId]: presentation, - }; + newState.lutPresentationStore = updateStore( + lutPresentationStore, + lutPresentationId, + lutPresentation + ); } + if (positionPresentationId) { - storeState.positionPresentationStore = { - ...positionPresentationStore, - [positionPresentationId]: presentation, - }; + newState.positionPresentationStore = updateStore( + positionPresentationStore, + positionPresentationId, + positionPresentation + ); } - stateSyncService.store(storeState); + + if (synchronizers?.length) { + newState.synchronizersStore = updateStore( + synchronizersStore, + viewportId, + synchronizers.map(synchronizer => ({ + id: synchronizer.id, + sourceViewports: [...synchronizer.getSourceViewports()], + targetViewports: [...synchronizer.getTargetViewports()], + })) + ); + } + + stateSyncService.store(newState); } /** @@ -283,20 +468,31 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi this.viewportsById.set(viewportId, viewportInfo); const viewport = renderingEngine.getViewport(viewportId); - this._setDisplaySets(viewport, viewportData, viewportInfo, presentations); + const displaySetPromise = this._setDisplaySets( + viewport, + viewportData, + viewportInfo, + presentations + ); // The broadcast event here ensures that listeners have a valid, up to date // viewport to access. Doing it too early can result in exceptions or // invalid data. - this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, { - viewportData, - viewportId, + displaySetPromise.then(() => { + this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, { + viewportData, + viewportId, + }); }); } - public getCornerstoneViewport( - viewportId: string - ): Types.IStackViewport | Types.IVolumeViewport | null { + /** + * Retrieves the Cornerstone viewport with the specified ID. + * + * @param viewportId - The ID of the viewport. + * @returns The Cornerstone viewport object if found, otherwise null. + */ + public getCornerstoneViewport(viewportId: string): Types.IViewport | null { const viewportInfo = this.getViewportInfo(viewportId); if (!viewportInfo || !this.renderingEngine || this.renderingEngine.hasBeenDestroyed) { @@ -308,16 +504,57 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi return viewport; } + /** + * Retrieves the viewport information for a given viewport ID. The viewport information + * is the OHIF construct that holds different options and data for a given viewport and + * is different from the cornerstone viewport. + * + * @param viewportId The ID of the viewport. + * @returns The viewport information. + */ public getViewportInfo(viewportId: string): ViewportInfo { return this.viewportsById.get(viewportId); } - _setStackViewport( + /** + * Looks through the viewports to see if the specified measurement can be + * displayed in one of the viewports. + * + * @param measurement + * The measurement that is desired to view. + * @param activeViewportId - the index that was active at the time the jump + * was initiated. + * @return the viewportId that the measurement should be displayed in. + */ + public getViewportIdToJump( + activeViewportId: string, + displaySetInstanceUID: string, + cameraProps: unknown + ): string { + const viewportInfo = this.getViewportInfo(activeViewportId); + + if (viewportInfo.getViewportType() === csEnums.ViewportType.VOLUME_3D) { + return null; + } + + const { referencedImageId } = cameraProps; + if (viewportInfo?.contains(displaySetInstanceUID, referencedImageId)) { + return activeViewportId; + } + + return ( + [...this.viewportsById.values()].find(viewportInfo => + viewportInfo.contains(displaySetInstanceUID, referencedImageId) + )?.viewportId ?? null + ); + } + + private async _setStackViewport( viewport: Types.IStackViewport, viewportData: StackViewportData, viewportInfo: ViewportInfo, - presentations: Presentations - ): void { + presentations: Presentations = {} + ): Promise { const displaySetOptions = viewportInfo.getDisplaySetOptions(); const { imageIds, initialImageIndex, displaySetInstanceUID } = viewportData.data; @@ -333,7 +570,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi const properties = { ...presentations.lutPresentation?.properties }; if (!presentations.lutPresentation?.properties) { - const { voi, voiInverted } = displaySetOptions[0]; + const { voi, voiInverted, colormap } = displaySetOptions[0]; if (voi && (voi.windowWidth || voi.windowCenter)) { const { lower, upper } = csUtils.windowLevel.toLowHighRange( voi.windowWidth, @@ -345,14 +582,15 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi if (voiInverted !== undefined) { properties.invert = voiInverted; } + + if (colormap !== undefined) { + properties.colormap = colormap; + } } - viewport.setStack(imageIds, initialImageIndexToUse).then(() => { + return viewport.setStack(imageIds, initialImageIndexToUse).then(() => { viewport.setProperties({ ...properties }); - const camera = presentations.positionPresentation?.camera; - if (camera) { - viewport.setCamera(camera); - } + this.setPresentations(viewport.id, presentations); }); } @@ -420,7 +658,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi viewport: Types.IVolumeViewport, viewportData: VolumeViewportData, viewportInfo: ViewportInfo, - presentations: Presentations + presentations: Presentations = {} ): Promise { // TODO: We need to overhaul the way data sources work so requests can be made // async. I think we should follow the image loader pattern which is async and @@ -465,29 +703,37 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi this.viewportsDisplaySets.set(viewport.id, displaySetInstanceUIDs); - if (hangingProtocolService.getShouldPerformCustomImageLoad()) { - // delegate the volume loading to the hanging protocol service if it has a custom image load strategy - return hangingProtocolService.runImageLoadStrategy({ - viewportId: viewport.id, - volumeInputArray, + const volumesNotLoaded = volumeToLoad.filter(volume => !volume.loadStatus.loaded); + + if (volumesNotLoaded.length) { + if (hangingProtocolService.getShouldPerformCustomImageLoad()) { + // delegate the volume loading to the hanging protocol service if it has a custom image load strategy + return hangingProtocolService.runImageLoadStrategy({ + viewportId: viewport.id, + volumeInputArray, + }); + } + + volumesNotLoaded.forEach(volume => { + if (!volume.loadStatus.loading) { + volume.load(); + } }); } - volumeToLoad.forEach(volume => { - if (!volume.loadStatus.loaded && !volume.loadStatus.loading) { - volume.load(); - } - }); - // This returns the async continuation only return this.setVolumesForViewport(viewport, volumeInputArray, presentations); } public async setVolumesForViewport(viewport, volumeInputArray, presentations) { - const { displaySetService, toolGroupService } = this.servicesManager.services; + const { displaySetService, toolGroupService, viewportGridService } = + this.servicesManager.services; const viewportInfo = this.getViewportInfo(viewport.id); const displaySetOptions = viewportInfo.getDisplaySetOptions(); + const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewport.id); + const displaySet = displaySetService.getDisplaySetByUID(displaySetUIDs[0]); + const displaySetModality = displaySet?.Modality; // Todo: use presentations states const volumesProperties = volumeInputArray.map((volumeInput, index) => { @@ -513,7 +759,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi } if (displayPreset !== undefined) { - properties.preset = displayPreset; + properties.preset = displayPreset[displaySetModality] || displayPreset.default; } return { properties, volumeId }; @@ -524,7 +770,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi viewport.setProperties(properties, volumeId); }); - this.setPresentations(viewport, presentations); + this.setPresentations(viewport.id, presentations); // load any secondary displaySets const displaySetInstanceUIDs = this.viewportsDisplaySets.get(viewport.id); @@ -557,6 +803,10 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi } viewport.render(); + + this._broadcastEvent(this.EVENTS.VIEWPORT_VOLUMES_CHANGED, { + viewportInfo, + }); } private _addSegmentationRepresentationToToolGroupIfNecessary( @@ -654,46 +904,54 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi const viewport = this.getCornerstoneViewport(viewportId); const viewportCamera = viewport.getCamera(); + let displaySetPromise; + if (viewport instanceof VolumeViewport || viewport instanceof VolumeViewport3D) { - this._setVolumeViewport(viewport, viewportData, viewportInfo).then(() => { + displaySetPromise = this._setVolumeViewport(viewport, viewportData, viewportInfo).then(() => { if (keepCamera) { viewport.setCamera(viewportCamera); viewport.render(); } }); - - return; } if (viewport instanceof StackViewport) { - this._setStackViewport(viewport, viewportData, viewportInfo); - return; + displaySetPromise = this._setStackViewport(viewport, viewportData, viewportInfo); } + + displaySetPromise.then(() => { + this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, { + viewportData, + viewportId, + }); + }); } _setDisplaySets( - viewport: StackViewport | VolumeViewport, + viewport: Types.IViewport, viewportData: StackViewportData | VolumeViewportData, viewportInfo: ViewportInfo, presentations: Presentations = {} - ): void { + ): Promise { if (viewport instanceof StackViewport) { - this._setStackViewport( + return this._setStackViewport( viewport, viewportData as StackViewportData, viewportInfo, presentations ); - } else if (viewport instanceof VolumeViewport || viewport instanceof VolumeViewport3D) { - this._setVolumeViewport( - viewport, + } + + if ([VolumeViewport, VolumeViewport3D].some(type => viewport instanceof type)) { + return this._setVolumeViewport( + viewport as Types.IVolumeViewport, viewportData as VolumeViewportData, viewportInfo, presentations ); - } else { - throw new Error('Unknown viewport type'); } + + throw new Error('Unknown viewport type'); } /** @@ -759,32 +1017,62 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi } } - /** - * Looks through the viewports to see if the specified measurement can be - * displayed in one of the viewports. - * - * @param measurement - * The measurement that is desired to view. - * @param activeViewportId - the index that was active at the time the jump - * was initiated. - * @return the viewportId that the measurement should be displayed in. - */ - public getViewportIdToJump( - activeViewportId: string, - displaySetInstanceUID: string, - cameraProps: unknown - ): string { - const viewportInfo = this.getViewportInfo(activeViewportId); - const { referencedImageId } = cameraProps; - if (viewportInfo?.contains(displaySetInstanceUID, referencedImageId)) { - return activeViewportId; + private enqueueViewportResizeRequest() { + this.resizeQueue.push(false); // false indicates viewport resize + + clearTimeout(this.viewportResizeTimer); + this.viewportResizeTimer = setTimeout(() => { + this.processViewportResizeQueue(); + }, this.gridResizeDelay); + } + + private processViewportResizeQueue() { + const isGridResizeInQueue = this.resizeQueue.some(isGridResize => isGridResize); + if (this.resizeQueue.length > 0 && !isGridResizeInQueue && !this.gridResizeTimeOut) { + this.performResize(); } - return ( - [...this.viewportsById.values()].find(viewportInfo => - viewportInfo.contains(displaySetInstanceUID, referencedImageId) - )?.viewportId ?? null - ); + // Clear the queue after processing viewport resizes + this.resizeQueue = []; + } + + private performResize() { + const isImmediate = false; + + try { + const viewports = this.getRenderingEngine().getViewports(); + + // Store the current position presentations for each viewport. + viewports.forEach(({ id }) => { + const presentation = this.getPositionPresentation(id); + this.beforeResizePositionPresentations.set(id, presentation); + }); + + // Resize the rendering engine and render. + const renderingEngine = this.renderingEngine; + renderingEngine.resize(isImmediate); + renderingEngine.render(); + + // Reset the camera for viewports that should reset their camera on resize, + // which means only those viewports that have a zoom level of 1. + this.beforeResizePositionPresentations.forEach((positionPresentation, viewportId) => { + this.setPresentations(viewportId, { positionPresentation }); + }); + + // Resize and render the rendering engine again. + renderingEngine.resize(isImmediate); + renderingEngine.render(); + } catch (e) { + // This can happen if the resize is too close to navigation or shutdown + console.warn('Caught resize exception', e); + } + } + + private resetGridResizeTimeout() { + clearTimeout(this.gridResizeTimeOut); + this.gridResizeTimeOut = setTimeout(() => { + this.gridResizeTimeOut = null; + }, this.gridResizeDelay); } } diff --git a/extensions/cornerstone/src/services/ViewportService/IViewportService.ts b/extensions/cornerstone/src/services/ViewportService/IViewportService.ts index e96a7d702..b16fa989f 100644 --- a/extensions/cornerstone/src/services/ViewportService/IViewportService.ts +++ b/extensions/cornerstone/src/services/ViewportService/IViewportService.ts @@ -1,6 +1,7 @@ import { Types } from '@cornerstonejs/core'; -import { StackData, VolumeData } from '../../types/CornerstoneCacheService'; -import { DisplaySetOptions, PublicViewportOptions, ViewportOptions } from './Viewport'; +import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService'; +import { DisplaySetOptions, PublicViewportOptions } from './Viewport'; +import { Presentations } from '../../types/Presentation'; /** * Handles cornerstone viewport logic including enabling, disabling, and @@ -36,7 +37,7 @@ export interface IViewportService { * the element for resizing events * @param {*} elementRef */ - resize(element: HTMLDivElement): void; + resize(isGridResize: boolean): void; /** * Removes the viewport from cornerstone, and destroys the rendering engine */ @@ -55,8 +56,10 @@ export interface IViewportService { * @returns */ setViewportData( - viewportData: StackData | VolumeData, + viewportId: string, + viewportData: StackViewportData | VolumeViewportData, publicViewportOptions: PublicViewportOptions, - publicDisplaySetOptions: DisplaySetOptions[] + publicDisplaySetOptions: DisplaySetOptions[], + presentations?: Presentations ): void; } diff --git a/extensions/cornerstone/src/services/ViewportService/Viewport.ts b/extensions/cornerstone/src/services/ViewportService/Viewport.ts index 685652279..247b4d3bd 100644 --- a/extensions/cornerstone/src/services/ViewportService/Viewport.ts +++ b/extensions/cornerstone/src/services/ViewportService/Viewport.ts @@ -1,5 +1,11 @@ -import { Types, Enums } from '@cornerstonejs/core'; -import { Types as UITypes } from '@ohif/ui'; +import { + Types, + Enums, + getEnabledElementByViewportId, + VolumeViewport, + utilities, +} from '@cornerstonejs/core'; +import { Types as CoreTypes } from '@ohif/core'; import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService'; import getCornerstoneBlendMode from '../../utils/getCornerstoneBlendMode'; import getCornerstoneOrientation from '../../utils/getCornerstoneOrientation'; @@ -18,7 +24,7 @@ export type ViewportOptions = { toolGroupId: string; viewportId: string; // Presentation ID to store/load presentation state from - presentationIds?: UITypes.PresentationIds; + presentationIds?: CoreTypes.PresentationIds; orientation?: Enums.OrientationAxis; background?: Types.Point3; displayArea?: Types.DisplayArea; @@ -36,7 +42,7 @@ export type PublicViewportOptions = { id?: string; viewportType?: string; toolGroupId?: string; - presentationIds?: UITypes.PresentationIds; + presentationIds?: CoreTypes.PresentationIds; viewportId?: string; orientation?: Enums.OrientationAxis; background?: Types.Point3; @@ -71,7 +77,7 @@ export type DisplaySetOptions = { voiInverted: boolean; blendMode?: Enums.BlendModes; slabThickness?: number; - colormap?: string; + colormap?: { name: string; opacity?: number }; displayPreset?: string; }; @@ -89,13 +95,30 @@ const DEFAULT_TOOLGROUP_ID = 'default'; // Return true if the data contains the given display set UID OR the imageId // if it is a composite object. -const dataContains = (data, displaySetUID: string, imageId?: string): boolean => { - if (data.displaySetInstanceUID === displaySetUID) { - return true; - } +const dataContains = ({ data, displaySetUID, imageId, viewport }): boolean => { if (imageId && data.isCompositeStack && data.imageIds) { return !!data.imageIds.find(dataId => dataId === imageId); } + + if (imageId && (data.volumeId || viewport instanceof VolumeViewport)) { + const isAcquisition = !!viewport.getCurrentImageId(); + + if (!isAcquisition) { + return false; + } + + const imageURI = utilities.imageIdToURI(imageId); + const hasImageId = viewport.hasImageURI(imageURI); + + if (hasImageId) { + return true; + } + } + + if (data.displaySetInstanceUID === displaySetUID) { + return true; + } + return false; }; @@ -122,10 +145,20 @@ class ViewportInfo { return false; } + const { viewport } = getEnabledElementByViewportId(this.viewportId) || {}; + if (this.viewportData.data.length) { - return !!this.viewportData.data.find(data => dataContains(data, displaySetUID, imageId)); + return !!this.viewportData.data.find(data => + dataContains({ data, displaySetUID, imageId, viewport }) + ); } - return dataContains(this.viewportData.data, displaySetUID, imageId); + + return dataContains({ + data: this.viewportData.data, + displaySetUID, + imageId, + viewport, + }); } public destroy = (): void => { @@ -238,6 +271,11 @@ class ViewportInfo { return this.viewportOptions; } + public getPresentationIds(): CoreTypes.PresentationIds { + const { presentationIds } = this.viewportOptions; + return presentationIds; + } + public setDisplaySetOptions(displaySetOptions: Array): void { this.displaySetOptions = displaySetOptions; } diff --git a/extensions/cornerstone/src/tools/CalibrationLineTool.ts b/extensions/cornerstone/src/tools/CalibrationLineTool.ts index 3b4a9bede..c9e008fc6 100644 --- a/extensions/cornerstone/src/tools/CalibrationLineTool.ts +++ b/extensions/cornerstone/src/tools/CalibrationLineTool.ts @@ -66,14 +66,6 @@ export function onCompletedCalibrationLine(servicesManager, csToolsEvent) { calculateLength3(annotationData.handles.points[0], annotationData.handles.points[1]) * 100 ) / 100; - // calculate the currently applied pixel spacing on the viewport - const calibratedPixelSpacing = metaData.get('calibratedPixelSpacing', imageId); - const imagePlaneModule = metaData.get('imagePlaneModule', imageId); - const currentRowPixelSpacing = - calibratedPixelSpacing?.[0] || imagePlaneModule?.rowPixelSpacing || 1; - const currentColumnPixelSpacing = - calibratedPixelSpacing?.[1] || imagePlaneModule?.columnPixelSpacing || 1; - const adjustCalibration = newLength => { const spacingScale = newLength / length; diff --git a/extensions/cornerstone/src/tools/ImageOverlayViewerTool.tsx b/extensions/cornerstone/src/tools/ImageOverlayViewerTool.tsx index 20cb8b9ae..c2ba56cbd 100644 --- a/extensions/cornerstone/src/tools/ImageOverlayViewerTool.tsx +++ b/extensions/cornerstone/src/tools/ImageOverlayViewerTool.tsx @@ -1,8 +1,8 @@ -import { VolumeViewport, metaData } from '@cornerstonejs/core'; -import { utilities } from '@cornerstonejs/core'; +import { VolumeViewport, metaData, utilities } from '@cornerstonejs/core'; import { IStackViewport, IVolumeViewport, Point3 } from '@cornerstonejs/core/dist/esm/types'; import { AnnotationDisplayTool, drawing } from '@cornerstonejs/tools'; -import { guid } from '@ohif/core/src/utils'; +import { guid, b64toBlob } from '@ohif/core/src/utils'; +import OverlayPlaneModuleProvider from './OverlayPlaneModuleProvider'; interface CachedStat { color: number[]; // [r, g, b, a] @@ -27,8 +27,12 @@ interface CachedStat { */ class ImageOverlayViewerTool extends AnnotationDisplayTool { static toolName = 'ImageOverlayViewer'; - private _cachedOverlayMetadata: Map = new Map(); - private _cachedStats: { [key: string]: CachedStat } = {}; + + /** + * The overlay plane module provider add method is exposed here to be used + * when updating the overlay for this tool to use for displaying data. + */ + public static addOverlayPlaneModule = OverlayPlaneModuleProvider.add; constructor( toolProps = {}, @@ -42,10 +46,7 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool { super(toolProps, defaultToolProps); } - onSetToolDisabled = (): void => { - this._cachedStats = {}; - this._cachedOverlayMetadata = new Map(); - }; + onSetToolDisabled = (): void => {}; protected getReferencedImageId(viewport: IStackViewport | IVolumeViewport): string { if (viewport instanceof VolumeViewport) { @@ -64,18 +65,24 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool { return; } - const overlays = - this._cachedOverlayMetadata.get(imageId) ?? - metaData.get('overlayPlaneModule', imageId)?.overlays; + const overlayMetadata = metaData.get('overlayPlaneModule', imageId); + const overlays = overlayMetadata?.overlays; // no overlays if (!overlays?.length) { return; } - this._cachedOverlayMetadata.set(imageId, overlays); + // Fix the x, y positions + overlays.forEach(overlay => { + overlay.x ||= 0; + overlay.y ||= 0; + }); - this._getCachedStat(imageId, overlays, this.configuration.fillColor).then(cachedStat => { + // Will clear cached stat data when the overlay data changes + ImageOverlayViewerTool.addOverlayPlaneModule(imageId, overlayMetadata); + + this._getCachedStat(imageId, overlayMetadata, this.configuration.fillColor).then(cachedStat => { cachedStat.overlays.forEach(overlay => { this._renderOverlay(enabledElement, svgDrawingHelper, overlay); }); @@ -146,15 +153,18 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool { private async _getCachedStat( imageId: string, - overlayMetadata: any[], + overlayMetadata, color: number[] ): Promise { - if (this._cachedStats[imageId] && this._isSameColor(this._cachedStats[imageId].color, color)) { - return this._cachedStats[imageId]; + const missingOverlay = overlayMetadata.overlays.filter( + overlay => overlay.pixelData && !overlay.dataUrl + ); + if (missingOverlay.length === 0) { + return overlayMetadata; } const overlays = await Promise.all( - overlayMetadata + overlayMetadata.overlays .filter(overlay => overlay.pixelData) .map(async (overlay, idx) => { let pixelData = null; @@ -164,6 +174,10 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool { pixelData = overlay.pixelData[0]; } else if (overlay.pixelData.retrieveBulkData) { pixelData = await overlay.pixelData.retrieveBulkData(); + } else if (overlay.pixelData.InlineBinary) { + const blob = b64toBlob(overlay.pixelData.InlineBinary); + const arrayBuffer = await blob.arrayBuffer(); + pixelData = arrayBuffer; } if (!pixelData) { @@ -172,7 +186,7 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool { const dataUrl = this._renderOverlayToDataUrl( { width: overlay.columns, height: overlay.rows }, - color, + overlay.color || color, pixelData ); @@ -184,13 +198,9 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool { }; }) ); + overlayMetadata.overlays = overlays; - this._cachedStats[imageId] = { - color: color, - overlays: overlays.filter(overlay => overlay), - }; - - return this._cachedStats[imageId]; + return overlayMetadata; } /** diff --git a/extensions/cornerstone/src/tools/OverlayPlaneModuleProvider.ts b/extensions/cornerstone/src/tools/OverlayPlaneModuleProvider.ts new file mode 100644 index 000000000..d36b5437b --- /dev/null +++ b/extensions/cornerstone/src/tools/OverlayPlaneModuleProvider.ts @@ -0,0 +1,40 @@ +import { metaData } from '@cornerstonejs/core'; + +const _cachedOverlayMetadata: Map = new Map(); + +/** + * Image Overlay Viewer tool is not a traditional tool that requires user interactin. + * But it is used to display Pixel Overlays. And it will provide toggling capability. + * + * The documentation for Overlay Plane Module of DICOM can be found in [C.9.2 of + * Part-3 of DICOM standard](https://dicom.nema.org/medical/dicom/2018b/output/chtml/part03/sect_C.9.2.html) + * + * Image Overlay rendered by this tool can be toggled on and off using + * toolGroup.setToolEnabled() and toolGroup.setToolDisabled() + */ +const OverlayPlaneModuleProvider = { + /** Adds the metadata for overlayPlaneModule */ + add: (imageId, metadata) => { + if (_cachedOverlayMetadata.get(imageId) === metadata) { + // This is a no-op here as the tool re-caches the data + return; + } + _cachedOverlayMetadata.set(imageId, metadata); + }, + + /** Standard getter for metadata */ + get: (type: string, query: string | string[]) => { + if (Array.isArray(query)) { + return; + } + if (type !== 'overlayPlaneModule') { + return; + } + return _cachedOverlayMetadata.get(query); + }, +}; + +// Needs to be higher priority than default provider +metaData.addProvider(OverlayPlaneModuleProvider.get, 10_000); + +export default OverlayPlaneModuleProvider; diff --git a/extensions/cornerstone/src/types/Colorbar.ts b/extensions/cornerstone/src/types/Colorbar.ts new file mode 100644 index 000000000..79beb199f --- /dev/null +++ b/extensions/cornerstone/src/types/Colorbar.ts @@ -0,0 +1,32 @@ +import { ColorMapPreset } from './Colormap'; +import { CommandsManager, ServicesManager } from '@ohif/core'; + +export type ColorbarOptions = { + position: string; + colormaps: Array; + activeColormapName: string; + ticks: object; + width: string; +}; + +export type ColorbarProps = { + viewportId: string; + commandsManager: CommandsManager; + serviceManager: ServicesManager; + displaySets: Array; + colorbarProperties: ColorbarProperties; +}; + +export type ColorbarProperties = { + width: string; + colorbarTickPosition: string; + colorbarContainerPosition: string; + colormaps: Array; + colorbarInitialColormap: string; +}; + +export enum ChangeTypes { + Removed = 'removed', + Added = 'added', + Modified = 'modified', +} diff --git a/extensions/cornerstone/src/types/Colormap.ts b/extensions/cornerstone/src/types/Colormap.ts new file mode 100644 index 000000000..d0cf1b22a --- /dev/null +++ b/extensions/cornerstone/src/types/Colormap.ts @@ -0,0 +1,16 @@ +import { CommandsManager, ServicesManager } from '@ohif/core'; + +export type ColorMapPreset = { + ColorSpace; + description: string; + RGBPoints; + Name; +}; + +export type ColormapProps = { + viewportId: string; + commandsManager: CommandsManager; + serviceManager: ServicesManager; + colormaps: Array; + displaySets: Array; +}; diff --git a/extensions/cornerstone/src/types/CornerstoneServices.ts b/extensions/cornerstone/src/types/CornerstoneServices.ts index b7b791ae6..a55b4ab47 100644 --- a/extensions/cornerstone/src/types/CornerstoneServices.ts +++ b/extensions/cornerstone/src/types/CornerstoneServices.ts @@ -4,6 +4,7 @@ import SyncGroupService from '../services/SyncGroupService'; import SegmentationService from '../services/SegmentationService'; import CornerstoneCacheService from '../services/CornerstoneCacheService'; import CornerstoneViewportService from '../services/ViewportService/CornerstoneViewportService'; +import ViewportActionCornersService from '../services/ViewportActionCornersService/ViewportActionCornersService'; interface CornerstoneServices extends Types.Services { cornerstoneViewportService: CornerstoneViewportService; @@ -11,6 +12,7 @@ interface CornerstoneServices extends Types.Services { syncGroupService: SyncGroupService; segmentationService: SegmentationService; cornerstoneCacheService: CornerstoneCacheService; + viewportActionCornersService: ViewportActionCornersService; } export default CornerstoneServices; diff --git a/extensions/cornerstone/src/types/Presentation.ts b/extensions/cornerstone/src/types/Presentation.ts index 82f13f745..57737cc44 100644 --- a/extensions/cornerstone/src/types/Presentation.ts +++ b/extensions/cornerstone/src/types/Presentation.ts @@ -1,23 +1,45 @@ -/** Store presentation data for either stack viewports or volume viewports */ -import { Types } from '@cornerstonejs/core'; -import { Types as UITypes } from '@ohif/ui'; +import type { Types } from '@cornerstonejs/core'; /** - * Has information on the presentation of the viewport. + * Represents a position presentation in a viewport. This is basically + * viewport specific camera position and zoom, and not the display set */ -export interface Presentation extends Types.StackViewportProperties { - presentationIds: UITypes.PresentationIds; +export type PositionPresentation = { + id: string; viewportType: string; - initialImageIndex: number; - camera: Types.ICamera; - properties: Types.StackViewportProperties | Types.VolumeViewportProperties; - zoom?: number; - pan?: [number, number]; + presentation: { + initialImageIndex: number; + viewUp: Types.Point3; + viewPlaneNormal: Types.Point3; + zoom?: number; + pan?: Types.Point2; + }; +}; + +/** + * Represents a LUT presentation in a viewport, and is really related + * to displaySets and not the viewport itself. So that is why it can + * be an object with volumeId keys, or a single object with the properties + * itself + */ +export interface LutPresentation { + id: string; + viewportType: string; + presentation: Record | Types.ViewportProperties; } +/** + * Presentation can be a PositionPresentation or a LutPresentation. + */ +type Presentation = PositionPresentation | LutPresentation; + +/** + * Viewport presentations object that can contain a positionPresentation + * and or a lutPresentation. + */ export type Presentations = { - positionPresentation?: Presentation; - lutPresentation?: Presentation; + positionPresentation?: PositionPresentation; + lutPresentation?: LutPresentation; }; export default Presentation; diff --git a/extensions/cornerstone/src/types/ViewportPresets.ts b/extensions/cornerstone/src/types/ViewportPresets.ts new file mode 100644 index 000000000..070a6bbde --- /dev/null +++ b/extensions/cornerstone/src/types/ViewportPresets.ts @@ -0,0 +1,66 @@ +import { ServicesManager, CommandsManager } from '@ohif/core'; + +export type ViewportPreset = { + name: string; + gradientOpacity: string; + specularPower: string; + scalarOpacity: string; + specular: string; + shade: string; + ambient: string; + colorTransfer: string; + diffuse: string; + interpolation: string; +}; + +export type VolumeRenderingPresetsProps = { + viewportId: string; + serviceManager: ServicesManager; + commandsManager: CommandsManager; + volumeRenderingPresets: ViewportPreset[]; +}; + +export type VolumeRenderingPresetsContentProps = { + presets: ViewportPreset[]; + onClose: () => void; + viewportId: string; + commandsManager: CommandsManager; +}; + +export type VolumeRenderingOptionsProps = { + viewportId: string; + commandsManager: CommandsManager; + serviceManager: ServicesManager; + volumeRenderingQualityRange: VolumeRenderingQualityRange; +}; + +export type VolumeRenderingQualityRange = { + min: number; + max: number; + step: number; +}; + +export type VolumeRenderingQualityProps = { + viewportId: string; + commandsManager: CommandsManager; + serviceManager: ServicesManager; + volumeRenderingQualityRange: VolumeRenderingQualityRange; +}; + +export type VolumeShiftProps = { + viewportId: string; + commandsManager: CommandsManager; + serviceManager: ServicesManager; +}; + +export type VolumeShadeProps = { + viewportId: string; + commandsManager: CommandsManager; + serviceManager: ServicesManager; +}; + +export type VolumeLightingProps = { + viewportId: string; + commandsManager: CommandsManager; + serviceManager: ServicesManager; +}; diff --git a/extensions/cornerstone/src/types/WindowLevel.ts b/extensions/cornerstone/src/types/WindowLevel.ts new file mode 100644 index 000000000..556d0cad2 --- /dev/null +++ b/extensions/cornerstone/src/types/WindowLevel.ts @@ -0,0 +1,5 @@ +export type WindowLevelPreset = { + description: string; + window: string; + level: string; +}; diff --git a/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx b/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx index 4de163c79..f4eaa13e8 100644 --- a/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx +++ b/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx @@ -1,11 +1,11 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import html2canvas from 'html2canvas'; import { Enums, getEnabledElement, getOrCreateCanvas, StackViewport, - VolumeViewport, + BaseVolumeViewport, } from '@cornerstonejs/core'; import { ToolGroupManager } from '@cornerstonejs/tools'; import PropTypes from 'prop-types'; @@ -27,7 +27,11 @@ const CornerstoneViewportDownloadForm = ({ const activeViewportElement = enabledElement?.element; const activeViewportEnabledElement = getEnabledElement(activeViewportElement); - const { viewportId: activeViewportId, renderingEngineId } = activeViewportEnabledElement; + const { + viewportId: activeViewportId, + renderingEngineId, + viewport: activeViewport, + } = activeViewportEnabledElement; const toolGroup = ToolGroupManager.getToolGroupForViewport(activeViewportId, renderingEngineId); @@ -93,6 +97,7 @@ const CornerstoneViewportDownloadForm = ({ renderingEngine.resize(); // Trigger the render on the viewport to update the on screen + // downloadViewport.resetCamera(); downloadViewport.render(); downloadViewportElement.addEventListener( @@ -119,6 +124,12 @@ const CornerstoneViewportDownloadForm = ({ resolve({ dataUrl, width: newWidth, height: newHeight }); downloadViewportElement.removeEventListener(Enums.Events.IMAGE_RENDERED, updateViewport); + + // for some reason we need a reset camera here, and I don't know why + downloadViewport.resetCamera(); + const presentation = activeViewport.getViewPresentation(); + downloadViewport.setView(activeViewport.getViewReference(), presentation); + downloadViewport.render(); } ); }); @@ -153,14 +164,13 @@ const CornerstoneViewportDownloadForm = ({ console.warn('Unable to set properties', e); } }); - } else if (downloadViewport instanceof VolumeViewport) { + } else if (downloadViewport instanceof BaseVolumeViewport) { const actors = viewport.getActors(); // downloadViewport.setActors(actors); actors.forEach(actor => { downloadViewport.addActor(actor); }); - downloadViewport.setCamera(viewport.getCamera()); downloadViewport.render(); const newWidth = Math.min(width || image.width, MAX_TEXTURE_SIZE); @@ -188,7 +198,7 @@ const CornerstoneViewportDownloadForm = ({ // add the viewport to the toolGroup toolGroup.addViewport(downloadViewportId, renderingEngineId); - Object.keys(toolGroup._toolInstances).forEach(toolName => { + Object.keys(toolGroup.getToolInstances()).forEach(toolName => { // make all tools Enabled so that they can not be interacted with // in the download viewport if (toggle && toolName !== 'Crosshairs') { @@ -223,7 +233,6 @@ const CornerstoneViewportDownloadForm = ({ minimumSize={MINIMUM_SIZE} maximumSize={MAX_TEXTURE_SIZE} defaultSize={DEFAULT_SIZE} - canvasClass={'cornerstone-canvas'} activeViewportElement={activeViewportElement} enableViewport={enableViewport} disableViewport={disableViewport} diff --git a/extensions/cornerstone/src/utils/callInputDialog.tsx b/extensions/cornerstone/src/utils/callInputDialog.tsx index 37d5021c5..05946b87f 100644 --- a/extensions/cornerstone/src/utils/callInputDialog.tsx +++ b/extensions/cornerstone/src/utils/callInputDialog.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Input, Dialog, ButtonEnums } from '@ohif/ui'; +import { Input, Dialog, ButtonEnums, LabellingFlow } from '@ohif/ui'; /** * @@ -13,6 +13,7 @@ import { Input, Dialog, ButtonEnums } from '@ohif/ui'; * @param {string?} dialogConfig.dialogTitle - title of the input dialog * @param {string?} dialogConfig.inputLabel - show label above the input */ + function callInputDialog( uiDialogService, data, @@ -88,4 +89,70 @@ function callInputDialog( } } +export function callLabelAutocompleteDialog(uiDialogService, callback, dialogConfig, labelConfig) { + const exclusive = labelConfig ? labelConfig.exclusive : false; + const dropDownItems = labelConfig ? labelConfig.items : []; + + const { validateFunc = value => true } = dialogConfig; + + const labellingDoneCallback = value => { + if (typeof value === 'string') { + if (typeof validateFunc === 'function' && !validateFunc(value)) { + return; + } + callback(value, 'save'); + } else { + callback('', 'cancel'); + } + uiDialogService.dismiss({ id: 'select-annotation' }); + }; + + uiDialogService.create({ + id: 'select-annotation', + centralize: true, + isDraggable: false, + showOverlay: true, + content: LabellingFlow, + contentProps: { + labellingDoneCallback: labellingDoneCallback, + measurementData: { label: '' }, + componentClassName: {}, + labelData: dropDownItems, + exclusive: exclusive, + }, + }); +} + +export function showLabelAnnotationPopup(measurement, uiDialogService, labelConfig) { + const exclusive = labelConfig ? labelConfig.exclusive : false; + const dropDownItems = labelConfig ? labelConfig.items : []; + return new Promise>((resolve, reject) => { + const labellingDoneCallback = value => { + uiDialogService.dismiss({ id: 'select-annotation' }); + if (typeof value === 'string') { + measurement.label = value; + } + resolve(measurement); + }; + + uiDialogService.create({ + id: 'select-annotation', + isDraggable: false, + showOverlay: true, + content: LabellingFlow, + defaultPosition: { + x: window.innerWidth / 2, + y: window.innerHeight / 2, + }, + contentProps: { + labellingDoneCallback: labellingDoneCallback, + measurementData: measurement, + componentClassName: {}, + labelData: dropDownItems, + exclusive: exclusive, + }, + }); + }); +} + export default callInputDialog; diff --git a/extensions/cornerstone/src/utils/colormaps.js b/extensions/cornerstone/src/utils/colormaps.js new file mode 100644 index 000000000..a21df96ac --- /dev/null +++ b/extensions/cornerstone/src/utils/colormaps.js @@ -0,0 +1,1600 @@ +const colormaps = [ + { + ColorSpace: 'RGB', + Name: 'Grayscale', + name: 'Grayscale', + NanColor: [1, 0, 0], + RGBPoints: [0, 0, 0, 0, 1, 1, 1, 1], + description: 'Grayscale', + }, + { + ColorSpace: 'RGB', + Name: 'X Ray', + name: 'X Ray', + NanColor: [1, 0, 0], + RGBPoints: [0, 1, 1, 1, 1, 0, 0, 0], + description: 'X Ray', + }, + { + ColorSpace: 'RGB', + Name: 'hsv', + name: 'hsv', + RGBPoints: [ + -1, 1, 0, 0, -0.666666, 1, 0, 1, -0.333333, 0, 0, 1, 0, 0, 1, 1, 0.33333, 0, 1, 0, 0.66666, 1, + 1, 0, 1, 1, 0, 0, + ], + description: 'HSV', + }, + { + ColorSpace: 'RGB', + Name: 'hot_iron', + name: 'hot_iron', + RGBPoints: [ + 0.0, 0.0039215686, 0.0039215686, 0.0156862745, 0.00392156862745098, 0.0039215686, + 0.0039215686, 0.0156862745, 0.00784313725490196, 0.0039215686, 0.0039215686, 0.031372549, + 0.011764705882352941, 0.0039215686, 0.0039215686, 0.0470588235, 0.01568627450980392, + 0.0039215686, 0.0039215686, 0.062745098, 0.0196078431372549, 0.0039215686, 0.0039215686, + 0.0784313725, 0.023529411764705882, 0.0039215686, 0.0039215686, 0.0941176471, + 0.027450980392156862, 0.0039215686, 0.0039215686, 0.1098039216, 0.03137254901960784, + 0.0039215686, 0.0039215686, 0.1254901961, 0.03529411764705882, 0.0039215686, 0.0039215686, + 0.1411764706, 0.0392156862745098, 0.0039215686, 0.0039215686, 0.1568627451, + 0.043137254901960784, 0.0039215686, 0.0039215686, 0.1725490196, 0.047058823529411764, + 0.0039215686, 0.0039215686, 0.1882352941, 0.050980392156862744, 0.0039215686, 0.0039215686, + 0.2039215686, 0.054901960784313725, 0.0039215686, 0.0039215686, 0.2196078431, + 0.05882352941176471, 0.0039215686, 0.0039215686, 0.2352941176, 0.06274509803921569, + 0.0039215686, 0.0039215686, 0.2509803922, 0.06666666666666667, 0.0039215686, 0.0039215686, + 0.262745098, 0.07058823529411765, 0.0039215686, 0.0039215686, 0.2784313725, + 0.07450980392156863, 0.0039215686, 0.0039215686, 0.2941176471, 0.0784313725490196, + 0.0039215686, 0.0039215686, 0.3098039216, 0.08235294117647059, 0.0039215686, 0.0039215686, + 0.3254901961, 0.08627450980392157, 0.0039215686, 0.0039215686, 0.3411764706, + 0.09019607843137255, 0.0039215686, 0.0039215686, 0.3568627451, 0.09411764705882353, + 0.0039215686, 0.0039215686, 0.3725490196, 0.09803921568627451, 0.0039215686, 0.0039215686, + 0.3882352941, 0.10196078431372549, 0.0039215686, 0.0039215686, 0.4039215686, + 0.10588235294117647, 0.0039215686, 0.0039215686, 0.4196078431, 0.10980392156862745, + 0.0039215686, 0.0039215686, 0.4352941176, 0.11372549019607843, 0.0039215686, 0.0039215686, + 0.4509803922, 0.11764705882352942, 0.0039215686, 0.0039215686, 0.4666666667, + 0.12156862745098039, 0.0039215686, 0.0039215686, 0.4823529412, 0.12549019607843137, + 0.0039215686, 0.0039215686, 0.4980392157, 0.12941176470588237, 0.0039215686, 0.0039215686, + 0.5137254902, 0.13333333333333333, 0.0039215686, 0.0039215686, 0.5294117647, + 0.13725490196078433, 0.0039215686, 0.0039215686, 0.5450980392, 0.1411764705882353, + 0.0039215686, 0.0039215686, 0.5607843137, 0.1450980392156863, 0.0039215686, 0.0039215686, + 0.5764705882, 0.14901960784313725, 0.0039215686, 0.0039215686, 0.5921568627, + 0.15294117647058825, 0.0039215686, 0.0039215686, 0.6078431373, 0.1568627450980392, + 0.0039215686, 0.0039215686, 0.6235294118, 0.1607843137254902, 0.0039215686, 0.0039215686, + 0.6392156863, 0.16470588235294117, 0.0039215686, 0.0039215686, 0.6549019608, + 0.16862745098039217, 0.0039215686, 0.0039215686, 0.6705882353, 0.17254901960784313, + 0.0039215686, 0.0039215686, 0.6862745098, 0.17647058823529413, 0.0039215686, 0.0039215686, + 0.7019607843, 0.1803921568627451, 0.0039215686, 0.0039215686, 0.7176470588, + 0.1843137254901961, 0.0039215686, 0.0039215686, 0.7333333333, 0.18823529411764706, + 0.0039215686, 0.0039215686, 0.7490196078, 0.19215686274509805, 0.0039215686, 0.0039215686, + 0.7607843137, 0.19607843137254902, 0.0039215686, 0.0039215686, 0.7764705882, 0.2, + 0.0039215686, 0.0039215686, 0.7921568627, 0.20392156862745098, 0.0039215686, 0.0039215686, + 0.8078431373, 0.20784313725490197, 0.0039215686, 0.0039215686, 0.8235294118, + 0.21176470588235294, 0.0039215686, 0.0039215686, 0.8392156863, 0.21568627450980393, + 0.0039215686, 0.0039215686, 0.8549019608, 0.2196078431372549, 0.0039215686, 0.0039215686, + 0.8705882353, 0.2235294117647059, 0.0039215686, 0.0039215686, 0.8862745098, + 0.22745098039215686, 0.0039215686, 0.0039215686, 0.9019607843, 0.23137254901960785, + 0.0039215686, 0.0039215686, 0.9176470588, 0.23529411764705885, 0.0039215686, 0.0039215686, + 0.9333333333, 0.23921568627450984, 0.0039215686, 0.0039215686, 0.9490196078, + 0.24313725490196078, 0.0039215686, 0.0039215686, 0.9647058824, 0.24705882352941178, + 0.0039215686, 0.0039215686, 0.9803921569, 0.25098039215686274, 0.0039215686, 0.0039215686, + 0.9960784314, 0.2549019607843137, 0.0039215686, 0.0039215686, 0.9960784314, + 0.25882352941176473, 0.0156862745, 0.0039215686, 0.9803921569, 0.2627450980392157, + 0.031372549, 0.0039215686, 0.9647058824, 0.26666666666666666, 0.0470588235, 0.0039215686, + 0.9490196078, 0.27058823529411763, 0.062745098, 0.0039215686, 0.9333333333, + 0.27450980392156865, 0.0784313725, 0.0039215686, 0.9176470588, 0.2784313725490196, + 0.0941176471, 0.0039215686, 0.9019607843, 0.2823529411764706, 0.1098039216, 0.0039215686, + 0.8862745098, 0.28627450980392155, 0.1254901961, 0.0039215686, 0.8705882353, + 0.2901960784313726, 0.1411764706, 0.0039215686, 0.8549019608, 0.29411764705882354, + 0.1568627451, 0.0039215686, 0.8392156863, 0.2980392156862745, 0.1725490196, 0.0039215686, + 0.8235294118, 0.30196078431372547, 0.1882352941, 0.0039215686, 0.8078431373, + 0.3058823529411765, 0.2039215686, 0.0039215686, 0.7921568627, 0.30980392156862746, + 0.2196078431, 0.0039215686, 0.7764705882, 0.3137254901960784, 0.2352941176, 0.0039215686, + 0.7607843137, 0.3176470588235294, 0.2509803922, 0.0039215686, 0.7490196078, + 0.3215686274509804, 0.262745098, 0.0039215686, 0.7333333333, 0.3254901960784314, 0.2784313725, + 0.0039215686, 0.7176470588, 0.32941176470588235, 0.2941176471, 0.0039215686, 0.7019607843, + 0.3333333333333333, 0.3098039216, 0.0039215686, 0.6862745098, 0.33725490196078434, + 0.3254901961, 0.0039215686, 0.6705882353, 0.3411764705882353, 0.3411764706, 0.0039215686, + 0.6549019608, 0.34509803921568627, 0.3568627451, 0.0039215686, 0.6392156863, + 0.34901960784313724, 0.3725490196, 0.0039215686, 0.6235294118, 0.35294117647058826, + 0.3882352941, 0.0039215686, 0.6078431373, 0.3568627450980392, 0.4039215686, 0.0039215686, + 0.5921568627, 0.3607843137254902, 0.4196078431, 0.0039215686, 0.5764705882, + 0.36470588235294116, 0.4352941176, 0.0039215686, 0.5607843137, 0.3686274509803922, + 0.4509803922, 0.0039215686, 0.5450980392, 0.37254901960784315, 0.4666666667, 0.0039215686, + 0.5294117647, 0.3764705882352941, 0.4823529412, 0.0039215686, 0.5137254902, + 0.3803921568627451, 0.4980392157, 0.0039215686, 0.4980392157, 0.3843137254901961, + 0.5137254902, 0.0039215686, 0.4823529412, 0.38823529411764707, 0.5294117647, 0.0039215686, + 0.4666666667, 0.39215686274509803, 0.5450980392, 0.0039215686, 0.4509803922, + 0.396078431372549, 0.5607843137, 0.0039215686, 0.4352941176, 0.4, 0.5764705882, 0.0039215686, + 0.4196078431, 0.403921568627451, 0.5921568627, 0.0039215686, 0.4039215686, + 0.40784313725490196, 0.6078431373, 0.0039215686, 0.3882352941, 0.4117647058823529, + 0.6235294118, 0.0039215686, 0.3725490196, 0.41568627450980394, 0.6392156863, 0.0039215686, + 0.3568627451, 0.4196078431372549, 0.6549019608, 0.0039215686, 0.3411764706, + 0.4235294117647059, 0.6705882353, 0.0039215686, 0.3254901961, 0.42745098039215684, + 0.6862745098, 0.0039215686, 0.3098039216, 0.43137254901960786, 0.7019607843, 0.0039215686, + 0.2941176471, 0.43529411764705883, 0.7176470588, 0.0039215686, 0.2784313725, + 0.4392156862745098, 0.7333333333, 0.0039215686, 0.262745098, 0.44313725490196076, + 0.7490196078, 0.0039215686, 0.2509803922, 0.4470588235294118, 0.7607843137, 0.0039215686, + 0.2352941176, 0.45098039215686275, 0.7764705882, 0.0039215686, 0.2196078431, + 0.4549019607843137, 0.7921568627, 0.0039215686, 0.2039215686, 0.4588235294117647, + 0.8078431373, 0.0039215686, 0.1882352941, 0.4627450980392157, 0.8235294118, 0.0039215686, + 0.1725490196, 0.4666666666666667, 0.8392156863, 0.0039215686, 0.1568627451, + 0.4705882352941177, 0.8549019608, 0.0039215686, 0.1411764706, 0.4745098039215686, + 0.8705882353, 0.0039215686, 0.1254901961, 0.4784313725490197, 0.8862745098, 0.0039215686, + 0.1098039216, 0.48235294117647065, 0.9019607843, 0.0039215686, 0.0941176471, + 0.48627450980392156, 0.9176470588, 0.0039215686, 0.0784313725, 0.49019607843137253, + 0.9333333333, 0.0039215686, 0.062745098, 0.49411764705882355, 0.9490196078, 0.0039215686, + 0.0470588235, 0.4980392156862745, 0.9647058824, 0.0039215686, 0.031372549, 0.5019607843137255, + 0.9803921569, 0.0039215686, 0.0156862745, 0.5058823529411764, 0.9960784314, 0.0039215686, + 0.0039215686, 0.5098039215686274, 0.9960784314, 0.0156862745, 0.0039215686, + 0.5137254901960784, 0.9960784314, 0.031372549, 0.0039215686, 0.5176470588235295, 0.9960784314, + 0.0470588235, 0.0039215686, 0.5215686274509804, 0.9960784314, 0.062745098, 0.0039215686, + 0.5254901960784314, 0.9960784314, 0.0784313725, 0.0039215686, 0.5294117647058824, + 0.9960784314, 0.0941176471, 0.0039215686, 0.5333333333333333, 0.9960784314, 0.1098039216, + 0.0039215686, 0.5372549019607843, 0.9960784314, 0.1254901961, 0.0039215686, + 0.5411764705882353, 0.9960784314, 0.1411764706, 0.0039215686, 0.5450980392156862, + 0.9960784314, 0.1568627451, 0.0039215686, 0.5490196078431373, 0.9960784314, 0.1725490196, + 0.0039215686, 0.5529411764705883, 0.9960784314, 0.1882352941, 0.0039215686, + 0.5568627450980392, 0.9960784314, 0.2039215686, 0.0039215686, 0.5607843137254902, + 0.9960784314, 0.2196078431, 0.0039215686, 0.5647058823529412, 0.9960784314, 0.2352941176, + 0.0039215686, 0.5686274509803921, 0.9960784314, 0.2509803922, 0.0039215686, + 0.5725490196078431, 0.9960784314, 0.262745098, 0.0039215686, 0.5764705882352941, 0.9960784314, + 0.2784313725, 0.0039215686, 0.5803921568627451, 0.9960784314, 0.2941176471, 0.0039215686, + 0.5843137254901961, 0.9960784314, 0.3098039216, 0.0039215686, 0.5882352941176471, + 0.9960784314, 0.3254901961, 0.0039215686, 0.592156862745098, 0.9960784314, 0.3411764706, + 0.0039215686, 0.596078431372549, 0.9960784314, 0.3568627451, 0.0039215686, 0.6, 0.9960784314, + 0.3725490196, 0.0039215686, 0.6039215686274509, 0.9960784314, 0.3882352941, 0.0039215686, + 0.6078431372549019, 0.9960784314, 0.4039215686, 0.0039215686, 0.611764705882353, 0.9960784314, + 0.4196078431, 0.0039215686, 0.615686274509804, 0.9960784314, 0.4352941176, 0.0039215686, + 0.6196078431372549, 0.9960784314, 0.4509803922, 0.0039215686, 0.6235294117647059, + 0.9960784314, 0.4666666667, 0.0039215686, 0.6274509803921569, 0.9960784314, 0.4823529412, + 0.0039215686, 0.6313725490196078, 0.9960784314, 0.4980392157, 0.0039215686, + 0.6352941176470588, 0.9960784314, 0.5137254902, 0.0039215686, 0.6392156862745098, + 0.9960784314, 0.5294117647, 0.0039215686, 0.6431372549019608, 0.9960784314, 0.5450980392, + 0.0039215686, 0.6470588235294118, 0.9960784314, 0.5607843137, 0.0039215686, + 0.6509803921568628, 0.9960784314, 0.5764705882, 0.0039215686, 0.6549019607843137, + 0.9960784314, 0.5921568627, 0.0039215686, 0.6588235294117647, 0.9960784314, 0.6078431373, + 0.0039215686, 0.6627450980392157, 0.9960784314, 0.6235294118, 0.0039215686, + 0.6666666666666666, 0.9960784314, 0.6392156863, 0.0039215686, 0.6705882352941176, + 0.9960784314, 0.6549019608, 0.0039215686, 0.6745098039215687, 0.9960784314, 0.6705882353, + 0.0039215686, 0.6784313725490196, 0.9960784314, 0.6862745098, 0.0039215686, + 0.6823529411764706, 0.9960784314, 0.7019607843, 0.0039215686, 0.6862745098039216, + 0.9960784314, 0.7176470588, 0.0039215686, 0.6901960784313725, 0.9960784314, 0.7333333333, + 0.0039215686, 0.6941176470588235, 0.9960784314, 0.7490196078, 0.0039215686, + 0.6980392156862745, 0.9960784314, 0.7607843137, 0.0039215686, 0.7019607843137254, + 0.9960784314, 0.7764705882, 0.0039215686, 0.7058823529411765, 0.9960784314, 0.7921568627, + 0.0039215686, 0.7098039215686275, 0.9960784314, 0.8078431373, 0.0039215686, + 0.7137254901960784, 0.9960784314, 0.8235294118, 0.0039215686, 0.7176470588235294, + 0.9960784314, 0.8392156863, 0.0039215686, 0.7215686274509804, 0.9960784314, 0.8549019608, + 0.0039215686, 0.7254901960784313, 0.9960784314, 0.8705882353, 0.0039215686, + 0.7294117647058823, 0.9960784314, 0.8862745098, 0.0039215686, 0.7333333333333333, + 0.9960784314, 0.9019607843, 0.0039215686, 0.7372549019607844, 0.9960784314, 0.9176470588, + 0.0039215686, 0.7411764705882353, 0.9960784314, 0.9333333333, 0.0039215686, + 0.7450980392156863, 0.9960784314, 0.9490196078, 0.0039215686, 0.7490196078431373, + 0.9960784314, 0.9647058824, 0.0039215686, 0.7529411764705882, 0.9960784314, 0.9803921569, + 0.0039215686, 0.7568627450980392, 0.9960784314, 0.9960784314, 0.0039215686, + 0.7607843137254902, 0.9960784314, 0.9960784314, 0.0196078431, 0.7647058823529411, + 0.9960784314, 0.9960784314, 0.0352941176, 0.7686274509803922, 0.9960784314, 0.9960784314, + 0.0509803922, 0.7725490196078432, 0.9960784314, 0.9960784314, 0.0666666667, + 0.7764705882352941, 0.9960784314, 0.9960784314, 0.0823529412, 0.7803921568627451, + 0.9960784314, 0.9960784314, 0.0980392157, 0.7843137254901961, 0.9960784314, 0.9960784314, + 0.1137254902, 0.788235294117647, 0.9960784314, 0.9960784314, 0.1294117647, 0.792156862745098, + 0.9960784314, 0.9960784314, 0.1450980392, 0.796078431372549, 0.9960784314, 0.9960784314, + 0.1607843137, 0.8, 0.9960784314, 0.9960784314, 0.1764705882, 0.803921568627451, 0.9960784314, + 0.9960784314, 0.1921568627, 0.807843137254902, 0.9960784314, 0.9960784314, 0.2078431373, + 0.8117647058823529, 0.9960784314, 0.9960784314, 0.2235294118, 0.8156862745098039, + 0.9960784314, 0.9960784314, 0.2392156863, 0.8196078431372549, 0.9960784314, 0.9960784314, + 0.2509803922, 0.8235294117647058, 0.9960784314, 0.9960784314, 0.2666666667, + 0.8274509803921568, 0.9960784314, 0.9960784314, 0.2823529412, 0.8313725490196079, + 0.9960784314, 0.9960784314, 0.2980392157, 0.8352941176470589, 0.9960784314, 0.9960784314, + 0.3137254902, 0.8392156862745098, 0.9960784314, 0.9960784314, 0.3333333333, + 0.8431372549019608, 0.9960784314, 0.9960784314, 0.3490196078, 0.8470588235294118, + 0.9960784314, 0.9960784314, 0.3647058824, 0.8509803921568627, 0.9960784314, 0.9960784314, + 0.3803921569, 0.8549019607843137, 0.9960784314, 0.9960784314, 0.3960784314, + 0.8588235294117647, 0.9960784314, 0.9960784314, 0.4117647059, 0.8627450980392157, + 0.9960784314, 0.9960784314, 0.4274509804, 0.8666666666666667, 0.9960784314, 0.9960784314, + 0.4431372549, 0.8705882352941177, 0.9960784314, 0.9960784314, 0.4588235294, + 0.8745098039215686, 0.9960784314, 0.9960784314, 0.4745098039, 0.8784313725490196, + 0.9960784314, 0.9960784314, 0.4901960784, 0.8823529411764706, 0.9960784314, 0.9960784314, + 0.5058823529, 0.8862745098039215, 0.9960784314, 0.9960784314, 0.5215686275, + 0.8901960784313725, 0.9960784314, 0.9960784314, 0.537254902, 0.8941176470588236, 0.9960784314, + 0.9960784314, 0.5529411765, 0.8980392156862745, 0.9960784314, 0.9960784314, 0.568627451, + 0.9019607843137255, 0.9960784314, 0.9960784314, 0.5843137255, 0.9058823529411765, + 0.9960784314, 0.9960784314, 0.6, 0.9098039215686274, 0.9960784314, 0.9960784314, 0.6156862745, + 0.9137254901960784, 0.9960784314, 0.9960784314, 0.631372549, 0.9176470588235294, 0.9960784314, + 0.9960784314, 0.6470588235, 0.9215686274509803, 0.9960784314, 0.9960784314, 0.6666666667, + 0.9254901960784314, 0.9960784314, 0.9960784314, 0.6823529412, 0.9294117647058824, + 0.9960784314, 0.9960784314, 0.6980392157, 0.9333333333333333, 0.9960784314, 0.9960784314, + 0.7137254902, 0.9372549019607843, 0.9960784314, 0.9960784314, 0.7294117647, + 0.9411764705882354, 0.9960784314, 0.9960784314, 0.7450980392, 0.9450980392156864, + 0.9960784314, 0.9960784314, 0.7568627451, 0.9490196078431372, 0.9960784314, 0.9960784314, + 0.7725490196, 0.9529411764705882, 0.9960784314, 0.9960784314, 0.7882352941, + 0.9568627450980394, 0.9960784314, 0.9960784314, 0.8039215686, 0.9607843137254903, + 0.9960784314, 0.9960784314, 0.8196078431, 0.9647058823529413, 0.9960784314, 0.9960784314, + 0.8352941176, 0.9686274509803922, 0.9960784314, 0.9960784314, 0.8509803922, + 0.9725490196078431, 0.9960784314, 0.9960784314, 0.8666666667, 0.9764705882352941, + 0.9960784314, 0.9960784314, 0.8823529412, 0.9803921568627451, 0.9960784314, 0.9960784314, + 0.8980392157, 0.984313725490196, 0.9960784314, 0.9960784314, 0.9137254902, 0.9882352941176471, + 0.9960784314, 0.9960784314, 0.9294117647, 0.9921568627450981, 0.9960784314, 0.9960784314, + 0.9450980392, 0.996078431372549, 0.9960784314, 0.9960784314, 0.9607843137, 1.0, 0.9960784314, + 0.9960784314, 0.9607843137, + ], + description: 'Hot Iron', + }, + { + ColorSpace: 'RGB', + Name: 'red_hot', + name: 'red_hot', + RGBPoints: [ + 0.0, 0.0, 0.0, 0.0, 0.00392156862745098, 0.0, 0.0, 0.0, 0.00784313725490196, 0.0, 0.0, 0.0, + 0.011764705882352941, 0.0, 0.0, 0.0, 0.01568627450980392, 0.0039215686, 0.0039215686, + 0.0039215686, 0.0196078431372549, 0.0039215686, 0.0039215686, 0.0039215686, + 0.023529411764705882, 0.0039215686, 0.0039215686, 0.0039215686, 0.027450980392156862, + 0.0039215686, 0.0039215686, 0.0039215686, 0.03137254901960784, 0.0039215686, 0.0039215686, + 0.0039215686, 0.03529411764705882, 0.0156862745, 0.0, 0.0, 0.0392156862745098, 0.0274509804, + 0.0, 0.0, 0.043137254901960784, 0.0392156863, 0.0, 0.0, 0.047058823529411764, 0.0509803922, + 0.0, 0.0, 0.050980392156862744, 0.062745098, 0.0, 0.0, 0.054901960784313725, 0.0784313725, + 0.0, 0.0, 0.05882352941176471, 0.0901960784, 0.0, 0.0, 0.06274509803921569, 0.1058823529, 0.0, + 0.0, 0.06666666666666667, 0.1176470588, 0.0, 0.0, 0.07058823529411765, 0.1294117647, 0.0, 0.0, + 0.07450980392156863, 0.1411764706, 0.0, 0.0, 0.0784313725490196, 0.1529411765, 0.0, 0.0, + 0.08235294117647059, 0.1647058824, 0.0, 0.0, 0.08627450980392157, 0.1764705882, 0.0, 0.0, + 0.09019607843137255, 0.1882352941, 0.0, 0.0, 0.09411764705882353, 0.2039215686, 0.0, 0.0, + 0.09803921568627451, 0.2156862745, 0.0, 0.0, 0.10196078431372549, 0.2274509804, 0.0, 0.0, + 0.10588235294117647, 0.2392156863, 0.0, 0.0, 0.10980392156862745, 0.2549019608, 0.0, 0.0, + 0.11372549019607843, 0.2666666667, 0.0, 0.0, 0.11764705882352942, 0.2784313725, 0.0, 0.0, + 0.12156862745098039, 0.2901960784, 0.0, 0.0, 0.12549019607843137, 0.3058823529, 0.0, 0.0, + 0.12941176470588237, 0.3176470588, 0.0, 0.0, 0.13333333333333333, 0.3294117647, 0.0, 0.0, + 0.13725490196078433, 0.3411764706, 0.0, 0.0, 0.1411764705882353, 0.3529411765, 0.0, 0.0, + 0.1450980392156863, 0.3647058824, 0.0, 0.0, 0.14901960784313725, 0.3764705882, 0.0, 0.0, + 0.15294117647058825, 0.3882352941, 0.0, 0.0, 0.1568627450980392, 0.4039215686, 0.0, 0.0, + 0.1607843137254902, 0.4156862745, 0.0, 0.0, 0.16470588235294117, 0.431372549, 0.0, 0.0, + 0.16862745098039217, 0.4431372549, 0.0, 0.0, 0.17254901960784313, 0.4588235294, 0.0, 0.0, + 0.17647058823529413, 0.4705882353, 0.0, 0.0, 0.1803921568627451, 0.4823529412, 0.0, 0.0, + 0.1843137254901961, 0.4941176471, 0.0, 0.0, 0.18823529411764706, 0.5098039216, 0.0, 0.0, + 0.19215686274509805, 0.5215686275, 0.0, 0.0, 0.19607843137254902, 0.5333333333, 0.0, 0.0, 0.2, + 0.5450980392, 0.0, 0.0, 0.20392156862745098, 0.5568627451, 0.0, 0.0, 0.20784313725490197, + 0.568627451, 0.0, 0.0, 0.21176470588235294, 0.5803921569, 0.0, 0.0, 0.21568627450980393, + 0.5921568627, 0.0, 0.0, 0.2196078431372549, 0.6078431373, 0.0, 0.0, 0.2235294117647059, + 0.6196078431, 0.0, 0.0, 0.22745098039215686, 0.631372549, 0.0, 0.0, 0.23137254901960785, + 0.6431372549, 0.0, 0.0, 0.23529411764705885, 0.6588235294, 0.0, 0.0, 0.23921568627450984, + 0.6705882353, 0.0, 0.0, 0.24313725490196078, 0.6823529412, 0.0, 0.0, 0.24705882352941178, + 0.6941176471, 0.0, 0.0, 0.25098039215686274, 0.7098039216, 0.0, 0.0, 0.2549019607843137, + 0.7215686275, 0.0, 0.0, 0.25882352941176473, 0.7333333333, 0.0, 0.0, 0.2627450980392157, + 0.7450980392, 0.0, 0.0, 0.26666666666666666, 0.7568627451, 0.0, 0.0, 0.27058823529411763, + 0.768627451, 0.0, 0.0, 0.27450980392156865, 0.7843137255, 0.0, 0.0, 0.2784313725490196, + 0.7960784314, 0.0, 0.0, 0.2823529411764706, 0.8117647059, 0.0, 0.0, 0.28627450980392155, + 0.8235294118, 0.0, 0.0, 0.2901960784313726, 0.8352941176, 0.0, 0.0, 0.29411764705882354, + 0.8470588235, 0.0, 0.0, 0.2980392156862745, 0.862745098, 0.0, 0.0, 0.30196078431372547, + 0.8745098039, 0.0, 0.0, 0.3058823529411765, 0.8862745098, 0.0, 0.0, 0.30980392156862746, + 0.8980392157, 0.0, 0.0, 0.3137254901960784, 0.9137254902, 0.0, 0.0, 0.3176470588235294, + 0.9254901961, 0.0, 0.0, 0.3215686274509804, 0.937254902, 0.0, 0.0, 0.3254901960784314, + 0.9490196078, 0.0, 0.0, 0.32941176470588235, 0.9607843137, 0.0, 0.0, 0.3333333333333333, + 0.968627451, 0.0, 0.0, 0.33725490196078434, 0.9803921569, 0.0039215686, 0.0, + 0.3411764705882353, 0.9882352941, 0.0078431373, 0.0, 0.34509803921568627, 1.0, 0.0117647059, + 0.0, 0.34901960784313724, 1.0, 0.0235294118, 0.0, 0.35294117647058826, 1.0, 0.0352941176, 0.0, + 0.3568627450980392, 1.0, 0.0470588235, 0.0, 0.3607843137254902, 1.0, 0.062745098, 0.0, + 0.36470588235294116, 1.0, 0.0745098039, 0.0, 0.3686274509803922, 1.0, 0.0862745098, 0.0, + 0.37254901960784315, 1.0, 0.0980392157, 0.0, 0.3764705882352941, 1.0, 0.1137254902, 0.0, + 0.3803921568627451, 1.0, 0.1254901961, 0.0, 0.3843137254901961, 1.0, 0.137254902, 0.0, + 0.38823529411764707, 1.0, 0.1490196078, 0.0, 0.39215686274509803, 1.0, 0.1647058824, 0.0, + 0.396078431372549, 1.0, 0.1764705882, 0.0, 0.4, 1.0, 0.1882352941, 0.0, 0.403921568627451, + 1.0, 0.2, 0.0, 0.40784313725490196, 1.0, 0.2156862745, 0.0, 0.4117647058823529, 1.0, + 0.2274509804, 0.0, 0.41568627450980394, 1.0, 0.2392156863, 0.0, 0.4196078431372549, 1.0, + 0.2509803922, 0.0, 0.4235294117647059, 1.0, 0.2666666667, 0.0, 0.42745098039215684, 1.0, + 0.2784313725, 0.0, 0.43137254901960786, 1.0, 0.2901960784, 0.0, 0.43529411764705883, 1.0, + 0.3019607843, 0.0, 0.4392156862745098, 1.0, 0.3176470588, 0.0, 0.44313725490196076, 1.0, + 0.3294117647, 0.0, 0.4470588235294118, 1.0, 0.3411764706, 0.0, 0.45098039215686275, 1.0, + 0.3529411765, 0.0, 0.4549019607843137, 1.0, 0.368627451, 0.0, 0.4588235294117647, 1.0, + 0.3803921569, 0.0, 0.4627450980392157, 1.0, 0.3921568627, 0.0, 0.4666666666666667, 1.0, + 0.4039215686, 0.0, 0.4705882352941177, 1.0, 0.4156862745, 0.0, 0.4745098039215686, 1.0, + 0.4274509804, 0.0, 0.4784313725490197, 1.0, 0.4392156863, 0.0, 0.48235294117647065, 1.0, + 0.4509803922, 0.0, 0.48627450980392156, 1.0, 0.4666666667, 0.0, 0.49019607843137253, 1.0, + 0.4784313725, 0.0, 0.49411764705882355, 1.0, 0.4941176471, 0.0, 0.4980392156862745, 1.0, + 0.5058823529, 0.0, 0.5019607843137255, 1.0, 0.5215686275, 0.0, 0.5058823529411764, 1.0, + 0.5333333333, 0.0, 0.5098039215686274, 1.0, 0.5450980392, 0.0, 0.5137254901960784, 1.0, + 0.5568627451, 0.0, 0.5176470588235295, 1.0, 0.568627451, 0.0, 0.5215686274509804, 1.0, + 0.5803921569, 0.0, 0.5254901960784314, 1.0, 0.5921568627, 0.0, 0.5294117647058824, 1.0, + 0.6039215686, 0.0, 0.5333333333333333, 1.0, 0.6196078431, 0.0, 0.5372549019607843, 1.0, + 0.631372549, 0.0, 0.5411764705882353, 1.0, 0.6431372549, 0.0, 0.5450980392156862, 1.0, + 0.6549019608, 0.0, 0.5490196078431373, 1.0, 0.6705882353, 0.0, 0.5529411764705883, 1.0, + 0.6823529412, 0.0, 0.5568627450980392, 1.0, 0.6941176471, 0.0, 0.5607843137254902, 1.0, + 0.7058823529, 0.0, 0.5647058823529412, 1.0, 0.7215686275, 0.0, 0.5686274509803921, 1.0, + 0.7333333333, 0.0, 0.5725490196078431, 1.0, 0.7450980392, 0.0, 0.5764705882352941, 1.0, + 0.7568627451, 0.0, 0.5803921568627451, 1.0, 0.7725490196, 0.0, 0.5843137254901961, 1.0, + 0.7843137255, 0.0, 0.5882352941176471, 1.0, 0.7960784314, 0.0, 0.592156862745098, 1.0, + 0.8078431373, 0.0, 0.596078431372549, 1.0, 0.8196078431, 0.0, 0.6, 1.0, 0.831372549, 0.0, + 0.6039215686274509, 1.0, 0.8470588235, 0.0, 0.6078431372549019, 1.0, 0.8588235294, 0.0, + 0.611764705882353, 1.0, 0.8745098039, 0.0, 0.615686274509804, 1.0, 0.8862745098, 0.0, + 0.6196078431372549, 1.0, 0.8980392157, 0.0, 0.6235294117647059, 1.0, 0.9098039216, 0.0, + 0.6274509803921569, 1.0, 0.9254901961, 0.0, 0.6313725490196078, 1.0, 0.937254902, 0.0, + 0.6352941176470588, 1.0, 0.9490196078, 0.0, 0.6392156862745098, 1.0, 0.9607843137, 0.0, + 0.6431372549019608, 1.0, 0.9764705882, 0.0, 0.6470588235294118, 1.0, 0.9803921569, + 0.0039215686, 0.6509803921568628, 1.0, 0.9882352941, 0.0117647059, 0.6549019607843137, 1.0, + 0.9921568627, 0.0156862745, 0.6588235294117647, 1.0, 1.0, 0.0235294118, 0.6627450980392157, + 1.0, 1.0, 0.0352941176, 0.6666666666666666, 1.0, 1.0, 0.0470588235, 0.6705882352941176, 1.0, + 1.0, 0.0588235294, 0.6745098039215687, 1.0, 1.0, 0.0745098039, 0.6784313725490196, 1.0, 1.0, + 0.0862745098, 0.6823529411764706, 1.0, 1.0, 0.0980392157, 0.6862745098039216, 1.0, 1.0, + 0.1098039216, 0.6901960784313725, 1.0, 1.0, 0.1254901961, 0.6941176470588235, 1.0, 1.0, + 0.137254902, 0.6980392156862745, 1.0, 1.0, 0.1490196078, 0.7019607843137254, 1.0, 1.0, + 0.1607843137, 0.7058823529411765, 1.0, 1.0, 0.1764705882, 0.7098039215686275, 1.0, 1.0, + 0.1882352941, 0.7137254901960784, 1.0, 1.0, 0.2, 0.7176470588235294, 1.0, 1.0, 0.2117647059, + 0.7215686274509804, 1.0, 1.0, 0.2274509804, 0.7254901960784313, 1.0, 1.0, 0.2392156863, + 0.7294117647058823, 1.0, 1.0, 0.2509803922, 0.7333333333333333, 1.0, 1.0, 0.262745098, + 0.7372549019607844, 1.0, 1.0, 0.2784313725, 0.7411764705882353, 1.0, 1.0, 0.2901960784, + 0.7450980392156863, 1.0, 1.0, 0.3019607843, 0.7490196078431373, 1.0, 1.0, 0.3137254902, + 0.7529411764705882, 1.0, 1.0, 0.3294117647, 0.7568627450980392, 1.0, 1.0, 0.3411764706, + 0.7607843137254902, 1.0, 1.0, 0.3529411765, 0.7647058823529411, 1.0, 1.0, 0.3647058824, + 0.7686274509803922, 1.0, 1.0, 0.3803921569, 0.7725490196078432, 1.0, 1.0, 0.3921568627, + 0.7764705882352941, 1.0, 1.0, 0.4039215686, 0.7803921568627451, 1.0, 1.0, 0.4156862745, + 0.7843137254901961, 1.0, 1.0, 0.431372549, 0.788235294117647, 1.0, 1.0, 0.4431372549, + 0.792156862745098, 1.0, 1.0, 0.4549019608, 0.796078431372549, 1.0, 1.0, 0.4666666667, 0.8, + 1.0, 1.0, 0.4784313725, 0.803921568627451, 1.0, 1.0, 0.4901960784, 0.807843137254902, 1.0, + 1.0, 0.5019607843, 0.8117647058823529, 1.0, 1.0, 0.5137254902, 0.8156862745098039, 1.0, 1.0, + 0.5294117647, 0.8196078431372549, 1.0, 1.0, 0.5411764706, 0.8235294117647058, 1.0, 1.0, + 0.5568627451, 0.8274509803921568, 1.0, 1.0, 0.568627451, 0.8313725490196079, 1.0, 1.0, + 0.5843137255, 0.8352941176470589, 1.0, 1.0, 0.5960784314, 0.8392156862745098, 1.0, 1.0, + 0.6078431373, 0.8431372549019608, 1.0, 1.0, 0.6196078431, 0.8470588235294118, 1.0, 1.0, + 0.631372549, 0.8509803921568627, 1.0, 1.0, 0.6431372549, 0.8549019607843137, 1.0, 1.0, + 0.6549019608, 0.8588235294117647, 1.0, 1.0, 0.6666666667, 0.8627450980392157, 1.0, 1.0, + 0.6823529412, 0.8666666666666667, 1.0, 1.0, 0.6941176471, 0.8705882352941177, 1.0, 1.0, + 0.7058823529, 0.8745098039215686, 1.0, 1.0, 0.7176470588, 0.8784313725490196, 1.0, 1.0, + 0.7333333333, 0.8823529411764706, 1.0, 1.0, 0.7450980392, 0.8862745098039215, 1.0, 1.0, + 0.7568627451, 0.8901960784313725, 1.0, 1.0, 0.768627451, 0.8941176470588236, 1.0, 1.0, + 0.7843137255, 0.8980392156862745, 1.0, 1.0, 0.7960784314, 0.9019607843137255, 1.0, 1.0, + 0.8078431373, 0.9058823529411765, 1.0, 1.0, 0.8196078431, 0.9098039215686274, 1.0, 1.0, + 0.8352941176, 0.9137254901960784, 1.0, 1.0, 0.8470588235, 0.9176470588235294, 1.0, 1.0, + 0.8588235294, 0.9215686274509803, 1.0, 1.0, 0.8705882353, 0.9254901960784314, 1.0, 1.0, + 0.8823529412, 0.9294117647058824, 1.0, 1.0, 0.8941176471, 0.9333333333333333, 1.0, 1.0, + 0.9098039216, 0.9372549019607843, 1.0, 1.0, 0.9215686275, 0.9411764705882354, 1.0, 1.0, + 0.937254902, 0.9450980392156864, 1.0, 1.0, 0.9490196078, 0.9490196078431372, 1.0, 1.0, + 0.9607843137, 0.9529411764705882, 1.0, 1.0, 0.9725490196, 0.9568627450980394, 1.0, 1.0, + 0.9882352941, 0.9607843137254903, 1.0, 1.0, 0.9882352941, 0.9647058823529413, 1.0, 1.0, + 0.9921568627, 0.9686274509803922, 1.0, 1.0, 0.9960784314, 0.9725490196078431, 1.0, 1.0, 1.0, + 0.9764705882352941, 1.0, 1.0, 1.0, 0.9803921568627451, 1.0, 1.0, 1.0, 0.984313725490196, 1.0, + 1.0, 1.0, 0.9882352941176471, 1.0, 1.0, 1.0, 0.9921568627450981, 1.0, 1.0, 1.0, + 0.996078431372549, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + ], + description: 'Red Hot', + }, + { + ColorSpace: 'RGB', + Name: 's_pet', + name: 's_pet', + RGBPoints: [ + 0.0, 0.0156862745, 0.0039215686, 0.0156862745, 0.00392156862745098, 0.0156862745, + 0.0039215686, 0.0156862745, 0.00784313725490196, 0.0274509804, 0.0039215686, 0.031372549, + 0.011764705882352941, 0.0352941176, 0.0039215686, 0.0509803922, 0.01568627450980392, + 0.0392156863, 0.0039215686, 0.0666666667, 0.0196078431372549, 0.0509803922, 0.0039215686, + 0.0823529412, 0.023529411764705882, 0.062745098, 0.0039215686, 0.0980392157, + 0.027450980392156862, 0.0705882353, 0.0039215686, 0.1176470588, 0.03137254901960784, + 0.0745098039, 0.0039215686, 0.1333333333, 0.03529411764705882, 0.0862745098, 0.0039215686, + 0.1490196078, 0.0392156862745098, 0.0980392157, 0.0039215686, 0.1647058824, + 0.043137254901960784, 0.1058823529, 0.0039215686, 0.1843137255, 0.047058823529411764, + 0.1098039216, 0.0039215686, 0.2, 0.050980392156862744, 0.1215686275, 0.0039215686, + 0.2156862745, 0.054901960784313725, 0.1333333333, 0.0039215686, 0.231372549, + 0.05882352941176471, 0.137254902, 0.0039215686, 0.2509803922, 0.06274509803921569, + 0.1490196078, 0.0039215686, 0.262745098, 0.06666666666666667, 0.1607843137, 0.0039215686, + 0.2784313725, 0.07058823529411765, 0.168627451, 0.0039215686, 0.2941176471, + 0.07450980392156863, 0.1725490196, 0.0039215686, 0.3137254902, 0.0784313725490196, + 0.1843137255, 0.0039215686, 0.3294117647, 0.08235294117647059, 0.1960784314, 0.0039215686, + 0.3450980392, 0.08627450980392157, 0.2039215686, 0.0039215686, 0.3607843137, + 0.09019607843137255, 0.2078431373, 0.0039215686, 0.3803921569, 0.09411764705882353, + 0.2196078431, 0.0039215686, 0.3960784314, 0.09803921568627451, 0.231372549, 0.0039215686, + 0.4117647059, 0.10196078431372549, 0.2392156863, 0.0039215686, 0.4274509804, + 0.10588235294117647, 0.2431372549, 0.0039215686, 0.4470588235, 0.10980392156862745, + 0.2509803922, 0.0039215686, 0.462745098, 0.11372549019607843, 0.262745098, 0.0039215686, + 0.4784313725, 0.11764705882352942, 0.2666666667, 0.0039215686, 0.4980392157, + 0.12156862745098039, 0.2666666667, 0.0039215686, 0.4980392157, 0.12549019607843137, + 0.262745098, 0.0039215686, 0.5137254902, 0.12941176470588237, 0.2509803922, 0.0039215686, + 0.5294117647, 0.13333333333333333, 0.2431372549, 0.0039215686, 0.5450980392, + 0.13725490196078433, 0.2392156863, 0.0039215686, 0.5607843137, 0.1411764705882353, + 0.231372549, 0.0039215686, 0.5764705882, 0.1450980392156863, 0.2196078431, 0.0039215686, + 0.5921568627, 0.14901960784313725, 0.2078431373, 0.0039215686, 0.6078431373, + 0.15294117647058825, 0.2039215686, 0.0039215686, 0.6235294118, 0.1568627450980392, + 0.1960784314, 0.0039215686, 0.6392156863, 0.1607843137254902, 0.1843137255, 0.0039215686, + 0.6549019608, 0.16470588235294117, 0.1725490196, 0.0039215686, 0.6705882353, + 0.16862745098039217, 0.168627451, 0.0039215686, 0.6862745098, 0.17254901960784313, + 0.1607843137, 0.0039215686, 0.7019607843, 0.17647058823529413, 0.1490196078, 0.0039215686, + 0.7176470588, 0.1803921568627451, 0.137254902, 0.0039215686, 0.7333333333, 0.1843137254901961, + 0.1333333333, 0.0039215686, 0.7490196078, 0.18823529411764706, 0.1215686275, 0.0039215686, + 0.7607843137, 0.19215686274509805, 0.1098039216, 0.0039215686, 0.7764705882, + 0.19607843137254902, 0.1058823529, 0.0039215686, 0.7921568627, 0.2, 0.0980392157, + 0.0039215686, 0.8078431373, 0.20392156862745098, 0.0862745098, 0.0039215686, 0.8235294118, + 0.20784313725490197, 0.0745098039, 0.0039215686, 0.8392156863, 0.21176470588235294, + 0.0705882353, 0.0039215686, 0.8549019608, 0.21568627450980393, 0.062745098, 0.0039215686, + 0.8705882353, 0.2196078431372549, 0.0509803922, 0.0039215686, 0.8862745098, + 0.2235294117647059, 0.0392156863, 0.0039215686, 0.9019607843, 0.22745098039215686, + 0.0352941176, 0.0039215686, 0.9176470588, 0.23137254901960785, 0.0274509804, 0.0039215686, + 0.9333333333, 0.23529411764705885, 0.0156862745, 0.0039215686, 0.9490196078, + 0.23921568627450984, 0.0078431373, 0.0039215686, 0.9647058824, 0.24313725490196078, + 0.0039215686, 0.0039215686, 0.9960784314, 0.24705882352941178, 0.0039215686, 0.0039215686, + 0.9960784314, 0.25098039215686274, 0.0039215686, 0.0196078431, 0.9647058824, + 0.2549019607843137, 0.0039215686, 0.0392156863, 0.9490196078, 0.25882352941176473, + 0.0039215686, 0.0549019608, 0.9333333333, 0.2627450980392157, 0.0039215686, 0.0745098039, + 0.9176470588, 0.26666666666666666, 0.0039215686, 0.0901960784, 0.9019607843, + 0.27058823529411763, 0.0039215686, 0.1098039216, 0.8862745098, 0.27450980392156865, + 0.0039215686, 0.1254901961, 0.8705882353, 0.2784313725490196, 0.0039215686, 0.1450980392, + 0.8549019608, 0.2823529411764706, 0.0039215686, 0.1607843137, 0.8392156863, + 0.28627450980392155, 0.0039215686, 0.1803921569, 0.8235294118, 0.2901960784313726, + 0.0039215686, 0.1960784314, 0.8078431373, 0.29411764705882354, 0.0039215686, 0.2156862745, + 0.7921568627, 0.2980392156862745, 0.0039215686, 0.231372549, 0.7764705882, + 0.30196078431372547, 0.0039215686, 0.2509803922, 0.7607843137, 0.3058823529411765, + 0.0039215686, 0.262745098, 0.7490196078, 0.30980392156862746, 0.0039215686, 0.2823529412, + 0.7333333333, 0.3137254901960784, 0.0039215686, 0.2980392157, 0.7176470588, + 0.3176470588235294, 0.0039215686, 0.3176470588, 0.7019607843, 0.3215686274509804, + 0.0039215686, 0.3333333333, 0.6862745098, 0.3254901960784314, 0.0039215686, 0.3529411765, + 0.6705882353, 0.32941176470588235, 0.0039215686, 0.368627451, 0.6549019608, + 0.3333333333333333, 0.0039215686, 0.3882352941, 0.6392156863, 0.33725490196078434, + 0.0039215686, 0.4039215686, 0.6235294118, 0.3411764705882353, 0.0039215686, 0.4235294118, + 0.6078431373, 0.34509803921568627, 0.0039215686, 0.4392156863, 0.5921568627, + 0.34901960784313724, 0.0039215686, 0.4588235294, 0.5764705882, 0.35294117647058826, + 0.0039215686, 0.4745098039, 0.5607843137, 0.3568627450980392, 0.0039215686, 0.4941176471, + 0.5450980392, 0.3607843137254902, 0.0039215686, 0.5098039216, 0.5294117647, + 0.36470588235294116, 0.0039215686, 0.5294117647, 0.5137254902, 0.3686274509803922, + 0.0039215686, 0.5450980392, 0.4980392157, 0.37254901960784315, 0.0039215686, 0.5647058824, + 0.4784313725, 0.3764705882352941, 0.0039215686, 0.5803921569, 0.462745098, 0.3803921568627451, + 0.0039215686, 0.6, 0.4470588235, 0.3843137254901961, 0.0039215686, 0.6156862745, 0.4274509804, + 0.38823529411764707, 0.0039215686, 0.6352941176, 0.4117647059, 0.39215686274509803, + 0.0039215686, 0.6509803922, 0.3960784314, 0.396078431372549, 0.0039215686, 0.6705882353, + 0.3803921569, 0.4, 0.0039215686, 0.6862745098, 0.3607843137, 0.403921568627451, 0.0039215686, + 0.7058823529, 0.3450980392, 0.40784313725490196, 0.0039215686, 0.7215686275, 0.3294117647, + 0.4117647058823529, 0.0039215686, 0.7411764706, 0.3137254902, 0.41568627450980394, + 0.0039215686, 0.7529411765, 0.2941176471, 0.4196078431372549, 0.0039215686, 0.7960784314, + 0.2784313725, 0.4235294117647059, 0.0039215686, 0.7960784314, 0.262745098, + 0.42745098039215684, 0.0392156863, 0.8039215686, 0.2509803922, 0.43137254901960786, + 0.0745098039, 0.8117647059, 0.231372549, 0.43529411764705883, 0.1098039216, 0.8196078431, + 0.2156862745, 0.4392156862745098, 0.1450980392, 0.8274509804, 0.2, 0.44313725490196076, + 0.1803921569, 0.8352941176, 0.1843137255, 0.4470588235294118, 0.2156862745, 0.8431372549, + 0.1647058824, 0.45098039215686275, 0.2509803922, 0.8509803922, 0.1490196078, + 0.4549019607843137, 0.2823529412, 0.8588235294, 0.1333333333, 0.4588235294117647, + 0.3176470588, 0.8666666667, 0.1176470588, 0.4627450980392157, 0.3529411765, 0.8745098039, + 0.0980392157, 0.4666666666666667, 0.3882352941, 0.8823529412, 0.0823529412, + 0.4705882352941177, 0.4235294118, 0.8901960784, 0.0666666667, 0.4745098039215686, + 0.4588235294, 0.8980392157, 0.0509803922, 0.4784313725490197, 0.4941176471, 0.9058823529, + 0.0431372549, 0.48235294117647065, 0.5294117647, 0.9137254902, 0.031372549, + 0.48627450980392156, 0.5647058824, 0.9215686275, 0.0196078431, 0.49019607843137253, 0.6, + 0.9294117647, 0.0078431373, 0.49411764705882355, 0.6352941176, 0.937254902, 0.0039215686, + 0.4980392156862745, 0.6705882353, 0.9450980392, 0.0039215686, 0.5019607843137255, + 0.7058823529, 0.9490196078, 0.0039215686, 0.5058823529411764, 0.7411764706, 0.9568627451, + 0.0039215686, 0.5098039215686274, 0.7725490196, 0.9607843137, 0.0039215686, + 0.5137254901960784, 0.8078431373, 0.968627451, 0.0039215686, 0.5176470588235295, 0.8431372549, + 0.9725490196, 0.0039215686, 0.5215686274509804, 0.8784313725, 0.9803921569, 0.0039215686, + 0.5254901960784314, 0.9137254902, 0.9843137255, 0.0039215686, 0.5294117647058824, + 0.9490196078, 0.9921568627, 0.0039215686, 0.5333333333333333, 0.9960784314, 0.9960784314, + 0.0039215686, 0.5372549019607843, 0.9960784314, 0.9960784314, 0.0039215686, + 0.5411764705882353, 0.9960784314, 0.9921568627, 0.0039215686, 0.5450980392156862, + 0.9960784314, 0.9843137255, 0.0039215686, 0.5490196078431373, 0.9960784314, 0.9764705882, + 0.0039215686, 0.5529411764705883, 0.9960784314, 0.968627451, 0.0039215686, 0.5568627450980392, + 0.9960784314, 0.9607843137, 0.0039215686, 0.5607843137254902, 0.9960784314, 0.9529411765, + 0.0039215686, 0.5647058823529412, 0.9960784314, 0.9450980392, 0.0039215686, + 0.5686274509803921, 0.9960784314, 0.937254902, 0.0039215686, 0.5725490196078431, 0.9960784314, + 0.9294117647, 0.0039215686, 0.5764705882352941, 0.9960784314, 0.9215686275, 0.0039215686, + 0.5803921568627451, 0.9960784314, 0.9137254902, 0.0039215686, 0.5843137254901961, + 0.9960784314, 0.9058823529, 0.0039215686, 0.5882352941176471, 0.9960784314, 0.8980392157, + 0.0039215686, 0.592156862745098, 0.9960784314, 0.8901960784, 0.0039215686, 0.596078431372549, + 0.9960784314, 0.8823529412, 0.0039215686, 0.6, 0.9960784314, 0.8745098039, 0.0039215686, + 0.6039215686274509, 0.9960784314, 0.8666666667, 0.0039215686, 0.6078431372549019, + 0.9960784314, 0.8588235294, 0.0039215686, 0.611764705882353, 0.9960784314, 0.8509803922, + 0.0039215686, 0.615686274509804, 0.9960784314, 0.8431372549, 0.0039215686, 0.6196078431372549, + 0.9960784314, 0.8352941176, 0.0039215686, 0.6235294117647059, 0.9960784314, 0.8274509804, + 0.0039215686, 0.6274509803921569, 0.9960784314, 0.8196078431, 0.0039215686, + 0.6313725490196078, 0.9960784314, 0.8117647059, 0.0039215686, 0.6352941176470588, + 0.9960784314, 0.8039215686, 0.0039215686, 0.6392156862745098, 0.9960784314, 0.7960784314, + 0.0039215686, 0.6431372549019608, 0.9960784314, 0.7882352941, 0.0039215686, + 0.6470588235294118, 0.9960784314, 0.7803921569, 0.0039215686, 0.6509803921568628, + 0.9960784314, 0.7725490196, 0.0039215686, 0.6549019607843137, 0.9960784314, 0.7647058824, + 0.0039215686, 0.6588235294117647, 0.9960784314, 0.7568627451, 0.0039215686, + 0.6627450980392157, 0.9960784314, 0.7490196078, 0.0039215686, 0.6666666666666666, + 0.9960784314, 0.7450980392, 0.0039215686, 0.6705882352941176, 0.9960784314, 0.737254902, + 0.0039215686, 0.6745098039215687, 0.9960784314, 0.7294117647, 0.0039215686, + 0.6784313725490196, 0.9960784314, 0.7215686275, 0.0039215686, 0.6823529411764706, + 0.9960784314, 0.7137254902, 0.0039215686, 0.6862745098039216, 0.9960784314, 0.7058823529, + 0.0039215686, 0.6901960784313725, 0.9960784314, 0.6980392157, 0.0039215686, + 0.6941176470588235, 0.9960784314, 0.6901960784, 0.0039215686, 0.6980392156862745, + 0.9960784314, 0.6823529412, 0.0039215686, 0.7019607843137254, 0.9960784314, 0.6745098039, + 0.0039215686, 0.7058823529411765, 0.9960784314, 0.6666666667, 0.0039215686, + 0.7098039215686275, 0.9960784314, 0.6588235294, 0.0039215686, 0.7137254901960784, + 0.9960784314, 0.6509803922, 0.0039215686, 0.7176470588235294, 0.9960784314, 0.6431372549, + 0.0039215686, 0.7215686274509804, 0.9960784314, 0.6352941176, 0.0039215686, + 0.7254901960784313, 0.9960784314, 0.6274509804, 0.0039215686, 0.7294117647058823, + 0.9960784314, 0.6196078431, 0.0039215686, 0.7333333333333333, 0.9960784314, 0.6117647059, + 0.0039215686, 0.7372549019607844, 0.9960784314, 0.6039215686, 0.0039215686, + 0.7411764705882353, 0.9960784314, 0.5960784314, 0.0039215686, 0.7450980392156863, + 0.9960784314, 0.5882352941, 0.0039215686, 0.7490196078431373, 0.9960784314, 0.5803921569, + 0.0039215686, 0.7529411764705882, 0.9960784314, 0.5725490196, 0.0039215686, + 0.7568627450980392, 0.9960784314, 0.5647058824, 0.0039215686, 0.7607843137254902, + 0.9960784314, 0.5568627451, 0.0039215686, 0.7647058823529411, 0.9960784314, 0.5490196078, + 0.0039215686, 0.7686274509803922, 0.9960784314, 0.5411764706, 0.0039215686, + 0.7725490196078432, 0.9960784314, 0.5333333333, 0.0039215686, 0.7764705882352941, + 0.9960784314, 0.5254901961, 0.0039215686, 0.7803921568627451, 0.9960784314, 0.5176470588, + 0.0039215686, 0.7843137254901961, 0.9960784314, 0.5098039216, 0.0039215686, 0.788235294117647, + 0.9960784314, 0.5019607843, 0.0039215686, 0.792156862745098, 0.9960784314, 0.4941176471, + 0.0039215686, 0.796078431372549, 0.9960784314, 0.4862745098, 0.0039215686, 0.8, 0.9960784314, + 0.4784313725, 0.0039215686, 0.803921568627451, 0.9960784314, 0.4705882353, 0.0039215686, + 0.807843137254902, 0.9960784314, 0.462745098, 0.0039215686, 0.8117647058823529, 0.9960784314, + 0.4549019608, 0.0039215686, 0.8156862745098039, 0.9960784314, 0.4470588235, 0.0039215686, + 0.8196078431372549, 0.9960784314, 0.4392156863, 0.0039215686, 0.8235294117647058, + 0.9960784314, 0.431372549, 0.0039215686, 0.8274509803921568, 0.9960784314, 0.4235294118, + 0.0039215686, 0.8313725490196079, 0.9960784314, 0.4156862745, 0.0039215686, + 0.8352941176470589, 0.9960784314, 0.4078431373, 0.0039215686, 0.8392156862745098, + 0.9960784314, 0.4, 0.0039215686, 0.8431372549019608, 0.9960784314, 0.3921568627, 0.0039215686, + 0.8470588235294118, 0.9960784314, 0.3843137255, 0.0039215686, 0.8509803921568627, + 0.9960784314, 0.3764705882, 0.0039215686, 0.8549019607843137, 0.9960784314, 0.368627451, + 0.0039215686, 0.8588235294117647, 0.9960784314, 0.3607843137, 0.0039215686, + 0.8627450980392157, 0.9960784314, 0.3529411765, 0.0039215686, 0.8666666666666667, + 0.9960784314, 0.3450980392, 0.0039215686, 0.8705882352941177, 0.9960784314, 0.337254902, + 0.0039215686, 0.8745098039215686, 0.9960784314, 0.3294117647, 0.0039215686, + 0.8784313725490196, 0.9960784314, 0.3215686275, 0.0039215686, 0.8823529411764706, + 0.9960784314, 0.3137254902, 0.0039215686, 0.8862745098039215, 0.9960784314, 0.3058823529, + 0.0039215686, 0.8901960784313725, 0.9960784314, 0.2980392157, 0.0039215686, + 0.8941176470588236, 0.9960784314, 0.2901960784, 0.0039215686, 0.8980392156862745, + 0.9960784314, 0.2823529412, 0.0039215686, 0.9019607843137255, 0.9960784314, 0.2705882353, + 0.0039215686, 0.9058823529411765, 0.9960784314, 0.2588235294, 0.0039215686, + 0.9098039215686274, 0.9960784314, 0.2509803922, 0.0039215686, 0.9137254901960784, + 0.9960784314, 0.2431372549, 0.0039215686, 0.9176470588235294, 0.9960784314, 0.231372549, + 0.0039215686, 0.9215686274509803, 0.9960784314, 0.2196078431, 0.0039215686, + 0.9254901960784314, 0.9960784314, 0.2117647059, 0.0039215686, 0.9294117647058824, + 0.9960784314, 0.2, 0.0039215686, 0.9333333333333333, 0.9960784314, 0.1882352941, 0.0039215686, + 0.9372549019607843, 0.9960784314, 0.1764705882, 0.0039215686, 0.9411764705882354, + 0.9960784314, 0.168627451, 0.0039215686, 0.9450980392156864, 0.9960784314, 0.1568627451, + 0.0039215686, 0.9490196078431372, 0.9960784314, 0.1450980392, 0.0039215686, + 0.9529411764705882, 0.9960784314, 0.1333333333, 0.0039215686, 0.9568627450980394, + 0.9960784314, 0.1254901961, 0.0039215686, 0.9607843137254903, 0.9960784314, 0.1137254902, + 0.0039215686, 0.9647058823529413, 0.9960784314, 0.1019607843, 0.0039215686, + 0.9686274509803922, 0.9960784314, 0.0901960784, 0.0039215686, 0.9725490196078431, + 0.9960784314, 0.0823529412, 0.0039215686, 0.9764705882352941, 0.9960784314, 0.0705882353, + 0.0039215686, 0.9803921568627451, 0.9960784314, 0.0588235294, 0.0039215686, 0.984313725490196, + 0.9960784314, 0.0470588235, 0.0039215686, 0.9882352941176471, 0.9960784314, 0.0392156863, + 0.0039215686, 0.9921568627450981, 0.9960784314, 0.0274509804, 0.0039215686, 0.996078431372549, + 0.9960784314, 0.0156862745, 0.0039215686, 1.0, 0.9960784314, 0.0156862745, 0.0039215686, + ], + description: 'S PET', + }, + { + ColorSpace: 'RGB', + Name: 'perfusion', + name: 'perfusion', + RGBPoints: [ + 0.0, 0.0, 0.0, 0.0, 0.00392156862745098, 0.0078431373, 0.0235294118, 0.0235294118, + 0.00784313725490196, 0.0078431373, 0.031372549, 0.0470588235, 0.011764705882352941, + 0.0078431373, 0.0392156863, 0.062745098, 0.01568627450980392, 0.0078431373, 0.0470588235, + 0.0862745098, 0.0196078431372549, 0.0078431373, 0.0549019608, 0.1019607843, + 0.023529411764705882, 0.0078431373, 0.0549019608, 0.1254901961, 0.027450980392156862, + 0.0078431373, 0.062745098, 0.1411764706, 0.03137254901960784, 0.0078431373, 0.0705882353, + 0.1647058824, 0.03529411764705882, 0.0078431373, 0.0784313725, 0.1803921569, + 0.0392156862745098, 0.0078431373, 0.0862745098, 0.2039215686, 0.043137254901960784, + 0.0078431373, 0.0862745098, 0.2196078431, 0.047058823529411764, 0.0078431373, 0.0941176471, + 0.2431372549, 0.050980392156862744, 0.0078431373, 0.1019607843, 0.2666666667, + 0.054901960784313725, 0.0078431373, 0.1098039216, 0.2823529412, 0.05882352941176471, + 0.0078431373, 0.1176470588, 0.3058823529, 0.06274509803921569, 0.0078431373, 0.1176470588, + 0.3215686275, 0.06666666666666667, 0.0078431373, 0.1254901961, 0.3450980392, + 0.07058823529411765, 0.0078431373, 0.1333333333, 0.3607843137, 0.07450980392156863, + 0.0078431373, 0.1411764706, 0.3843137255, 0.0784313725490196, 0.0078431373, 0.1490196078, 0.4, + 0.08235294117647059, 0.0078431373, 0.1490196078, 0.4235294118, 0.08627450980392157, + 0.0078431373, 0.1568627451, 0.4392156863, 0.09019607843137255, 0.0078431373, 0.1647058824, + 0.462745098, 0.09411764705882353, 0.0078431373, 0.1725490196, 0.4784313725, + 0.09803921568627451, 0.0078431373, 0.1803921569, 0.5019607843, 0.10196078431372549, + 0.0078431373, 0.1803921569, 0.5254901961, 0.10588235294117647, 0.0078431373, 0.1882352941, + 0.5411764706, 0.10980392156862745, 0.0078431373, 0.1960784314, 0.5647058824, + 0.11372549019607843, 0.0078431373, 0.2039215686, 0.5803921569, 0.11764705882352942, + 0.0078431373, 0.2117647059, 0.6039215686, 0.12156862745098039, 0.0078431373, 0.2117647059, + 0.6196078431, 0.12549019607843137, 0.0078431373, 0.2196078431, 0.6431372549, + 0.12941176470588237, 0.0078431373, 0.2274509804, 0.6588235294, 0.13333333333333333, + 0.0078431373, 0.2352941176, 0.6823529412, 0.13725490196078433, 0.0078431373, 0.2431372549, + 0.6980392157, 0.1411764705882353, 0.0078431373, 0.2431372549, 0.7215686275, + 0.1450980392156863, 0.0078431373, 0.2509803922, 0.737254902, 0.14901960784313725, + 0.0078431373, 0.2588235294, 0.7607843137, 0.15294117647058825, 0.0078431373, 0.2666666667, + 0.7843137255, 0.1568627450980392, 0.0078431373, 0.2745098039, 0.8, 0.1607843137254902, + 0.0078431373, 0.2745098039, 0.8235294118, 0.16470588235294117, 0.0078431373, 0.2823529412, + 0.8392156863, 0.16862745098039217, 0.0078431373, 0.2901960784, 0.862745098, + 0.17254901960784313, 0.0078431373, 0.2980392157, 0.8784313725, 0.17647058823529413, + 0.0078431373, 0.3058823529, 0.9019607843, 0.1803921568627451, 0.0078431373, 0.3058823529, + 0.9176470588, 0.1843137254901961, 0.0078431373, 0.2980392157, 0.9411764706, + 0.18823529411764706, 0.0078431373, 0.3058823529, 0.9568627451, 0.19215686274509805, + 0.0078431373, 0.2980392157, 0.9803921569, 0.19607843137254902, 0.0078431373, 0.2980392157, + 0.9882352941, 0.2, 0.0078431373, 0.2901960784, 0.9803921569, 0.20392156862745098, + 0.0078431373, 0.2901960784, 0.9647058824, 0.20784313725490197, 0.0078431373, 0.2823529412, + 0.9568627451, 0.21176470588235294, 0.0078431373, 0.2823529412, 0.9411764706, + 0.21568627450980393, 0.0078431373, 0.2745098039, 0.9333333333, 0.2196078431372549, + 0.0078431373, 0.2666666667, 0.9176470588, 0.2235294117647059, 0.0078431373, 0.2666666667, + 0.9098039216, 0.22745098039215686, 0.0078431373, 0.2588235294, 0.9019607843, + 0.23137254901960785, 0.0078431373, 0.2588235294, 0.8862745098, 0.23529411764705885, + 0.0078431373, 0.2509803922, 0.8784313725, 0.23921568627450984, 0.0078431373, 0.2509803922, + 0.862745098, 0.24313725490196078, 0.0078431373, 0.2431372549, 0.8549019608, + 0.24705882352941178, 0.0078431373, 0.2352941176, 0.8392156863, 0.25098039215686274, + 0.0078431373, 0.2352941176, 0.831372549, 0.2549019607843137, 0.0078431373, 0.2274509804, + 0.8235294118, 0.25882352941176473, 0.0078431373, 0.2274509804, 0.8078431373, + 0.2627450980392157, 0.0078431373, 0.2196078431, 0.8, 0.26666666666666666, 0.0078431373, + 0.2196078431, 0.7843137255, 0.27058823529411763, 0.0078431373, 0.2117647059, 0.7764705882, + 0.27450980392156865, 0.0078431373, 0.2039215686, 0.7607843137, 0.2784313725490196, + 0.0078431373, 0.2039215686, 0.7529411765, 0.2823529411764706, 0.0078431373, 0.1960784314, + 0.7450980392, 0.28627450980392155, 0.0078431373, 0.1960784314, 0.7294117647, + 0.2901960784313726, 0.0078431373, 0.1882352941, 0.7215686275, 0.29411764705882354, + 0.0078431373, 0.1882352941, 0.7058823529, 0.2980392156862745, 0.0078431373, 0.1803921569, + 0.6980392157, 0.30196078431372547, 0.0078431373, 0.1803921569, 0.6823529412, + 0.3058823529411765, 0.0078431373, 0.1725490196, 0.6745098039, 0.30980392156862746, + 0.0078431373, 0.1647058824, 0.6666666667, 0.3137254901960784, 0.0078431373, 0.1647058824, + 0.6509803922, 0.3176470588235294, 0.0078431373, 0.1568627451, 0.6431372549, + 0.3215686274509804, 0.0078431373, 0.1568627451, 0.6274509804, 0.3254901960784314, + 0.0078431373, 0.1490196078, 0.6196078431, 0.32941176470588235, 0.0078431373, 0.1490196078, + 0.6039215686, 0.3333333333333333, 0.0078431373, 0.1411764706, 0.5960784314, + 0.33725490196078434, 0.0078431373, 0.1333333333, 0.5882352941, 0.3411764705882353, + 0.0078431373, 0.1333333333, 0.5725490196, 0.34509803921568627, 0.0078431373, 0.1254901961, + 0.5647058824, 0.34901960784313724, 0.0078431373, 0.1254901961, 0.5490196078, + 0.35294117647058826, 0.0078431373, 0.1176470588, 0.5411764706, 0.3568627450980392, + 0.0078431373, 0.1176470588, 0.5254901961, 0.3607843137254902, 0.0078431373, 0.1098039216, + 0.5176470588, 0.36470588235294116, 0.0078431373, 0.1019607843, 0.5098039216, + 0.3686274509803922, 0.0078431373, 0.1019607843, 0.4941176471, 0.37254901960784315, + 0.0078431373, 0.0941176471, 0.4862745098, 0.3764705882352941, 0.0078431373, 0.0941176471, + 0.4705882353, 0.3803921568627451, 0.0078431373, 0.0862745098, 0.462745098, 0.3843137254901961, + 0.0078431373, 0.0862745098, 0.4470588235, 0.38823529411764707, 0.0078431373, 0.0784313725, + 0.4392156863, 0.39215686274509803, 0.0078431373, 0.0705882353, 0.431372549, 0.396078431372549, + 0.0078431373, 0.0705882353, 0.4156862745, 0.4, 0.0078431373, 0.062745098, 0.4078431373, + 0.403921568627451, 0.0078431373, 0.062745098, 0.3921568627, 0.40784313725490196, 0.0078431373, + 0.0549019608, 0.3843137255, 0.4117647058823529, 0.0078431373, 0.0549019608, 0.368627451, + 0.41568627450980394, 0.0078431373, 0.0470588235, 0.3607843137, 0.4196078431372549, + 0.0078431373, 0.0470588235, 0.3529411765, 0.4235294117647059, 0.0078431373, 0.0392156863, + 0.337254902, 0.42745098039215684, 0.0078431373, 0.031372549, 0.3294117647, + 0.43137254901960786, 0.0078431373, 0.031372549, 0.3137254902, 0.43529411764705883, + 0.0078431373, 0.0235294118, 0.3058823529, 0.4392156862745098, 0.0078431373, 0.0235294118, + 0.2901960784, 0.44313725490196076, 0.0078431373, 0.0156862745, 0.2823529412, + 0.4470588235294118, 0.0078431373, 0.0156862745, 0.2745098039, 0.45098039215686275, + 0.0078431373, 0.0078431373, 0.2588235294, 0.4549019607843137, 0.0235294118, 0.0078431373, + 0.2509803922, 0.4588235294117647, 0.0078431373, 0.0078431373, 0.2352941176, + 0.4627450980392157, 0.0078431373, 0.0078431373, 0.2274509804, 0.4666666666666667, + 0.0078431373, 0.0078431373, 0.2117647059, 0.4705882352941177, 0.0078431373, 0.0078431373, + 0.2039215686, 0.4745098039215686, 0.0078431373, 0.0078431373, 0.1960784314, + 0.4784313725490197, 0.0078431373, 0.0078431373, 0.1803921569, 0.48235294117647065, + 0.0078431373, 0.0078431373, 0.1725490196, 0.48627450980392156, 0.0078431373, 0.0078431373, + 0.1568627451, 0.49019607843137253, 0.0078431373, 0.0078431373, 0.1490196078, + 0.49411764705882355, 0.0078431373, 0.0078431373, 0.1333333333, 0.4980392156862745, + 0.0078431373, 0.0078431373, 0.1254901961, 0.5019607843137255, 0.0078431373, 0.0078431373, + 0.1176470588, 0.5058823529411764, 0.0078431373, 0.0078431373, 0.1019607843, + 0.5098039215686274, 0.0078431373, 0.0078431373, 0.0941176471, 0.5137254901960784, + 0.0078431373, 0.0078431373, 0.0784313725, 0.5176470588235295, 0.0078431373, 0.0078431373, + 0.0705882353, 0.5215686274509804, 0.0078431373, 0.0078431373, 0.0549019608, + 0.5254901960784314, 0.0078431373, 0.0078431373, 0.0470588235, 0.5294117647058824, + 0.0235294118, 0.0078431373, 0.0392156863, 0.5333333333333333, 0.031372549, 0.0078431373, + 0.0235294118, 0.5372549019607843, 0.0392156863, 0.0078431373, 0.0156862745, + 0.5411764705882353, 0.0549019608, 0.0078431373, 0.0, 0.5450980392156862, 0.062745098, + 0.0078431373, 0.0, 0.5490196078431373, 0.0705882353, 0.0078431373, 0.0, 0.5529411764705883, + 0.0862745098, 0.0078431373, 0.0, 0.5568627450980392, 0.0941176471, 0.0078431373, 0.0, + 0.5607843137254902, 0.1019607843, 0.0078431373, 0.0, 0.5647058823529412, 0.1098039216, + 0.0078431373, 0.0, 0.5686274509803921, 0.1254901961, 0.0078431373, 0.0, 0.5725490196078431, + 0.1333333333, 0.0078431373, 0.0, 0.5764705882352941, 0.1411764706, 0.0078431373, 0.0, + 0.5803921568627451, 0.1568627451, 0.0078431373, 0.0, 0.5843137254901961, 0.1647058824, + 0.0078431373, 0.0, 0.5882352941176471, 0.1725490196, 0.0078431373, 0.0, 0.592156862745098, + 0.1882352941, 0.0078431373, 0.0, 0.596078431372549, 0.1960784314, 0.0078431373, 0.0, 0.6, + 0.2039215686, 0.0078431373, 0.0, 0.6039215686274509, 0.2117647059, 0.0078431373, 0.0, + 0.6078431372549019, 0.2274509804, 0.0078431373, 0.0, 0.611764705882353, 0.2352941176, + 0.0078431373, 0.0, 0.615686274509804, 0.2431372549, 0.0078431373, 0.0, 0.6196078431372549, + 0.2588235294, 0.0078431373, 0.0, 0.6235294117647059, 0.2666666667, 0.0078431373, 0.0, + 0.6274509803921569, 0.2745098039, 0.0, 0.0, 0.6313725490196078, 0.2901960784, 0.0156862745, + 0.0, 0.6352941176470588, 0.2980392157, 0.0235294118, 0.0, 0.6392156862745098, 0.3058823529, + 0.0392156863, 0.0, 0.6431372549019608, 0.3137254902, 0.0470588235, 0.0, 0.6470588235294118, + 0.3294117647, 0.0549019608, 0.0, 0.6509803921568628, 0.337254902, 0.0705882353, 0.0, + 0.6549019607843137, 0.3450980392, 0.0784313725, 0.0, 0.6588235294117647, 0.3607843137, + 0.0862745098, 0.0, 0.6627450980392157, 0.368627451, 0.1019607843, 0.0, 0.6666666666666666, + 0.3764705882, 0.1098039216, 0.0, 0.6705882352941176, 0.3843137255, 0.1176470588, 0.0, + 0.6745098039215687, 0.4, 0.1333333333, 0.0, 0.6784313725490196, 0.4078431373, 0.1411764706, + 0.0, 0.6823529411764706, 0.4156862745, 0.1490196078, 0.0, 0.6862745098039216, 0.431372549, + 0.1647058824, 0.0, 0.6901960784313725, 0.4392156863, 0.1725490196, 0.0, 0.6941176470588235, + 0.4470588235, 0.1803921569, 0.0, 0.6980392156862745, 0.462745098, 0.1960784314, 0.0, + 0.7019607843137254, 0.4705882353, 0.2039215686, 0.0, 0.7058823529411765, 0.4784313725, + 0.2117647059, 0.0, 0.7098039215686275, 0.4862745098, 0.2274509804, 0.0, 0.7137254901960784, + 0.5019607843, 0.2352941176, 0.0, 0.7176470588235294, 0.5098039216, 0.2431372549, 0.0, + 0.7215686274509804, 0.5176470588, 0.2588235294, 0.0, 0.7254901960784313, 0.5333333333, + 0.2666666667, 0.0, 0.7294117647058823, 0.5411764706, 0.2745098039, 0.0, 0.7333333333333333, + 0.5490196078, 0.2901960784, 0.0, 0.7372549019607844, 0.5647058824, 0.2980392157, 0.0, + 0.7411764705882353, 0.5725490196, 0.3058823529, 0.0, 0.7450980392156863, 0.5803921569, + 0.3215686275, 0.0, 0.7490196078431373, 0.5882352941, 0.3294117647, 0.0, 0.7529411764705882, + 0.6039215686, 0.337254902, 0.0, 0.7568627450980392, 0.6117647059, 0.3529411765, 0.0, + 0.7607843137254902, 0.6196078431, 0.3607843137, 0.0, 0.7647058823529411, 0.6352941176, + 0.368627451, 0.0, 0.7686274509803922, 0.6431372549, 0.3843137255, 0.0, 0.7725490196078432, + 0.6509803922, 0.3921568627, 0.0, 0.7764705882352941, 0.6588235294, 0.4, 0.0, + 0.7803921568627451, 0.6745098039, 0.4156862745, 0.0, 0.7843137254901961, 0.6823529412, + 0.4235294118, 0.0, 0.788235294117647, 0.6901960784, 0.431372549, 0.0, 0.792156862745098, + 0.7058823529, 0.4470588235, 0.0, 0.796078431372549, 0.7137254902, 0.4549019608, 0.0, 0.8, + 0.7215686275, 0.462745098, 0.0, 0.803921568627451, 0.737254902, 0.4784313725, 0.0, + 0.807843137254902, 0.7450980392, 0.4862745098, 0.0, 0.8117647058823529, 0.7529411765, + 0.4941176471, 0.0, 0.8156862745098039, 0.7607843137, 0.5098039216, 0.0, 0.8196078431372549, + 0.7764705882, 0.5176470588, 0.0, 0.8235294117647058, 0.7843137255, 0.5254901961, 0.0, + 0.8274509803921568, 0.7921568627, 0.5411764706, 0.0, 0.8313725490196079, 0.8078431373, + 0.5490196078, 0.0, 0.8352941176470589, 0.8156862745, 0.5568627451, 0.0, 0.8392156862745098, + 0.8235294118, 0.5725490196, 0.0, 0.8431372549019608, 0.8392156863, 0.5803921569, 0.0, + 0.8470588235294118, 0.8470588235, 0.5882352941, 0.0, 0.8509803921568627, 0.8549019608, + 0.6039215686, 0.0, 0.8549019607843137, 0.862745098, 0.6117647059, 0.0, 0.8588235294117647, + 0.8784313725, 0.6196078431, 0.0, 0.8627450980392157, 0.8862745098, 0.6352941176, 0.0, + 0.8666666666666667, 0.8941176471, 0.6431372549, 0.0, 0.8705882352941177, 0.9098039216, + 0.6509803922, 0.0, 0.8745098039215686, 0.9176470588, 0.6666666667, 0.0, 0.8784313725490196, + 0.9254901961, 0.6745098039, 0.0, 0.8823529411764706, 0.9411764706, 0.6823529412, 0.0, + 0.8862745098039215, 0.9490196078, 0.6980392157, 0.0, 0.8901960784313725, 0.9568627451, + 0.7058823529, 0.0, 0.8941176470588236, 0.9647058824, 0.7137254902, 0.0, 0.8980392156862745, + 0.9803921569, 0.7294117647, 0.0, 0.9019607843137255, 0.9882352941, 0.737254902, 0.0, + 0.9058823529411765, 0.9960784314, 0.7450980392, 0.0, 0.9098039215686274, 0.9960784314, + 0.7607843137, 0.0, 0.9137254901960784, 0.9960784314, 0.768627451, 0.0, 0.9176470588235294, + 0.9960784314, 0.7764705882, 0.0, 0.9215686274509803, 0.9960784314, 0.7921568627, 0.0, + 0.9254901960784314, 0.9960784314, 0.8, 0.0, 0.9294117647058824, 0.9960784314, 0.8078431373, + 0.0, 0.9333333333333333, 0.9960784314, 0.8235294118, 0.0, 0.9372549019607843, 0.9960784314, + 0.831372549, 0.0, 0.9411764705882354, 0.9960784314, 0.8392156863, 0.0, 0.9450980392156864, + 0.9960784314, 0.8549019608, 0.0, 0.9490196078431372, 0.9960784314, 0.862745098, 0.0549019608, + 0.9529411764705882, 0.9960784314, 0.8705882353, 0.1098039216, 0.9568627450980394, + 0.9960784314, 0.8862745098, 0.1647058824, 0.9607843137254903, 0.9960784314, 0.8941176471, + 0.2196078431, 0.9647058823529413, 0.9960784314, 0.9019607843, 0.2666666667, + 0.9686274509803922, 0.9960784314, 0.9176470588, 0.3215686275, 0.9725490196078431, + 0.9960784314, 0.9254901961, 0.3764705882, 0.9764705882352941, 0.9960784314, 0.9333333333, + 0.431372549, 0.9803921568627451, 0.9960784314, 0.9490196078, 0.4862745098, 0.984313725490196, + 0.9960784314, 0.9568627451, 0.5333333333, 0.9882352941176471, 0.9960784314, 0.9647058824, + 0.5882352941, 0.9921568627450981, 0.9960784314, 0.9803921569, 0.6431372549, 0.996078431372549, + 0.9960784314, 0.9882352941, 0.6980392157, 1.0, 0.9960784314, 0.9960784314, 0.7450980392, + ], + description: 'Perfusion', + }, + { + ColorSpace: 'RGB', + Name: 'rainbow_2', + name: 'rainbow_2', + RGBPoints: [ + 0.0, 0.0, 0.0, 0.0, 0.00392156862745098, 0.0156862745, 0.0, 0.0117647059, 0.00784313725490196, + 0.0352941176, 0.0, 0.0274509804, 0.011764705882352941, 0.0509803922, 0.0, 0.0392156863, + 0.01568627450980392, 0.0705882353, 0.0, 0.0549019608, 0.0196078431372549, 0.0862745098, 0.0, + 0.0745098039, 0.023529411764705882, 0.1058823529, 0.0, 0.0901960784, 0.027450980392156862, + 0.1215686275, 0.0, 0.1098039216, 0.03137254901960784, 0.1411764706, 0.0, 0.1254901961, + 0.03529411764705882, 0.1568627451, 0.0, 0.1490196078, 0.0392156862745098, 0.1764705882, 0.0, + 0.168627451, 0.043137254901960784, 0.1960784314, 0.0, 0.1882352941, 0.047058823529411764, + 0.2117647059, 0.0, 0.2078431373, 0.050980392156862744, 0.2274509804, 0.0, 0.231372549, + 0.054901960784313725, 0.2392156863, 0.0, 0.2470588235, 0.05882352941176471, 0.2509803922, 0.0, + 0.2666666667, 0.06274509803921569, 0.2666666667, 0.0, 0.2823529412, 0.06666666666666667, + 0.2705882353, 0.0, 0.3019607843, 0.07058823529411765, 0.2823529412, 0.0, 0.3176470588, + 0.07450980392156863, 0.2901960784, 0.0, 0.337254902, 0.0784313725490196, 0.3019607843, 0.0, + 0.3568627451, 0.08235294117647059, 0.3098039216, 0.0, 0.3725490196, 0.08627450980392157, + 0.3137254902, 0.0, 0.3921568627, 0.09019607843137255, 0.3215686275, 0.0, 0.4078431373, + 0.09411764705882353, 0.3254901961, 0.0, 0.4274509804, 0.09803921568627451, 0.3333333333, 0.0, + 0.4431372549, 0.10196078431372549, 0.3294117647, 0.0, 0.462745098, 0.10588235294117647, + 0.337254902, 0.0, 0.4784313725, 0.10980392156862745, 0.3411764706, 0.0, 0.4980392157, + 0.11372549019607843, 0.3450980392, 0.0, 0.5176470588, 0.11764705882352942, 0.337254902, 0.0, + 0.5333333333, 0.12156862745098039, 0.3411764706, 0.0, 0.5529411765, 0.12549019607843137, + 0.3411764706, 0.0, 0.568627451, 0.12941176470588237, 0.3411764706, 0.0, 0.5882352941, + 0.13333333333333333, 0.3333333333, 0.0, 0.6039215686, 0.13725490196078433, 0.3294117647, 0.0, + 0.6235294118, 0.1411764705882353, 0.3294117647, 0.0, 0.6392156863, 0.1450980392156863, + 0.3294117647, 0.0, 0.6588235294, 0.14901960784313725, 0.3254901961, 0.0, 0.6784313725, + 0.15294117647058825, 0.3098039216, 0.0, 0.6941176471, 0.1568627450980392, 0.3058823529, 0.0, + 0.7137254902, 0.1607843137254902, 0.3019607843, 0.0, 0.7294117647, 0.16470588235294117, + 0.2980392157, 0.0, 0.7490196078, 0.16862745098039217, 0.2784313725, 0.0, 0.7647058824, + 0.17254901960784313, 0.2745098039, 0.0, 0.7843137255, 0.17647058823529413, 0.2666666667, 0.0, + 0.8, 0.1803921568627451, 0.2588235294, 0.0, 0.8196078431, 0.1843137254901961, 0.2352941176, + 0.0, 0.8392156863, 0.18823529411764706, 0.2274509804, 0.0, 0.8549019608, 0.19215686274509805, + 0.2156862745, 0.0, 0.8745098039, 0.19607843137254902, 0.2078431373, 0.0, 0.8901960784, 0.2, + 0.1803921569, 0.0, 0.9098039216, 0.20392156862745098, 0.168627451, 0.0, 0.9254901961, + 0.20784313725490197, 0.1568627451, 0.0, 0.9450980392, 0.21176470588235294, 0.1411764706, 0.0, + 0.9607843137, 0.21568627450980393, 0.1294117647, 0.0, 0.9803921569, 0.2196078431372549, + 0.0980392157, 0.0, 1.0, 0.2235294117647059, 0.0823529412, 0.0, 1.0, 0.22745098039215686, + 0.062745098, 0.0, 1.0, 0.23137254901960785, 0.0470588235, 0.0, 1.0, 0.23529411764705885, + 0.0156862745, 0.0, 1.0, 0.23921568627450984, 0.0, 0.0, 1.0, 0.24313725490196078, 0.0, + 0.0156862745, 1.0, 0.24705882352941178, 0.0, 0.031372549, 1.0, 0.25098039215686274, 0.0, + 0.062745098, 1.0, 0.2549019607843137, 0.0, 0.0823529412, 1.0, 0.25882352941176473, 0.0, + 0.0980392157, 1.0, 0.2627450980392157, 0.0, 0.1137254902, 1.0, 0.26666666666666666, 0.0, + 0.1490196078, 1.0, 0.27058823529411763, 0.0, 0.1647058824, 1.0, 0.27450980392156865, 0.0, + 0.1803921569, 1.0, 0.2784313725490196, 0.0, 0.2, 1.0, 0.2823529411764706, 0.0, 0.2156862745, + 1.0, 0.28627450980392155, 0.0, 0.2470588235, 1.0, 0.2901960784313726, 0.0, 0.262745098, 1.0, + 0.29411764705882354, 0.0, 0.2823529412, 1.0, 0.2980392156862745, 0.0, 0.2980392157, 1.0, + 0.30196078431372547, 0.0, 0.3294117647, 1.0, 0.3058823529411765, 0.0, 0.3490196078, 1.0, + 0.30980392156862746, 0.0, 0.3647058824, 1.0, 0.3137254901960784, 0.0, 0.3803921569, 1.0, + 0.3176470588235294, 0.0, 0.4156862745, 1.0, 0.3215686274509804, 0.0, 0.431372549, 1.0, + 0.3254901960784314, 0.0, 0.4470588235, 1.0, 0.32941176470588235, 0.0, 0.4666666667, 1.0, + 0.3333333333333333, 0.0, 0.4980392157, 1.0, 0.33725490196078434, 0.0, 0.5137254902, 1.0, + 0.3411764705882353, 0.0, 0.5294117647, 1.0, 0.34509803921568627, 0.0, 0.5490196078, 1.0, + 0.34901960784313724, 0.0, 0.5647058824, 1.0, 0.35294117647058826, 0.0, 0.5960784314, 1.0, + 0.3568627450980392, 0.0, 0.6156862745, 1.0, 0.3607843137254902, 0.0, 0.631372549, 1.0, + 0.36470588235294116, 0.0, 0.6470588235, 1.0, 0.3686274509803922, 0.0, 0.6823529412, 1.0, + 0.37254901960784315, 0.0, 0.6980392157, 1.0, 0.3764705882352941, 0.0, 0.7137254902, 1.0, + 0.3803921568627451, 0.0, 0.7333333333, 1.0, 0.3843137254901961, 0.0, 0.7647058824, 1.0, + 0.38823529411764707, 0.0, 0.7803921569, 1.0, 0.39215686274509803, 0.0, 0.7960784314, 1.0, + 0.396078431372549, 0.0, 0.8156862745, 1.0, 0.4, 0.0, 0.8470588235, 1.0, 0.403921568627451, + 0.0, 0.862745098, 1.0, 0.40784313725490196, 0.0, 0.8823529412, 1.0, 0.4117647058823529, 0.0, + 0.8980392157, 1.0, 0.41568627450980394, 0.0, 0.9137254902, 1.0, 0.4196078431372549, 0.0, + 0.9490196078, 1.0, 0.4235294117647059, 0.0, 0.9647058824, 1.0, 0.42745098039215684, 0.0, + 0.9803921569, 1.0, 0.43137254901960786, 0.0, 1.0, 1.0, 0.43529411764705883, 0.0, 1.0, + 0.9647058824, 0.4392156862745098, 0.0, 1.0, 0.9490196078, 0.44313725490196076, 0.0, 1.0, + 0.9333333333, 0.4470588235294118, 0.0, 1.0, 0.9137254902, 0.45098039215686275, 0.0, 1.0, + 0.8823529412, 0.4549019607843137, 0.0, 1.0, 0.862745098, 0.4588235294117647, 0.0, 1.0, + 0.8470588235, 0.4627450980392157, 0.0, 1.0, 0.831372549, 0.4666666666666667, 0.0, 1.0, + 0.7960784314, 0.4705882352941177, 0.0, 1.0, 0.7803921569, 0.4745098039215686, 0.0, 1.0, + 0.7647058824, 0.4784313725490197, 0.0, 1.0, 0.7490196078, 0.48235294117647065, 0.0, 1.0, + 0.7333333333, 0.48627450980392156, 0.0, 1.0, 0.6980392157, 0.49019607843137253, 0.0, 1.0, + 0.6823529412, 0.49411764705882355, 0.0, 1.0, 0.6666666667, 0.4980392156862745, 0.0, 1.0, + 0.6470588235, 0.5019607843137255, 0.0, 1.0, 0.6156862745, 0.5058823529411764, 0.0, 1.0, + 0.5960784314, 0.5098039215686274, 0.0, 1.0, 0.5803921569, 0.5137254901960784, 0.0, 1.0, + 0.5647058824, 0.5176470588235295, 0.0, 1.0, 0.5294117647, 0.5215686274509804, 0.0, 1.0, + 0.5137254902, 0.5254901960784314, 0.0, 1.0, 0.4980392157, 0.5294117647058824, 0.0, 1.0, + 0.4823529412, 0.5333333333333333, 0.0, 1.0, 0.4470588235, 0.5372549019607843, 0.0, 1.0, + 0.431372549, 0.5411764705882353, 0.0, 1.0, 0.4156862745, 0.5450980392156862, 0.0, 1.0, 0.4, + 0.5490196078431373, 0.0, 1.0, 0.3803921569, 0.5529411764705883, 0.0, 1.0, 0.3490196078, + 0.5568627450980392, 0.0, 1.0, 0.3294117647, 0.5607843137254902, 0.0, 1.0, 0.3137254902, + 0.5647058823529412, 0.0, 1.0, 0.2980392157, 0.5686274509803921, 0.0, 1.0, 0.262745098, + 0.5725490196078431, 0.0, 1.0, 0.2470588235, 0.5764705882352941, 0.0, 1.0, 0.231372549, + 0.5803921568627451, 0.0, 1.0, 0.2156862745, 0.5843137254901961, 0.0, 1.0, 0.1803921569, + 0.5882352941176471, 0.0, 1.0, 0.1647058824, 0.592156862745098, 0.0, 1.0, 0.1490196078, + 0.596078431372549, 0.0, 1.0, 0.1333333333, 0.6, 0.0, 1.0, 0.0980392157, 0.6039215686274509, + 0.0, 1.0, 0.0823529412, 0.6078431372549019, 0.0, 1.0, 0.062745098, 0.611764705882353, 0.0, + 1.0, 0.0470588235, 0.615686274509804, 0.0, 1.0, 0.031372549, 0.6196078431372549, 0.0, 1.0, + 0.0, 0.6235294117647059, 0.0156862745, 1.0, 0.0, 0.6274509803921569, 0.031372549, 1.0, 0.0, + 0.6313725490196078, 0.0470588235, 1.0, 0.0, 0.6352941176470588, 0.0823529412, 1.0, 0.0, + 0.6392156862745098, 0.0980392157, 1.0, 0.0, 0.6431372549019608, 0.1137254902, 1.0, 0.0, + 0.6470588235294118, 0.1294117647, 1.0, 0.0, 0.6509803921568628, 0.1647058824, 1.0, 0.0, + 0.6549019607843137, 0.1803921569, 1.0, 0.0, 0.6588235294117647, 0.2, 1.0, 0.0, + 0.6627450980392157, 0.2156862745, 1.0, 0.0, 0.6666666666666666, 0.2470588235, 1.0, 0.0, + 0.6705882352941176, 0.262745098, 1.0, 0.0, 0.6745098039215687, 0.2823529412, 1.0, 0.0, + 0.6784313725490196, 0.2980392157, 1.0, 0.0, 0.6823529411764706, 0.3137254902, 1.0, 0.0, + 0.6862745098039216, 0.3490196078, 1.0, 0.0, 0.6901960784313725, 0.3647058824, 1.0, 0.0, + 0.6941176470588235, 0.3803921569, 1.0, 0.0, 0.6980392156862745, 0.3960784314, 1.0, 0.0, + 0.7019607843137254, 0.431372549, 1.0, 0.0, 0.7058823529411765, 0.4470588235, 1.0, 0.0, + 0.7098039215686275, 0.4666666667, 1.0, 0.0, 0.7137254901960784, 0.4823529412, 1.0, 0.0, + 0.7176470588235294, 0.5137254902, 1.0, 0.0, 0.7215686274509804, 0.5294117647, 1.0, 0.0, + 0.7254901960784313, 0.5490196078, 1.0, 0.0, 0.7294117647058823, 0.5647058824, 1.0, 0.0, + 0.7333333333333333, 0.6, 1.0, 0.0, 0.7372549019607844, 0.6156862745, 1.0, 0.0, + 0.7411764705882353, 0.631372549, 1.0, 0.0, 0.7450980392156863, 0.6470588235, 1.0, 0.0, + 0.7490196078431373, 0.662745098, 1.0, 0.0, 0.7529411764705882, 0.6980392157, 1.0, 0.0, + 0.7568627450980392, 0.7137254902, 1.0, 0.0, 0.7607843137254902, 0.7333333333, 1.0, 0.0, + 0.7647058823529411, 0.7490196078, 1.0, 0.0, 0.7686274509803922, 0.7803921569, 1.0, 0.0, + 0.7725490196078432, 0.7960784314, 1.0, 0.0, 0.7764705882352941, 0.8156862745, 1.0, 0.0, + 0.7803921568627451, 0.831372549, 1.0, 0.0, 0.7843137254901961, 0.8666666667, 1.0, 0.0, + 0.788235294117647, 0.8823529412, 1.0, 0.0, 0.792156862745098, 0.8980392157, 1.0, 0.0, + 0.796078431372549, 0.9137254902, 1.0, 0.0, 0.8, 0.9490196078, 1.0, 0.0, 0.803921568627451, + 0.9647058824, 1.0, 0.0, 0.807843137254902, 0.9803921569, 1.0, 0.0, 0.8117647058823529, 1.0, + 1.0, 0.0, 0.8156862745098039, 1.0, 0.9803921569, 0.0, 0.8196078431372549, 1.0, 0.9490196078, + 0.0, 0.8235294117647058, 1.0, 0.9333333333, 0.0, 0.8274509803921568, 1.0, 0.9137254902, 0.0, + 0.8313725490196079, 1.0, 0.8980392157, 0.0, 0.8352941176470589, 1.0, 0.8666666667, 0.0, + 0.8392156862745098, 1.0, 0.8470588235, 0.0, 0.8431372549019608, 1.0, 0.831372549, 0.0, + 0.8470588235294118, 1.0, 0.8156862745, 0.0, 0.8509803921568627, 1.0, 0.7803921569, 0.0, + 0.8549019607843137, 1.0, 0.7647058824, 0.0, 0.8588235294117647, 1.0, 0.7490196078, 0.0, + 0.8627450980392157, 1.0, 0.7333333333, 0.0, 0.8666666666666667, 1.0, 0.6980392157, 0.0, + 0.8705882352941177, 1.0, 0.6823529412, 0.0, 0.8745098039215686, 1.0, 0.6666666667, 0.0, + 0.8784313725490196, 1.0, 0.6470588235, 0.0, 0.8823529411764706, 1.0, 0.631372549, 0.0, + 0.8862745098039215, 1.0, 0.6, 0.0, 0.8901960784313725, 1.0, 0.5803921569, 0.0, + 0.8941176470588236, 1.0, 0.5647058824, 0.0, 0.8980392156862745, 1.0, 0.5490196078, 0.0, + 0.9019607843137255, 1.0, 0.5137254902, 0.0, 0.9058823529411765, 1.0, 0.4980392157, 0.0, + 0.9098039215686274, 1.0, 0.4823529412, 0.0, 0.9137254901960784, 1.0, 0.4666666667, 0.0, + 0.9176470588235294, 1.0, 0.431372549, 0.0, 0.9215686274509803, 1.0, 0.4156862745, 0.0, + 0.9254901960784314, 1.0, 0.4, 0.0, 0.9294117647058824, 1.0, 0.3803921569, 0.0, + 0.9333333333333333, 1.0, 0.3490196078, 0.0, 0.9372549019607843, 1.0, 0.3333333333, 0.0, + 0.9411764705882354, 1.0, 0.3137254902, 0.0, 0.9450980392156864, 1.0, 0.2980392157, 0.0, + 0.9490196078431372, 1.0, 0.2823529412, 0.0, 0.9529411764705882, 1.0, 0.2470588235, 0.0, + 0.9568627450980394, 1.0, 0.231372549, 0.0, 0.9607843137254903, 1.0, 0.2156862745, 0.0, + 0.9647058823529413, 1.0, 0.2, 0.0, 0.9686274509803922, 1.0, 0.1647058824, 0.0, + 0.9725490196078431, 1.0, 0.1490196078, 0.0, 0.9764705882352941, 1.0, 0.1333333333, 0.0, + 0.9803921568627451, 1.0, 0.1137254902, 0.0, 0.984313725490196, 1.0, 0.0823529412, 0.0, + 0.9882352941176471, 1.0, 0.0666666667, 0.0, 0.9921568627450981, 1.0, 0.0470588235, 0.0, + 0.996078431372549, 1.0, 0.031372549, 0.0, 1.0, 1.0, 0.0, 0.0, + ], + description: 'Rainbow', + }, + { + ColorSpace: 'RGB', + Name: 'suv', + name: 'suv', + RGBPoints: [ + 0.0, 1.0, 1.0, 1.0, 0.00392156862745098, 1.0, 1.0, 1.0, 0.00784313725490196, 1.0, 1.0, 1.0, + 0.011764705882352941, 1.0, 1.0, 1.0, 0.01568627450980392, 1.0, 1.0, 1.0, 0.0196078431372549, + 1.0, 1.0, 1.0, 0.023529411764705882, 1.0, 1.0, 1.0, 0.027450980392156862, 1.0, 1.0, 1.0, + 0.03137254901960784, 1.0, 1.0, 1.0, 0.03529411764705882, 1.0, 1.0, 1.0, 0.0392156862745098, + 1.0, 1.0, 1.0, 0.043137254901960784, 1.0, 1.0, 1.0, 0.047058823529411764, 1.0, 1.0, 1.0, + 0.050980392156862744, 1.0, 1.0, 1.0, 0.054901960784313725, 1.0, 1.0, 1.0, 0.05882352941176471, + 1.0, 1.0, 1.0, 0.06274509803921569, 1.0, 1.0, 1.0, 0.06666666666666667, 1.0, 1.0, 1.0, + 0.07058823529411765, 1.0, 1.0, 1.0, 0.07450980392156863, 1.0, 1.0, 1.0, 0.0784313725490196, + 1.0, 1.0, 1.0, 0.08235294117647059, 1.0, 1.0, 1.0, 0.08627450980392157, 1.0, 1.0, 1.0, + 0.09019607843137255, 1.0, 1.0, 1.0, 0.09411764705882353, 1.0, 1.0, 1.0, 0.09803921568627451, + 1.0, 1.0, 1.0, 0.10196078431372549, 0.737254902, 0.737254902, 0.737254902, + 0.10588235294117647, 0.737254902, 0.737254902, 0.737254902, 0.10980392156862745, 0.737254902, + 0.737254902, 0.737254902, 0.11372549019607843, 0.737254902, 0.737254902, 0.737254902, + 0.11764705882352942, 0.737254902, 0.737254902, 0.737254902, 0.12156862745098039, 0.737254902, + 0.737254902, 0.737254902, 0.12549019607843137, 0.737254902, 0.737254902, 0.737254902, + 0.12941176470588237, 0.737254902, 0.737254902, 0.737254902, 0.13333333333333333, 0.737254902, + 0.737254902, 0.737254902, 0.13725490196078433, 0.737254902, 0.737254902, 0.737254902, + 0.1411764705882353, 0.737254902, 0.737254902, 0.737254902, 0.1450980392156863, 0.737254902, + 0.737254902, 0.737254902, 0.14901960784313725, 0.737254902, 0.737254902, 0.737254902, + 0.15294117647058825, 0.737254902, 0.737254902, 0.737254902, 0.1568627450980392, 0.737254902, + 0.737254902, 0.737254902, 0.1607843137254902, 0.737254902, 0.737254902, 0.737254902, + 0.16470588235294117, 0.737254902, 0.737254902, 0.737254902, 0.16862745098039217, 0.737254902, + 0.737254902, 0.737254902, 0.17254901960784313, 0.737254902, 0.737254902, 0.737254902, + 0.17647058823529413, 0.737254902, 0.737254902, 0.737254902, 0.1803921568627451, 0.737254902, + 0.737254902, 0.737254902, 0.1843137254901961, 0.737254902, 0.737254902, 0.737254902, + 0.18823529411764706, 0.737254902, 0.737254902, 0.737254902, 0.19215686274509805, 0.737254902, + 0.737254902, 0.737254902, 0.19607843137254902, 0.737254902, 0.737254902, 0.737254902, 0.2, + 0.737254902, 0.737254902, 0.737254902, 0.20392156862745098, 0.431372549, 0.0, 0.568627451, + 0.20784313725490197, 0.431372549, 0.0, 0.568627451, 0.21176470588235294, 0.431372549, 0.0, + 0.568627451, 0.21568627450980393, 0.431372549, 0.0, 0.568627451, 0.2196078431372549, + 0.431372549, 0.0, 0.568627451, 0.2235294117647059, 0.431372549, 0.0, 0.568627451, + 0.22745098039215686, 0.431372549, 0.0, 0.568627451, 0.23137254901960785, 0.431372549, 0.0, + 0.568627451, 0.23529411764705885, 0.431372549, 0.0, 0.568627451, 0.23921568627450984, + 0.431372549, 0.0, 0.568627451, 0.24313725490196078, 0.431372549, 0.0, 0.568627451, + 0.24705882352941178, 0.431372549, 0.0, 0.568627451, 0.25098039215686274, 0.431372549, 0.0, + 0.568627451, 0.2549019607843137, 0.431372549, 0.0, 0.568627451, 0.25882352941176473, + 0.431372549, 0.0, 0.568627451, 0.2627450980392157, 0.431372549, 0.0, 0.568627451, + 0.26666666666666666, 0.431372549, 0.0, 0.568627451, 0.27058823529411763, 0.431372549, 0.0, + 0.568627451, 0.27450980392156865, 0.431372549, 0.0, 0.568627451, 0.2784313725490196, + 0.431372549, 0.0, 0.568627451, 0.2823529411764706, 0.431372549, 0.0, 0.568627451, + 0.28627450980392155, 0.431372549, 0.0, 0.568627451, 0.2901960784313726, 0.431372549, 0.0, + 0.568627451, 0.29411764705882354, 0.431372549, 0.0, 0.568627451, 0.2980392156862745, + 0.431372549, 0.0, 0.568627451, 0.30196078431372547, 0.431372549, 0.0, 0.568627451, + 0.3058823529411765, 0.2509803922, 0.3333333333, 0.6509803922, 0.30980392156862746, + 0.2509803922, 0.3333333333, 0.6509803922, 0.3137254901960784, 0.2509803922, 0.3333333333, + 0.6509803922, 0.3176470588235294, 0.2509803922, 0.3333333333, 0.6509803922, + 0.3215686274509804, 0.2509803922, 0.3333333333, 0.6509803922, 0.3254901960784314, + 0.2509803922, 0.3333333333, 0.6509803922, 0.32941176470588235, 0.2509803922, 0.3333333333, + 0.6509803922, 0.3333333333333333, 0.2509803922, 0.3333333333, 0.6509803922, + 0.33725490196078434, 0.2509803922, 0.3333333333, 0.6509803922, 0.3411764705882353, + 0.2509803922, 0.3333333333, 0.6509803922, 0.34509803921568627, 0.2509803922, 0.3333333333, + 0.6509803922, 0.34901960784313724, 0.2509803922, 0.3333333333, 0.6509803922, + 0.35294117647058826, 0.2509803922, 0.3333333333, 0.6509803922, 0.3568627450980392, + 0.2509803922, 0.3333333333, 0.6509803922, 0.3607843137254902, 0.2509803922, 0.3333333333, + 0.6509803922, 0.36470588235294116, 0.2509803922, 0.3333333333, 0.6509803922, + 0.3686274509803922, 0.2509803922, 0.3333333333, 0.6509803922, 0.37254901960784315, + 0.2509803922, 0.3333333333, 0.6509803922, 0.3764705882352941, 0.2509803922, 0.3333333333, + 0.6509803922, 0.3803921568627451, 0.2509803922, 0.3333333333, 0.6509803922, + 0.3843137254901961, 0.2509803922, 0.3333333333, 0.6509803922, 0.38823529411764707, + 0.2509803922, 0.3333333333, 0.6509803922, 0.39215686274509803, 0.2509803922, 0.3333333333, + 0.6509803922, 0.396078431372549, 0.2509803922, 0.3333333333, 0.6509803922, 0.4, 0.2509803922, + 0.3333333333, 0.6509803922, 0.403921568627451, 0.2509803922, 0.3333333333, 0.6509803922, + 0.40784313725490196, 0.0, 0.8, 1.0, 0.4117647058823529, 0.0, 0.8, 1.0, 0.41568627450980394, + 0.0, 0.8, 1.0, 0.4196078431372549, 0.0, 0.8, 1.0, 0.4235294117647059, 0.0, 0.8, 1.0, + 0.42745098039215684, 0.0, 0.8, 1.0, 0.43137254901960786, 0.0, 0.8, 1.0, 0.43529411764705883, + 0.0, 0.8, 1.0, 0.4392156862745098, 0.0, 0.8, 1.0, 0.44313725490196076, 0.0, 0.8, 1.0, + 0.4470588235294118, 0.0, 0.8, 1.0, 0.45098039215686275, 0.0, 0.8, 1.0, 0.4549019607843137, + 0.0, 0.8, 1.0, 0.4588235294117647, 0.0, 0.8, 1.0, 0.4627450980392157, 0.0, 0.8, 1.0, + 0.4666666666666667, 0.0, 0.8, 1.0, 0.4705882352941177, 0.0, 0.8, 1.0, 0.4745098039215686, 0.0, + 0.8, 1.0, 0.4784313725490197, 0.0, 0.8, 1.0, 0.48235294117647065, 0.0, 0.8, 1.0, + 0.48627450980392156, 0.0, 0.8, 1.0, 0.49019607843137253, 0.0, 0.8, 1.0, 0.49411764705882355, + 0.0, 0.8, 1.0, 0.4980392156862745, 0.0, 0.8, 1.0, 0.5019607843137255, 0.0, 0.8, 1.0, + 0.5058823529411764, 0.0, 0.6666666667, 0.5333333333, 0.5098039215686274, 0.0, 0.6666666667, + 0.5333333333, 0.5137254901960784, 0.0, 0.6666666667, 0.5333333333, 0.5176470588235295, 0.0, + 0.6666666667, 0.5333333333, 0.5215686274509804, 0.0, 0.6666666667, 0.5333333333, + 0.5254901960784314, 0.0, 0.6666666667, 0.5333333333, 0.5294117647058824, 0.0, 0.6666666667, + 0.5333333333, 0.5333333333333333, 0.0, 0.6666666667, 0.5333333333, 0.5372549019607843, 0.0, + 0.6666666667, 0.5333333333, 0.5411764705882353, 0.0, 0.6666666667, 0.5333333333, + 0.5450980392156862, 0.0, 0.6666666667, 0.5333333333, 0.5490196078431373, 0.0, 0.6666666667, + 0.5333333333, 0.5529411764705883, 0.0, 0.6666666667, 0.5333333333, 0.5568627450980392, 0.0, + 0.6666666667, 0.5333333333, 0.5607843137254902, 0.0, 0.6666666667, 0.5333333333, + 0.5647058823529412, 0.0, 0.6666666667, 0.5333333333, 0.5686274509803921, 0.0, 0.6666666667, + 0.5333333333, 0.5725490196078431, 0.0, 0.6666666667, 0.5333333333, 0.5764705882352941, 0.0, + 0.6666666667, 0.5333333333, 0.5803921568627451, 0.0, 0.6666666667, 0.5333333333, + 0.5843137254901961, 0.0, 0.6666666667, 0.5333333333, 0.5882352941176471, 0.0, 0.6666666667, + 0.5333333333, 0.592156862745098, 0.0, 0.6666666667, 0.5333333333, 0.596078431372549, 0.0, + 0.6666666667, 0.5333333333, 0.6, 0.0, 0.6666666667, 0.5333333333, 0.6039215686274509, 0.0, + 0.6666666667, 0.5333333333, 0.6078431372549019, 0.4, 1.0, 0.4, 0.611764705882353, 0.4, 1.0, + 0.4, 0.615686274509804, 0.4, 1.0, 0.4, 0.6196078431372549, 0.4, 1.0, 0.4, 0.6235294117647059, + 0.4, 1.0, 0.4, 0.6274509803921569, 0.4, 1.0, 0.4, 0.6313725490196078, 0.4, 1.0, 0.4, + 0.6352941176470588, 0.4, 1.0, 0.4, 0.6392156862745098, 0.4, 1.0, 0.4, 0.6431372549019608, 0.4, + 1.0, 0.4, 0.6470588235294118, 0.4, 1.0, 0.4, 0.6509803921568628, 0.4, 1.0, 0.4, + 0.6549019607843137, 0.4, 1.0, 0.4, 0.6588235294117647, 0.4, 1.0, 0.4, 0.6627450980392157, 0.4, + 1.0, 0.4, 0.6666666666666666, 0.4, 1.0, 0.4, 0.6705882352941176, 0.4, 1.0, 0.4, + 0.6745098039215687, 0.4, 1.0, 0.4, 0.6784313725490196, 0.4, 1.0, 0.4, 0.6823529411764706, 0.4, + 1.0, 0.4, 0.6862745098039216, 0.4, 1.0, 0.4, 0.6901960784313725, 0.4, 1.0, 0.4, + 0.6941176470588235, 0.4, 1.0, 0.4, 0.6980392156862745, 0.4, 1.0, 0.4, 0.7019607843137254, 0.4, + 1.0, 0.4, 0.7058823529411765, 1.0, 0.9490196078, 0.0, 0.7098039215686275, 1.0, 0.9490196078, + 0.0, 0.7137254901960784, 1.0, 0.9490196078, 0.0, 0.7176470588235294, 1.0, 0.9490196078, 0.0, + 0.7215686274509804, 1.0, 0.9490196078, 0.0, 0.7254901960784313, 1.0, 0.9490196078, 0.0, + 0.7294117647058823, 1.0, 0.9490196078, 0.0, 0.7333333333333333, 1.0, 0.9490196078, 0.0, + 0.7372549019607844, 1.0, 0.9490196078, 0.0, 0.7411764705882353, 1.0, 0.9490196078, 0.0, + 0.7450980392156863, 1.0, 0.9490196078, 0.0, 0.7490196078431373, 1.0, 0.9490196078, 0.0, + 0.7529411764705882, 1.0, 0.9490196078, 0.0, 0.7568627450980392, 1.0, 0.9490196078, 0.0, + 0.7607843137254902, 1.0, 0.9490196078, 0.0, 0.7647058823529411, 1.0, 0.9490196078, 0.0, + 0.7686274509803922, 1.0, 0.9490196078, 0.0, 0.7725490196078432, 1.0, 0.9490196078, 0.0, + 0.7764705882352941, 1.0, 0.9490196078, 0.0, 0.7803921568627451, 1.0, 0.9490196078, 0.0, + 0.7843137254901961, 1.0, 0.9490196078, 0.0, 0.788235294117647, 1.0, 0.9490196078, 0.0, + 0.792156862745098, 1.0, 0.9490196078, 0.0, 0.796078431372549, 1.0, 0.9490196078, 0.0, 0.8, + 1.0, 0.9490196078, 0.0, 0.803921568627451, 1.0, 0.9490196078, 0.0, 0.807843137254902, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8117647058823529, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8156862745098039, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8196078431372549, 0.9490196078, 0.6509803922, 0.2509803922, 0.8235294117647058, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8274509803921568, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8313725490196079, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8352941176470589, 0.9490196078, 0.6509803922, 0.2509803922, 0.8392156862745098, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8431372549019608, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8470588235294118, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8509803921568627, 0.9490196078, 0.6509803922, 0.2509803922, 0.8549019607843137, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8588235294117647, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8627450980392157, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8666666666666667, 0.9490196078, 0.6509803922, 0.2509803922, 0.8705882352941177, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8745098039215686, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8784313725490196, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8823529411764706, 0.9490196078, 0.6509803922, 0.2509803922, 0.8862745098039215, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8901960784313725, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8941176470588236, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8980392156862745, 0.9490196078, 0.6509803922, 0.2509803922, 0.9019607843137255, + 0.9490196078, 0.6509803922, 0.2509803922, 0.9058823529411765, 0.9490196078, 0.6509803922, + 0.2509803922, 0.9098039215686274, 1.0, 0.0, 0.0, 0.9137254901960784, 1.0, 0.0, 0.0, + 0.9176470588235294, 1.0, 0.0, 0.0, 0.9215686274509803, 1.0, 0.0, 0.0, 0.9254901960784314, 1.0, + 0.0, 0.0, 0.9294117647058824, 1.0, 0.0, 0.0, 0.9333333333333333, 1.0, 0.0, 0.0, + 0.9372549019607843, 1.0, 0.0, 0.0, 0.9411764705882354, 1.0, 0.0, 0.0, 0.9450980392156864, 1.0, + 0.0, 0.0, 0.9490196078431372, 1.0, 0.0, 0.0, 0.9529411764705882, 1.0, 0.0, 0.0, + 0.9568627450980394, 1.0, 0.0, 0.0, 0.9607843137254903, 1.0, 0.0, 0.0, 0.9647058823529413, 1.0, + 0.0, 0.0, 0.9686274509803922, 1.0, 0.0, 0.0, 0.9725490196078431, 1.0, 0.0, 0.0, + 0.9764705882352941, 1.0, 0.0, 0.0, 0.9803921568627451, 1.0, 0.0, 0.0, 0.984313725490196, 1.0, + 0.0, 0.0, 0.9882352941176471, 1.0, 0.0, 0.0, 0.9921568627450981, 1.0, 0.0, 0.0, + 0.996078431372549, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, + ], + description: 'SUV', + }, + { + ColorSpace: 'RGB', + Name: 'ge_256', + name: 'ge_256', + RGBPoints: [ + 0.0, 0.0039215686, 0.0078431373, 0.0078431373, 0.00392156862745098, 0.0039215686, + 0.0078431373, 0.0078431373, 0.00784313725490196, 0.0039215686, 0.0078431373, 0.0117647059, + 0.011764705882352941, 0.0039215686, 0.0117647059, 0.0156862745, 0.01568627450980392, + 0.0039215686, 0.0117647059, 0.0196078431, 0.0196078431372549, 0.0039215686, 0.0156862745, + 0.0235294118, 0.023529411764705882, 0.0039215686, 0.0156862745, 0.0274509804, + 0.027450980392156862, 0.0039215686, 0.0196078431, 0.031372549, 0.03137254901960784, + 0.0039215686, 0.0196078431, 0.0352941176, 0.03529411764705882, 0.0039215686, 0.0235294118, + 0.0392156863, 0.0392156862745098, 0.0039215686, 0.0235294118, 0.0431372549, + 0.043137254901960784, 0.0039215686, 0.0274509804, 0.0470588235, 0.047058823529411764, + 0.0039215686, 0.0274509804, 0.0509803922, 0.050980392156862744, 0.0039215686, 0.031372549, + 0.0549019608, 0.054901960784313725, 0.0039215686, 0.031372549, 0.0588235294, + 0.05882352941176471, 0.0039215686, 0.0352941176, 0.062745098, 0.06274509803921569, + 0.0039215686, 0.0352941176, 0.0666666667, 0.06666666666666667, 0.0039215686, 0.0392156863, + 0.0705882353, 0.07058823529411765, 0.0039215686, 0.0392156863, 0.0745098039, + 0.07450980392156863, 0.0039215686, 0.0431372549, 0.0784313725, 0.0784313725490196, + 0.0039215686, 0.0431372549, 0.0823529412, 0.08235294117647059, 0.0039215686, 0.0470588235, + 0.0862745098, 0.08627450980392157, 0.0039215686, 0.0470588235, 0.0901960784, + 0.09019607843137255, 0.0039215686, 0.0509803922, 0.0941176471, 0.09411764705882353, + 0.0039215686, 0.0509803922, 0.0980392157, 0.09803921568627451, 0.0039215686, 0.0549019608, + 0.1019607843, 0.10196078431372549, 0.0039215686, 0.0549019608, 0.1058823529, + 0.10588235294117647, 0.0039215686, 0.0588235294, 0.1098039216, 0.10980392156862745, + 0.0039215686, 0.0588235294, 0.1137254902, 0.11372549019607843, 0.0039215686, 0.062745098, + 0.1176470588, 0.11764705882352942, 0.0039215686, 0.062745098, 0.1215686275, + 0.12156862745098039, 0.0039215686, 0.0666666667, 0.1254901961, 0.12549019607843137, + 0.0039215686, 0.0666666667, 0.1294117647, 0.12941176470588237, 0.0039215686, 0.0705882353, + 0.1333333333, 0.13333333333333333, 0.0039215686, 0.0705882353, 0.137254902, + 0.13725490196078433, 0.0039215686, 0.0745098039, 0.1411764706, 0.1411764705882353, + 0.0039215686, 0.0745098039, 0.1450980392, 0.1450980392156863, 0.0039215686, 0.0784313725, + 0.1490196078, 0.14901960784313725, 0.0039215686, 0.0784313725, 0.1529411765, + 0.15294117647058825, 0.0039215686, 0.0823529412, 0.1568627451, 0.1568627450980392, + 0.0039215686, 0.0823529412, 0.1607843137, 0.1607843137254902, 0.0039215686, 0.0862745098, + 0.1647058824, 0.16470588235294117, 0.0039215686, 0.0862745098, 0.168627451, + 0.16862745098039217, 0.0039215686, 0.0901960784, 0.1725490196, 0.17254901960784313, + 0.0039215686, 0.0901960784, 0.1764705882, 0.17647058823529413, 0.0039215686, 0.0941176471, + 0.1803921569, 0.1803921568627451, 0.0039215686, 0.0941176471, 0.1843137255, + 0.1843137254901961, 0.0039215686, 0.0980392157, 0.1882352941, 0.18823529411764706, + 0.0039215686, 0.0980392157, 0.1921568627, 0.19215686274509805, 0.0039215686, 0.1019607843, + 0.1960784314, 0.19607843137254902, 0.0039215686, 0.1019607843, 0.2, 0.2, 0.0039215686, + 0.1058823529, 0.2039215686, 0.20392156862745098, 0.0039215686, 0.1058823529, 0.2078431373, + 0.20784313725490197, 0.0039215686, 0.1098039216, 0.2117647059, 0.21176470588235294, + 0.0039215686, 0.1098039216, 0.2156862745, 0.21568627450980393, 0.0039215686, 0.1137254902, + 0.2196078431, 0.2196078431372549, 0.0039215686, 0.1137254902, 0.2235294118, + 0.2235294117647059, 0.0039215686, 0.1176470588, 0.2274509804, 0.22745098039215686, + 0.0039215686, 0.1176470588, 0.231372549, 0.23137254901960785, 0.0039215686, 0.1215686275, + 0.2352941176, 0.23529411764705885, 0.0039215686, 0.1215686275, 0.2392156863, + 0.23921568627450984, 0.0039215686, 0.1254901961, 0.2431372549, 0.24313725490196078, + 0.0039215686, 0.1254901961, 0.2470588235, 0.24705882352941178, 0.0039215686, 0.1294117647, + 0.2509803922, 0.25098039215686274, 0.0039215686, 0.1294117647, 0.2509803922, + 0.2549019607843137, 0.0078431373, 0.1254901961, 0.2549019608, 0.25882352941176473, + 0.0156862745, 0.1254901961, 0.2588235294, 0.2627450980392157, 0.0235294118, 0.1215686275, + 0.262745098, 0.26666666666666666, 0.031372549, 0.1215686275, 0.2666666667, + 0.27058823529411763, 0.0392156863, 0.1176470588, 0.2705882353, 0.27450980392156865, + 0.0470588235, 0.1176470588, 0.2745098039, 0.2784313725490196, 0.0549019608, 0.1137254902, + 0.2784313725, 0.2823529411764706, 0.062745098, 0.1137254902, 0.2823529412, + 0.28627450980392155, 0.0705882353, 0.1098039216, 0.2862745098, 0.2901960784313726, + 0.0784313725, 0.1098039216, 0.2901960784, 0.29411764705882354, 0.0862745098, 0.1058823529, + 0.2941176471, 0.2980392156862745, 0.0941176471, 0.1058823529, 0.2980392157, + 0.30196078431372547, 0.1019607843, 0.1019607843, 0.3019607843, 0.3058823529411765, + 0.1098039216, 0.1019607843, 0.3058823529, 0.30980392156862746, 0.1176470588, 0.0980392157, + 0.3098039216, 0.3137254901960784, 0.1254901961, 0.0980392157, 0.3137254902, + 0.3176470588235294, 0.1333333333, 0.0941176471, 0.3176470588, 0.3215686274509804, + 0.1411764706, 0.0941176471, 0.3215686275, 0.3254901960784314, 0.1490196078, 0.0901960784, + 0.3254901961, 0.32941176470588235, 0.1568627451, 0.0901960784, 0.3294117647, + 0.3333333333333333, 0.1647058824, 0.0862745098, 0.3333333333, 0.33725490196078434, + 0.1725490196, 0.0862745098, 0.337254902, 0.3411764705882353, 0.1803921569, 0.0823529412, + 0.3411764706, 0.34509803921568627, 0.1882352941, 0.0823529412, 0.3450980392, + 0.34901960784313724, 0.1960784314, 0.0784313725, 0.3490196078, 0.35294117647058826, + 0.2039215686, 0.0784313725, 0.3529411765, 0.3568627450980392, 0.2117647059, 0.0745098039, + 0.3568627451, 0.3607843137254902, 0.2196078431, 0.0745098039, 0.3607843137, + 0.36470588235294116, 0.2274509804, 0.0705882353, 0.3647058824, 0.3686274509803922, + 0.2352941176, 0.0705882353, 0.368627451, 0.37254901960784315, 0.2431372549, 0.0666666667, + 0.3725490196, 0.3764705882352941, 0.2509803922, 0.0666666667, 0.3764705882, + 0.3803921568627451, 0.2549019608, 0.062745098, 0.3803921569, 0.3843137254901961, 0.262745098, + 0.062745098, 0.3843137255, 0.38823529411764707, 0.2705882353, 0.0588235294, 0.3882352941, + 0.39215686274509803, 0.2784313725, 0.0588235294, 0.3921568627, 0.396078431372549, + 0.2862745098, 0.0549019608, 0.3960784314, 0.4, 0.2941176471, 0.0549019608, 0.4, + 0.403921568627451, 0.3019607843, 0.0509803922, 0.4039215686, 0.40784313725490196, + 0.3098039216, 0.0509803922, 0.4078431373, 0.4117647058823529, 0.3176470588, 0.0470588235, + 0.4117647059, 0.41568627450980394, 0.3254901961, 0.0470588235, 0.4156862745, + 0.4196078431372549, 0.3333333333, 0.0431372549, 0.4196078431, 0.4235294117647059, + 0.3411764706, 0.0431372549, 0.4235294118, 0.42745098039215684, 0.3490196078, 0.0392156863, + 0.4274509804, 0.43137254901960786, 0.3568627451, 0.0392156863, 0.431372549, + 0.43529411764705883, 0.3647058824, 0.0352941176, 0.4352941176, 0.4392156862745098, + 0.3725490196, 0.0352941176, 0.4392156863, 0.44313725490196076, 0.3803921569, 0.031372549, + 0.4431372549, 0.4470588235294118, 0.3882352941, 0.031372549, 0.4470588235, + 0.45098039215686275, 0.3960784314, 0.0274509804, 0.4509803922, 0.4549019607843137, + 0.4039215686, 0.0274509804, 0.4549019608, 0.4588235294117647, 0.4117647059, 0.0235294118, + 0.4588235294, 0.4627450980392157, 0.4196078431, 0.0235294118, 0.462745098, 0.4666666666666667, + 0.4274509804, 0.0196078431, 0.4666666667, 0.4705882352941177, 0.4352941176, 0.0196078431, + 0.4705882353, 0.4745098039215686, 0.4431372549, 0.0156862745, 0.4745098039, + 0.4784313725490197, 0.4509803922, 0.0156862745, 0.4784313725, 0.48235294117647065, + 0.4588235294, 0.0117647059, 0.4823529412, 0.48627450980392156, 0.4666666667, 0.0117647059, + 0.4862745098, 0.49019607843137253, 0.4745098039, 0.0078431373, 0.4901960784, + 0.49411764705882355, 0.4823529412, 0.0078431373, 0.4941176471, 0.4980392156862745, + 0.4901960784, 0.0039215686, 0.4980392157, 0.5019607843137255, 0.4980392157, 0.0117647059, + 0.4980392157, 0.5058823529411764, 0.5058823529, 0.0156862745, 0.4901960784, + 0.5098039215686274, 0.5137254902, 0.0235294118, 0.4823529412, 0.5137254901960784, + 0.5215686275, 0.0274509804, 0.4745098039, 0.5176470588235295, 0.5294117647, 0.0352941176, + 0.4666666667, 0.5215686274509804, 0.537254902, 0.0392156863, 0.4588235294, 0.5254901960784314, + 0.5450980392, 0.0470588235, 0.4509803922, 0.5294117647058824, 0.5529411765, 0.0509803922, + 0.4431372549, 0.5333333333333333, 0.5607843137, 0.0588235294, 0.4352941176, + 0.5372549019607843, 0.568627451, 0.062745098, 0.4274509804, 0.5411764705882353, 0.5764705882, + 0.0705882353, 0.4196078431, 0.5450980392156862, 0.5843137255, 0.0745098039, 0.4117647059, + 0.5490196078431373, 0.5921568627, 0.0823529412, 0.4039215686, 0.5529411764705883, 0.6, + 0.0862745098, 0.3960784314, 0.5568627450980392, 0.6078431373, 0.0941176471, 0.3882352941, + 0.5607843137254902, 0.6156862745, 0.0980392157, 0.3803921569, 0.5647058823529412, + 0.6235294118, 0.1058823529, 0.3725490196, 0.5686274509803921, 0.631372549, 0.1098039216, + 0.3647058824, 0.5725490196078431, 0.6392156863, 0.1176470588, 0.3568627451, + 0.5764705882352941, 0.6470588235, 0.1215686275, 0.3490196078, 0.5803921568627451, + 0.6549019608, 0.1294117647, 0.3411764706, 0.5843137254901961, 0.662745098, 0.1333333333, + 0.3333333333, 0.5882352941176471, 0.6705882353, 0.1411764706, 0.3254901961, 0.592156862745098, + 0.6784313725, 0.1450980392, 0.3176470588, 0.596078431372549, 0.6862745098, 0.1529411765, + 0.3098039216, 0.6, 0.6941176471, 0.1568627451, 0.3019607843, 0.6039215686274509, 0.7019607843, + 0.1647058824, 0.2941176471, 0.6078431372549019, 0.7098039216, 0.168627451, 0.2862745098, + 0.611764705882353, 0.7176470588, 0.1764705882, 0.2784313725, 0.615686274509804, 0.7254901961, + 0.1803921569, 0.2705882353, 0.6196078431372549, 0.7333333333, 0.1882352941, 0.262745098, + 0.6235294117647059, 0.7411764706, 0.1921568627, 0.2549019608, 0.6274509803921569, + 0.7490196078, 0.2, 0.2509803922, 0.6313725490196078, 0.7529411765, 0.2039215686, 0.2431372549, + 0.6352941176470588, 0.7607843137, 0.2117647059, 0.2352941176, 0.6392156862745098, 0.768627451, + 0.2156862745, 0.2274509804, 0.6431372549019608, 0.7764705882, 0.2235294118, 0.2196078431, + 0.6470588235294118, 0.7843137255, 0.2274509804, 0.2117647059, 0.6509803921568628, + 0.7921568627, 0.2352941176, 0.2039215686, 0.6549019607843137, 0.8, 0.2392156863, 0.1960784314, + 0.6588235294117647, 0.8078431373, 0.2470588235, 0.1882352941, 0.6627450980392157, + 0.8156862745, 0.2509803922, 0.1803921569, 0.6666666666666666, 0.8235294118, 0.2549019608, + 0.1725490196, 0.6705882352941176, 0.831372549, 0.2588235294, 0.1647058824, 0.6745098039215687, + 0.8392156863, 0.2666666667, 0.1568627451, 0.6784313725490196, 0.8470588235, 0.2705882353, + 0.1490196078, 0.6823529411764706, 0.8549019608, 0.2784313725, 0.1411764706, + 0.6862745098039216, 0.862745098, 0.2823529412, 0.1333333333, 0.6901960784313725, 0.8705882353, + 0.2901960784, 0.1254901961, 0.6941176470588235, 0.8784313725, 0.2941176471, 0.1176470588, + 0.6980392156862745, 0.8862745098, 0.3019607843, 0.1098039216, 0.7019607843137254, + 0.8941176471, 0.3058823529, 0.1019607843, 0.7058823529411765, 0.9019607843, 0.3137254902, + 0.0941176471, 0.7098039215686275, 0.9098039216, 0.3176470588, 0.0862745098, + 0.7137254901960784, 0.9176470588, 0.3254901961, 0.0784313725, 0.7176470588235294, + 0.9254901961, 0.3294117647, 0.0705882353, 0.7215686274509804, 0.9333333333, 0.337254902, + 0.062745098, 0.7254901960784313, 0.9411764706, 0.3411764706, 0.0549019608, 0.7294117647058823, + 0.9490196078, 0.3490196078, 0.0470588235, 0.7333333333333333, 0.9568627451, 0.3529411765, + 0.0392156863, 0.7372549019607844, 0.9647058824, 0.3607843137, 0.031372549, 0.7411764705882353, + 0.9725490196, 0.3647058824, 0.0235294118, 0.7450980392156863, 0.9803921569, 0.3725490196, + 0.0156862745, 0.7490196078431373, 0.9882352941, 0.3725490196, 0.0039215686, + 0.7529411764705882, 0.9960784314, 0.3843137255, 0.0156862745, 0.7568627450980392, + 0.9960784314, 0.3921568627, 0.031372549, 0.7607843137254902, 0.9960784314, 0.4039215686, + 0.0470588235, 0.7647058823529411, 0.9960784314, 0.4117647059, 0.062745098, 0.7686274509803922, + 0.9960784314, 0.4235294118, 0.0784313725, 0.7725490196078432, 0.9960784314, 0.431372549, + 0.0941176471, 0.7764705882352941, 0.9960784314, 0.4431372549, 0.1098039216, + 0.7803921568627451, 0.9960784314, 0.4509803922, 0.1254901961, 0.7843137254901961, + 0.9960784314, 0.462745098, 0.1411764706, 0.788235294117647, 0.9960784314, 0.4705882353, + 0.1568627451, 0.792156862745098, 0.9960784314, 0.4823529412, 0.1725490196, 0.796078431372549, + 0.9960784314, 0.4901960784, 0.1882352941, 0.8, 0.9960784314, 0.5019607843, 0.2039215686, + 0.803921568627451, 0.9960784314, 0.5098039216, 0.2196078431, 0.807843137254902, 0.9960784314, + 0.5215686275, 0.2352941176, 0.8117647058823529, 0.9960784314, 0.5294117647, 0.2509803922, + 0.8156862745098039, 0.9960784314, 0.5411764706, 0.262745098, 0.8196078431372549, 0.9960784314, + 0.5490196078, 0.2784313725, 0.8235294117647058, 0.9960784314, 0.5607843137, 0.2941176471, + 0.8274509803921568, 0.9960784314, 0.568627451, 0.3098039216, 0.8313725490196079, 0.9960784314, + 0.5803921569, 0.3254901961, 0.8352941176470589, 0.9960784314, 0.5882352941, 0.3411764706, + 0.8392156862745098, 0.9960784314, 0.6, 0.3568627451, 0.8431372549019608, 0.9960784314, + 0.6078431373, 0.3725490196, 0.8470588235294118, 0.9960784314, 0.6196078431, 0.3882352941, + 0.8509803921568627, 0.9960784314, 0.6274509804, 0.4039215686, 0.8549019607843137, + 0.9960784314, 0.6392156863, 0.4196078431, 0.8588235294117647, 0.9960784314, 0.6470588235, + 0.4352941176, 0.8627450980392157, 0.9960784314, 0.6588235294, 0.4509803922, + 0.8666666666666667, 0.9960784314, 0.6666666667, 0.4666666667, 0.8705882352941177, + 0.9960784314, 0.6784313725, 0.4823529412, 0.8745098039215686, 0.9960784314, 0.6862745098, + 0.4980392157, 0.8784313725490196, 0.9960784314, 0.6980392157, 0.5137254902, + 0.8823529411764706, 0.9960784314, 0.7058823529, 0.5294117647, 0.8862745098039215, + 0.9960784314, 0.7176470588, 0.5450980392, 0.8901960784313725, 0.9960784314, 0.7254901961, + 0.5607843137, 0.8941176470588236, 0.9960784314, 0.737254902, 0.5764705882, 0.8980392156862745, + 0.9960784314, 0.7450980392, 0.5921568627, 0.9019607843137255, 0.9960784314, 0.7529411765, + 0.6078431373, 0.9058823529411765, 0.9960784314, 0.7607843137, 0.6235294118, + 0.9098039215686274, 0.9960784314, 0.7725490196, 0.6392156863, 0.9137254901960784, + 0.9960784314, 0.7803921569, 0.6549019608, 0.9176470588235294, 0.9960784314, 0.7921568627, + 0.6705882353, 0.9215686274509803, 0.9960784314, 0.8, 0.6862745098, 0.9254901960784314, + 0.9960784314, 0.8117647059, 0.7019607843, 0.9294117647058824, 0.9960784314, 0.8196078431, + 0.7176470588, 0.9333333333333333, 0.9960784314, 0.831372549, 0.7333333333, 0.9372549019607843, + 0.9960784314, 0.8392156863, 0.7490196078, 0.9411764705882354, 0.9960784314, 0.8509803922, + 0.7607843137, 0.9450980392156864, 0.9960784314, 0.8588235294, 0.7764705882, + 0.9490196078431372, 0.9960784314, 0.8705882353, 0.7921568627, 0.9529411764705882, + 0.9960784314, 0.8784313725, 0.8078431373, 0.9568627450980394, 0.9960784314, 0.8901960784, + 0.8235294118, 0.9607843137254903, 0.9960784314, 0.8980392157, 0.8392156863, + 0.9647058823529413, 0.9960784314, 0.9098039216, 0.8549019608, 0.9686274509803922, + 0.9960784314, 0.9176470588, 0.8705882353, 0.9725490196078431, 0.9960784314, 0.9294117647, + 0.8862745098, 0.9764705882352941, 0.9960784314, 0.937254902, 0.9019607843, 0.9803921568627451, + 0.9960784314, 0.9490196078, 0.9176470588, 0.984313725490196, 0.9960784314, 0.9568627451, + 0.9333333333, 0.9882352941176471, 0.9960784314, 0.968627451, 0.9490196078, 0.9921568627450981, + 0.9960784314, 0.9764705882, 0.9647058824, 0.996078431372549, 0.9960784314, 0.9882352941, + 0.9803921569, 1.0, 0.9960784314, 0.9882352941, 0.9803921569, + ], + description: 'GE 256', + }, + { + ColorSpace: 'RGB', + Name: 'ge', + name: 'ge', + RGBPoints: [ + 0.0, 0.0078431373, 0.0078431373, 0.0078431373, 0.00392156862745098, 0.0078431373, + 0.0078431373, 0.0078431373, 0.00784313725490196, 0.0078431373, 0.0078431373, 0.0078431373, + 0.011764705882352941, 0.0078431373, 0.0078431373, 0.0078431373, 0.01568627450980392, + 0.0078431373, 0.0078431373, 0.0078431373, 0.0196078431372549, 0.0078431373, 0.0078431373, + 0.0078431373, 0.023529411764705882, 0.0078431373, 0.0078431373, 0.0078431373, + 0.027450980392156862, 0.0078431373, 0.0078431373, 0.0078431373, 0.03137254901960784, + 0.0078431373, 0.0078431373, 0.0078431373, 0.03529411764705882, 0.0078431373, 0.0078431373, + 0.0078431373, 0.0392156862745098, 0.0078431373, 0.0078431373, 0.0078431373, + 0.043137254901960784, 0.0078431373, 0.0078431373, 0.0078431373, 0.047058823529411764, + 0.0078431373, 0.0078431373, 0.0078431373, 0.050980392156862744, 0.0078431373, 0.0078431373, + 0.0078431373, 0.054901960784313725, 0.0078431373, 0.0078431373, 0.0078431373, + 0.05882352941176471, 0.0117647059, 0.0078431373, 0.0078431373, 0.06274509803921569, + 0.0078431373, 0.0156862745, 0.0156862745, 0.06666666666666667, 0.0078431373, 0.0235294118, + 0.0235294118, 0.07058823529411765, 0.0078431373, 0.031372549, 0.031372549, + 0.07450980392156863, 0.0078431373, 0.0392156863, 0.0392156863, 0.0784313725490196, + 0.0078431373, 0.0470588235, 0.0470588235, 0.08235294117647059, 0.0078431373, 0.0549019608, + 0.0549019608, 0.08627450980392157, 0.0078431373, 0.062745098, 0.062745098, + 0.09019607843137255, 0.0078431373, 0.0705882353, 0.0705882353, 0.09411764705882353, + 0.0078431373, 0.0784313725, 0.0784313725, 0.09803921568627451, 0.0078431373, 0.0901960784, + 0.0862745098, 0.10196078431372549, 0.0078431373, 0.0980392157, 0.0941176471, + 0.10588235294117647, 0.0078431373, 0.1058823529, 0.1019607843, 0.10980392156862745, + 0.0078431373, 0.1137254902, 0.1098039216, 0.11372549019607843, 0.0078431373, 0.1215686275, + 0.1176470588, 0.11764705882352942, 0.0078431373, 0.1294117647, 0.1254901961, + 0.12156862745098039, 0.0078431373, 0.137254902, 0.1333333333, 0.12549019607843137, + 0.0078431373, 0.1450980392, 0.1411764706, 0.12941176470588237, 0.0078431373, 0.1529411765, + 0.1490196078, 0.13333333333333333, 0.0078431373, 0.1647058824, 0.1568627451, + 0.13725490196078433, 0.0078431373, 0.1725490196, 0.1647058824, 0.1411764705882353, + 0.0078431373, 0.1803921569, 0.1725490196, 0.1450980392156863, 0.0078431373, 0.1882352941, + 0.1803921569, 0.14901960784313725, 0.0078431373, 0.1960784314, 0.1882352941, + 0.15294117647058825, 0.0078431373, 0.2039215686, 0.1960784314, 0.1568627450980392, + 0.0078431373, 0.2117647059, 0.2039215686, 0.1607843137254902, 0.0078431373, 0.2196078431, + 0.2117647059, 0.16470588235294117, 0.0078431373, 0.2274509804, 0.2196078431, + 0.16862745098039217, 0.0078431373, 0.2352941176, 0.2274509804, 0.17254901960784313, + 0.0078431373, 0.2470588235, 0.2352941176, 0.17647058823529413, 0.0078431373, 0.2509803922, + 0.2431372549, 0.1803921568627451, 0.0078431373, 0.2549019608, 0.2509803922, + 0.1843137254901961, 0.0078431373, 0.262745098, 0.2509803922, 0.18823529411764706, + 0.0078431373, 0.2705882353, 0.2588235294, 0.19215686274509805, 0.0078431373, 0.2784313725, + 0.2666666667, 0.19607843137254902, 0.0078431373, 0.2862745098, 0.2745098039, 0.2, + 0.0078431373, 0.2941176471, 0.2823529412, 0.20392156862745098, 0.0078431373, 0.3019607843, + 0.2901960784, 0.20784313725490197, 0.0078431373, 0.3137254902, 0.2980392157, + 0.21176470588235294, 0.0078431373, 0.3215686275, 0.3058823529, 0.21568627450980393, + 0.0078431373, 0.3294117647, 0.3137254902, 0.2196078431372549, 0.0078431373, 0.337254902, + 0.3215686275, 0.2235294117647059, 0.0078431373, 0.3450980392, 0.3294117647, + 0.22745098039215686, 0.0078431373, 0.3529411765, 0.337254902, 0.23137254901960785, + 0.0078431373, 0.3607843137, 0.3450980392, 0.23529411764705885, 0.0078431373, 0.368627451, + 0.3529411765, 0.23921568627450984, 0.0078431373, 0.3764705882, 0.3607843137, + 0.24313725490196078, 0.0078431373, 0.3843137255, 0.368627451, 0.24705882352941178, + 0.0078431373, 0.3960784314, 0.3764705882, 0.25098039215686274, 0.0078431373, 0.4039215686, + 0.3843137255, 0.2549019607843137, 0.0078431373, 0.4117647059, 0.3921568627, + 0.25882352941176473, 0.0078431373, 0.4196078431, 0.4, 0.2627450980392157, 0.0078431373, + 0.4274509804, 0.4078431373, 0.26666666666666666, 0.0078431373, 0.4352941176, 0.4156862745, + 0.27058823529411763, 0.0078431373, 0.4431372549, 0.4235294118, 0.27450980392156865, + 0.0078431373, 0.4509803922, 0.431372549, 0.2784313725490196, 0.0078431373, 0.4588235294, + 0.4392156863, 0.2823529411764706, 0.0078431373, 0.4705882353, 0.4470588235, + 0.28627450980392155, 0.0078431373, 0.4784313725, 0.4549019608, 0.2901960784313726, + 0.0078431373, 0.4862745098, 0.462745098, 0.29411764705882354, 0.0078431373, 0.4941176471, + 0.4705882353, 0.2980392156862745, 0.0078431373, 0.5019607843, 0.4784313725, + 0.30196078431372547, 0.0117647059, 0.5098039216, 0.4862745098, 0.3058823529411765, + 0.0196078431, 0.5019607843, 0.4941176471, 0.30980392156862746, 0.0274509804, 0.4941176471, + 0.5058823529, 0.3137254901960784, 0.0352941176, 0.4862745098, 0.5137254902, + 0.3176470588235294, 0.0431372549, 0.4784313725, 0.5215686275, 0.3215686274509804, + 0.0509803922, 0.4705882353, 0.5294117647, 0.3254901960784314, 0.0588235294, 0.462745098, + 0.537254902, 0.32941176470588235, 0.0666666667, 0.4549019608, 0.5450980392, + 0.3333333333333333, 0.0745098039, 0.4470588235, 0.5529411765, 0.33725490196078434, + 0.0823529412, 0.4392156863, 0.5607843137, 0.3411764705882353, 0.0901960784, 0.431372549, + 0.568627451, 0.34509803921568627, 0.0980392157, 0.4235294118, 0.5764705882, + 0.34901960784313724, 0.1058823529, 0.4156862745, 0.5843137255, 0.35294117647058826, + 0.1137254902, 0.4078431373, 0.5921568627, 0.3568627450980392, 0.1215686275, 0.4, 0.6, + 0.3607843137254902, 0.1294117647, 0.3921568627, 0.6078431373, 0.36470588235294116, + 0.137254902, 0.3843137255, 0.6156862745, 0.3686274509803922, 0.1450980392, 0.3764705882, + 0.6235294118, 0.37254901960784315, 0.1529411765, 0.368627451, 0.631372549, 0.3764705882352941, + 0.1607843137, 0.3607843137, 0.6392156863, 0.3803921568627451, 0.168627451, 0.3529411765, + 0.6470588235, 0.3843137254901961, 0.1764705882, 0.3450980392, 0.6549019608, + 0.38823529411764707, 0.1843137255, 0.337254902, 0.662745098, 0.39215686274509803, + 0.1921568627, 0.3294117647, 0.6705882353, 0.396078431372549, 0.2, 0.3215686275, 0.6784313725, + 0.4, 0.2078431373, 0.3137254902, 0.6862745098, 0.403921568627451, 0.2156862745, 0.3058823529, + 0.6941176471, 0.40784313725490196, 0.2235294118, 0.2980392157, 0.7019607843, + 0.4117647058823529, 0.231372549, 0.2901960784, 0.7098039216, 0.41568627450980394, + 0.2392156863, 0.2823529412, 0.7176470588, 0.4196078431372549, 0.2470588235, 0.2745098039, + 0.7254901961, 0.4235294117647059, 0.2509803922, 0.2666666667, 0.7333333333, + 0.42745098039215684, 0.2509803922, 0.2588235294, 0.7411764706, 0.43137254901960786, + 0.2588235294, 0.2509803922, 0.7490196078, 0.43529411764705883, 0.2666666667, 0.2509803922, + 0.7490196078, 0.4392156862745098, 0.2745098039, 0.2431372549, 0.7568627451, + 0.44313725490196076, 0.2823529412, 0.2352941176, 0.7647058824, 0.4470588235294118, + 0.2901960784, 0.2274509804, 0.7725490196, 0.45098039215686275, 0.2980392157, 0.2196078431, + 0.7803921569, 0.4549019607843137, 0.3058823529, 0.2117647059, 0.7882352941, + 0.4588235294117647, 0.3137254902, 0.2039215686, 0.7960784314, 0.4627450980392157, + 0.3215686275, 0.1960784314, 0.8039215686, 0.4666666666666667, 0.3294117647, 0.1882352941, + 0.8117647059, 0.4705882352941177, 0.337254902, 0.1803921569, 0.8196078431, 0.4745098039215686, + 0.3450980392, 0.1725490196, 0.8274509804, 0.4784313725490197, 0.3529411765, 0.1647058824, + 0.8352941176, 0.48235294117647065, 0.3607843137, 0.1568627451, 0.8431372549, + 0.48627450980392156, 0.368627451, 0.1490196078, 0.8509803922, 0.49019607843137253, + 0.3764705882, 0.1411764706, 0.8588235294, 0.49411764705882355, 0.3843137255, 0.1333333333, + 0.8666666667, 0.4980392156862745, 0.3921568627, 0.1254901961, 0.8745098039, + 0.5019607843137255, 0.4, 0.1176470588, 0.8823529412, 0.5058823529411764, 0.4078431373, + 0.1098039216, 0.8901960784, 0.5098039215686274, 0.4156862745, 0.1019607843, 0.8980392157, + 0.5137254901960784, 0.4235294118, 0.0941176471, 0.9058823529, 0.5176470588235295, 0.431372549, + 0.0862745098, 0.9137254902, 0.5215686274509804, 0.4392156863, 0.0784313725, 0.9215686275, + 0.5254901960784314, 0.4470588235, 0.0705882353, 0.9294117647, 0.5294117647058824, + 0.4549019608, 0.062745098, 0.937254902, 0.5333333333333333, 0.462745098, 0.0549019608, + 0.9450980392, 0.5372549019607843, 0.4705882353, 0.0470588235, 0.9529411765, + 0.5411764705882353, 0.4784313725, 0.0392156863, 0.9607843137, 0.5450980392156862, + 0.4862745098, 0.031372549, 0.968627451, 0.5490196078431373, 0.4941176471, 0.0235294118, + 0.9764705882, 0.5529411764705883, 0.4980392157, 0.0156862745, 0.9843137255, + 0.5568627450980392, 0.5058823529, 0.0078431373, 0.9921568627, 0.5607843137254902, + 0.5137254902, 0.0156862745, 0.9803921569, 0.5647058823529412, 0.5215686275, 0.0235294118, + 0.9647058824, 0.5686274509803921, 0.5294117647, 0.0352941176, 0.9490196078, + 0.5725490196078431, 0.537254902, 0.0431372549, 0.9333333333, 0.5764705882352941, 0.5450980392, + 0.0509803922, 0.9176470588, 0.5803921568627451, 0.5529411765, 0.062745098, 0.9019607843, + 0.5843137254901961, 0.5607843137, 0.0705882353, 0.8862745098, 0.5882352941176471, 0.568627451, + 0.0784313725, 0.8705882353, 0.592156862745098, 0.5764705882, 0.0901960784, 0.8549019608, + 0.596078431372549, 0.5843137255, 0.0980392157, 0.8392156863, 0.6, 0.5921568627, 0.1098039216, + 0.8235294118, 0.6039215686274509, 0.6, 0.1176470588, 0.8078431373, 0.6078431372549019, + 0.6078431373, 0.1254901961, 0.7921568627, 0.611764705882353, 0.6156862745, 0.137254902, + 0.7764705882, 0.615686274509804, 0.6235294118, 0.1450980392, 0.7607843137, 0.6196078431372549, + 0.631372549, 0.1529411765, 0.7490196078, 0.6235294117647059, 0.6392156863, 0.1647058824, + 0.737254902, 0.6274509803921569, 0.6470588235, 0.1725490196, 0.7215686275, 0.6313725490196078, + 0.6549019608, 0.1843137255, 0.7058823529, 0.6352941176470588, 0.662745098, 0.1921568627, + 0.6901960784, 0.6392156862745098, 0.6705882353, 0.2, 0.6745098039, 0.6431372549019608, + 0.6784313725, 0.2117647059, 0.6588235294, 0.6470588235294118, 0.6862745098, 0.2196078431, + 0.6431372549, 0.6509803921568628, 0.6941176471, 0.2274509804, 0.6274509804, + 0.6549019607843137, 0.7019607843, 0.2392156863, 0.6117647059, 0.6588235294117647, + 0.7098039216, 0.2470588235, 0.5960784314, 0.6627450980392157, 0.7176470588, 0.2509803922, + 0.5803921569, 0.6666666666666666, 0.7254901961, 0.2588235294, 0.5647058824, + 0.6705882352941176, 0.7333333333, 0.2666666667, 0.5490196078, 0.6745098039215687, + 0.7411764706, 0.2784313725, 0.5333333333, 0.6784313725490196, 0.7490196078, 0.2862745098, + 0.5176470588, 0.6823529411764706, 0.7490196078, 0.2941176471, 0.5019607843, + 0.6862745098039216, 0.7529411765, 0.3058823529, 0.4862745098, 0.6901960784313725, + 0.7607843137, 0.3137254902, 0.4705882353, 0.6941176470588235, 0.768627451, 0.3215686275, + 0.4549019608, 0.6980392156862745, 0.7764705882, 0.3333333333, 0.4392156863, + 0.7019607843137254, 0.7843137255, 0.3411764706, 0.4235294118, 0.7058823529411765, + 0.7921568627, 0.3529411765, 0.4078431373, 0.7098039215686275, 0.8, 0.3607843137, 0.3921568627, + 0.7137254901960784, 0.8078431373, 0.368627451, 0.3764705882, 0.7176470588235294, 0.8156862745, + 0.3803921569, 0.3607843137, 0.7215686274509804, 0.8235294118, 0.3882352941, 0.3450980392, + 0.7254901960784313, 0.831372549, 0.3960784314, 0.3294117647, 0.7294117647058823, 0.8392156863, + 0.4078431373, 0.3137254902, 0.7333333333333333, 0.8470588235, 0.4156862745, 0.2980392157, + 0.7372549019607844, 0.8549019608, 0.4274509804, 0.2823529412, 0.7411764705882353, 0.862745098, + 0.4352941176, 0.2666666667, 0.7450980392156863, 0.8705882353, 0.4431372549, 0.2509803922, + 0.7490196078431373, 0.8784313725, 0.4549019608, 0.2431372549, 0.7529411764705882, + 0.8862745098, 0.462745098, 0.2274509804, 0.7568627450980392, 0.8941176471, 0.4705882353, + 0.2117647059, 0.7607843137254902, 0.9019607843, 0.4823529412, 0.1960784314, + 0.7647058823529411, 0.9098039216, 0.4901960784, 0.1803921569, 0.7686274509803922, + 0.9176470588, 0.4980392157, 0.1647058824, 0.7725490196078432, 0.9254901961, 0.5098039216, + 0.1490196078, 0.7764705882352941, 0.9333333333, 0.5176470588, 0.1333333333, + 0.7803921568627451, 0.9411764706, 0.5294117647, 0.1176470588, 0.7843137254901961, + 0.9490196078, 0.537254902, 0.1019607843, 0.788235294117647, 0.9568627451, 0.5450980392, + 0.0862745098, 0.792156862745098, 0.9647058824, 0.5568627451, 0.0705882353, 0.796078431372549, + 0.9725490196, 0.5647058824, 0.0549019608, 0.8, 0.9803921569, 0.5725490196, 0.0392156863, + 0.803921568627451, 0.9882352941, 0.5843137255, 0.0235294118, 0.807843137254902, 0.9921568627, + 0.5921568627, 0.0078431373, 0.8117647058823529, 0.9921568627, 0.6039215686, 0.0274509804, + 0.8156862745098039, 0.9921568627, 0.6117647059, 0.0509803922, 0.8196078431372549, + 0.9921568627, 0.6196078431, 0.0745098039, 0.8235294117647058, 0.9921568627, 0.631372549, + 0.0980392157, 0.8274509803921568, 0.9921568627, 0.6392156863, 0.1215686275, + 0.8313725490196079, 0.9921568627, 0.6470588235, 0.1411764706, 0.8352941176470589, + 0.9921568627, 0.6588235294, 0.1647058824, 0.8392156862745098, 0.9921568627, 0.6666666667, + 0.1882352941, 0.8431372549019608, 0.9921568627, 0.6784313725, 0.2117647059, + 0.8470588235294118, 0.9921568627, 0.6862745098, 0.2352941176, 0.8509803921568627, + 0.9921568627, 0.6941176471, 0.2509803922, 0.8549019607843137, 0.9921568627, 0.7058823529, + 0.2705882353, 0.8588235294117647, 0.9921568627, 0.7137254902, 0.2941176471, + 0.8627450980392157, 0.9921568627, 0.7215686275, 0.3176470588, 0.8666666666666667, + 0.9921568627, 0.7333333333, 0.3411764706, 0.8705882352941177, 0.9921568627, 0.7411764706, + 0.3647058824, 0.8745098039215686, 0.9921568627, 0.7490196078, 0.3843137255, + 0.8784313725490196, 0.9921568627, 0.7529411765, 0.4078431373, 0.8823529411764706, + 0.9921568627, 0.7607843137, 0.431372549, 0.8862745098039215, 0.9921568627, 0.7725490196, + 0.4549019608, 0.8901960784313725, 0.9921568627, 0.7803921569, 0.4784313725, + 0.8941176470588236, 0.9921568627, 0.7882352941, 0.4980392157, 0.8980392156862745, + 0.9921568627, 0.8, 0.5215686275, 0.9019607843137255, 0.9921568627, 0.8078431373, 0.5450980392, + 0.9058823529411765, 0.9921568627, 0.8156862745, 0.568627451, 0.9098039215686274, 0.9921568627, + 0.8274509804, 0.5921568627, 0.9137254901960784, 0.9921568627, 0.8352941176, 0.6156862745, + 0.9176470588235294, 0.9921568627, 0.8470588235, 0.6352941176, 0.9215686274509803, + 0.9921568627, 0.8549019608, 0.6588235294, 0.9254901960784314, 0.9921568627, 0.862745098, + 0.6823529412, 0.9294117647058824, 0.9921568627, 0.8745098039, 0.7058823529, + 0.9333333333333333, 0.9921568627, 0.8823529412, 0.7294117647, 0.9372549019607843, + 0.9921568627, 0.8901960784, 0.7490196078, 0.9411764705882354, 0.9921568627, 0.9019607843, + 0.7647058824, 0.9450980392156864, 0.9921568627, 0.9098039216, 0.7882352941, + 0.9490196078431372, 0.9921568627, 0.9215686275, 0.8117647059, 0.9529411764705882, + 0.9921568627, 0.9294117647, 0.8352941176, 0.9568627450980394, 0.9921568627, 0.937254902, + 0.8588235294, 0.9607843137254903, 0.9921568627, 0.9490196078, 0.8784313725, + 0.9647058823529413, 0.9921568627, 0.9568627451, 0.9019607843, 0.9686274509803922, + 0.9921568627, 0.9647058824, 0.9254901961, 0.9725490196078431, 0.9921568627, 0.9764705882, + 0.9490196078, 0.9764705882352941, 0.9921568627, 0.9843137255, 0.9725490196, + 0.9803921568627451, 0.9921568627, 0.9921568627, 0.9921568627, 0.984313725490196, 0.9921568627, + 0.9921568627, 0.9921568627, 0.9882352941176471, 0.9921568627, 0.9921568627, 0.9921568627, + 0.9921568627450981, 0.9921568627, 0.9921568627, 0.9921568627, 0.996078431372549, 0.9921568627, + 0.9921568627, 0.9921568627, 1.0, 0.9921568627, 0.9921568627, 0.9921568627, + ], + description: 'GE', + }, + { + ColorSpace: 'RGB', + Name: 'siemens', + name: 'siemens', + RGBPoints: [ + 0.0, 0.0078431373, 0.0039215686, 0.1254901961, 0.00392156862745098, 0.0078431373, + 0.0039215686, 0.1254901961, 0.00784313725490196, 0.0078431373, 0.0039215686, 0.1882352941, + 0.011764705882352941, 0.0117647059, 0.0039215686, 0.2509803922, 0.01568627450980392, + 0.0117647059, 0.0039215686, 0.3098039216, 0.0196078431372549, 0.0156862745, 0.0039215686, + 0.3725490196, 0.023529411764705882, 0.0156862745, 0.0039215686, 0.3725490196, + 0.027450980392156862, 0.0156862745, 0.0039215686, 0.3725490196, 0.03137254901960784, + 0.0156862745, 0.0039215686, 0.3725490196, 0.03529411764705882, 0.0156862745, 0.0039215686, + 0.3725490196, 0.0392156862745098, 0.0156862745, 0.0039215686, 0.3725490196, + 0.043137254901960784, 0.0156862745, 0.0039215686, 0.3725490196, 0.047058823529411764, + 0.0156862745, 0.0039215686, 0.3725490196, 0.050980392156862744, 0.0156862745, 0.0039215686, + 0.3725490196, 0.054901960784313725, 0.0156862745, 0.0039215686, 0.3725490196, + 0.05882352941176471, 0.0156862745, 0.0039215686, 0.3725490196, 0.06274509803921569, + 0.0156862745, 0.0039215686, 0.3882352941, 0.06666666666666667, 0.0156862745, 0.0039215686, + 0.4078431373, 0.07058823529411765, 0.0156862745, 0.0039215686, 0.4235294118, + 0.07450980392156863, 0.0156862745, 0.0039215686, 0.4431372549, 0.0784313725490196, + 0.0156862745, 0.0039215686, 0.462745098, 0.08235294117647059, 0.0156862745, 0.0039215686, + 0.4784313725, 0.08627450980392157, 0.0156862745, 0.0039215686, 0.4980392157, + 0.09019607843137255, 0.0196078431, 0.0039215686, 0.5137254902, 0.09411764705882353, + 0.0196078431, 0.0039215686, 0.5333333333, 0.09803921568627451, 0.0196078431, 0.0039215686, + 0.5529411765, 0.10196078431372549, 0.0196078431, 0.0039215686, 0.568627451, + 0.10588235294117647, 0.0196078431, 0.0039215686, 0.5882352941, 0.10980392156862745, + 0.0196078431, 0.0039215686, 0.6039215686, 0.11372549019607843, 0.0196078431, 0.0039215686, + 0.6235294118, 0.11764705882352942, 0.0196078431, 0.0039215686, 0.6431372549, + 0.12156862745098039, 0.0235294118, 0.0039215686, 0.6588235294, 0.12549019607843137, + 0.0235294118, 0.0039215686, 0.6784313725, 0.12941176470588237, 0.0235294118, 0.0039215686, + 0.6980392157, 0.13333333333333333, 0.0235294118, 0.0039215686, 0.7137254902, + 0.13725490196078433, 0.0235294118, 0.0039215686, 0.7333333333, 0.1411764705882353, + 0.0235294118, 0.0039215686, 0.7490196078, 0.1450980392156863, 0.0235294118, 0.0039215686, + 0.7647058824, 0.14901960784313725, 0.0235294118, 0.0039215686, 0.7843137255, + 0.15294117647058825, 0.0274509804, 0.0039215686, 0.8, 0.1568627450980392, 0.0274509804, + 0.0039215686, 0.8196078431, 0.1607843137254902, 0.0274509804, 0.0039215686, 0.8352941176, + 0.16470588235294117, 0.0274509804, 0.0039215686, 0.8549019608, 0.16862745098039217, + 0.0274509804, 0.0039215686, 0.8745098039, 0.17254901960784313, 0.0274509804, 0.0039215686, + 0.8901960784, 0.17647058823529413, 0.0274509804, 0.0039215686, 0.9098039216, + 0.1803921568627451, 0.031372549, 0.0039215686, 0.9294117647, 0.1843137254901961, 0.031372549, + 0.0039215686, 0.9254901961, 0.18823529411764706, 0.0509803922, 0.0039215686, 0.9098039216, + 0.19215686274509805, 0.0705882353, 0.0039215686, 0.8901960784, 0.19607843137254902, + 0.0901960784, 0.0039215686, 0.8705882353, 0.2, 0.1137254902, 0.0039215686, 0.8509803922, + 0.20392156862745098, 0.1333333333, 0.0039215686, 0.831372549, 0.20784313725490197, + 0.1529411765, 0.0039215686, 0.8117647059, 0.21176470588235294, 0.1725490196, 0.0039215686, + 0.7921568627, 0.21568627450980393, 0.1960784314, 0.0039215686, 0.7725490196, + 0.2196078431372549, 0.2156862745, 0.0039215686, 0.7529411765, 0.2235294117647059, + 0.2352941176, 0.0039215686, 0.737254902, 0.22745098039215686, 0.2509803922, 0.0039215686, + 0.7176470588, 0.23137254901960785, 0.2745098039, 0.0039215686, 0.6980392157, + 0.23529411764705885, 0.2941176471, 0.0039215686, 0.6784313725, 0.23921568627450984, + 0.3137254902, 0.0039215686, 0.6588235294, 0.24313725490196078, 0.3333333333, 0.0039215686, + 0.6392156863, 0.24705882352941178, 0.3568627451, 0.0039215686, 0.6196078431, + 0.25098039215686274, 0.3764705882, 0.0039215686, 0.6, 0.2549019607843137, 0.3960784314, + 0.0039215686, 0.5803921569, 0.25882352941176473, 0.4156862745, 0.0039215686, 0.5607843137, + 0.2627450980392157, 0.4392156863, 0.0039215686, 0.5411764706, 0.26666666666666666, + 0.4588235294, 0.0039215686, 0.5215686275, 0.27058823529411763, 0.4784313725, 0.0039215686, + 0.5019607843, 0.27450980392156865, 0.4980392157, 0.0039215686, 0.4823529412, + 0.2784313725490196, 0.5215686275, 0.0039215686, 0.4666666667, 0.2823529411764706, + 0.5411764706, 0.0039215686, 0.4470588235, 0.28627450980392155, 0.5607843137, 0.0039215686, + 0.4274509804, 0.2901960784313726, 0.5803921569, 0.0039215686, 0.4078431373, + 0.29411764705882354, 0.6039215686, 0.0039215686, 0.3882352941, 0.2980392156862745, + 0.6235294118, 0.0039215686, 0.368627451, 0.30196078431372547, 0.6431372549, 0.0039215686, + 0.3490196078, 0.3058823529411765, 0.662745098, 0.0039215686, 0.3294117647, + 0.30980392156862746, 0.6862745098, 0.0039215686, 0.3098039216, 0.3137254901960784, + 0.7058823529, 0.0039215686, 0.2901960784, 0.3176470588235294, 0.7254901961, 0.0039215686, + 0.2705882353, 0.3215686274509804, 0.7450980392, 0.0039215686, 0.2509803922, + 0.3254901960784314, 0.7647058824, 0.0039215686, 0.2352941176, 0.32941176470588235, + 0.7843137255, 0.0039215686, 0.2156862745, 0.3333333333333333, 0.8039215686, 0.0039215686, + 0.1960784314, 0.33725490196078434, 0.8235294118, 0.0039215686, 0.1764705882, + 0.3411764705882353, 0.8470588235, 0.0039215686, 0.1568627451, 0.34509803921568627, + 0.8666666667, 0.0039215686, 0.137254902, 0.34901960784313724, 0.8862745098, 0.0039215686, + 0.1176470588, 0.35294117647058826, 0.9058823529, 0.0039215686, 0.0980392157, + 0.3568627450980392, 0.9294117647, 0.0039215686, 0.0784313725, 0.3607843137254902, + 0.9490196078, 0.0039215686, 0.0588235294, 0.36470588235294116, 0.968627451, 0.0039215686, + 0.0392156863, 0.3686274509803922, 0.9921568627, 0.0039215686, 0.0235294118, + 0.37254901960784315, 0.9529411765, 0.0039215686, 0.0588235294, 0.3764705882352941, + 0.9529411765, 0.0078431373, 0.0549019608, 0.3803921568627451, 0.9529411765, 0.0156862745, + 0.0549019608, 0.3843137254901961, 0.9529411765, 0.0235294118, 0.0549019608, + 0.38823529411764707, 0.9529411765, 0.031372549, 0.0549019608, 0.39215686274509803, + 0.9529411765, 0.0352941176, 0.0549019608, 0.396078431372549, 0.9529411765, 0.0431372549, + 0.0549019608, 0.4, 0.9529411765, 0.0509803922, 0.0549019608, 0.403921568627451, 0.9529411765, + 0.0588235294, 0.0549019608, 0.40784313725490196, 0.9529411765, 0.062745098, 0.0549019608, + 0.4117647058823529, 0.9529411765, 0.0705882353, 0.0549019608, 0.41568627450980394, + 0.9529411765, 0.0784313725, 0.0509803922, 0.4196078431372549, 0.9529411765, 0.0862745098, + 0.0509803922, 0.4235294117647059, 0.9568627451, 0.0941176471, 0.0509803922, + 0.42745098039215684, 0.9568627451, 0.0980392157, 0.0509803922, 0.43137254901960786, + 0.9568627451, 0.1058823529, 0.0509803922, 0.43529411764705883, 0.9568627451, 0.1137254902, + 0.0509803922, 0.4392156862745098, 0.9568627451, 0.1215686275, 0.0509803922, + 0.44313725490196076, 0.9568627451, 0.1254901961, 0.0509803922, 0.4470588235294118, + 0.9568627451, 0.1333333333, 0.0509803922, 0.45098039215686275, 0.9568627451, 0.1411764706, + 0.0509803922, 0.4549019607843137, 0.9568627451, 0.1490196078, 0.0470588235, + 0.4588235294117647, 0.9568627451, 0.1568627451, 0.0470588235, 0.4627450980392157, + 0.9568627451, 0.1607843137, 0.0470588235, 0.4666666666666667, 0.9568627451, 0.168627451, + 0.0470588235, 0.4705882352941177, 0.9607843137, 0.1764705882, 0.0470588235, + 0.4745098039215686, 0.9607843137, 0.1843137255, 0.0470588235, 0.4784313725490197, + 0.9607843137, 0.1882352941, 0.0470588235, 0.48235294117647065, 0.9607843137, 0.1960784314, + 0.0470588235, 0.48627450980392156, 0.9607843137, 0.2039215686, 0.0470588235, + 0.49019607843137253, 0.9607843137, 0.2117647059, 0.0470588235, 0.49411764705882355, + 0.9607843137, 0.2196078431, 0.0431372549, 0.4980392156862745, 0.9607843137, 0.2235294118, + 0.0431372549, 0.5019607843137255, 0.9607843137, 0.231372549, 0.0431372549, 0.5058823529411764, + 0.9607843137, 0.2392156863, 0.0431372549, 0.5098039215686274, 0.9607843137, 0.2470588235, + 0.0431372549, 0.5137254901960784, 0.9607843137, 0.2509803922, 0.0431372549, + 0.5176470588235295, 0.9647058824, 0.2549019608, 0.0431372549, 0.5215686274509804, + 0.9647058824, 0.262745098, 0.0431372549, 0.5254901960784314, 0.9647058824, 0.2705882353, + 0.0431372549, 0.5294117647058824, 0.9647058824, 0.2745098039, 0.0431372549, + 0.5333333333333333, 0.9647058824, 0.2823529412, 0.0392156863, 0.5372549019607843, + 0.9647058824, 0.2901960784, 0.0392156863, 0.5411764705882353, 0.9647058824, 0.2980392157, + 0.0392156863, 0.5450980392156862, 0.9647058824, 0.3058823529, 0.0392156863, + 0.5490196078431373, 0.9647058824, 0.3098039216, 0.0392156863, 0.5529411764705883, + 0.9647058824, 0.3176470588, 0.0392156863, 0.5568627450980392, 0.9647058824, 0.3254901961, + 0.0392156863, 0.5607843137254902, 0.9647058824, 0.3333333333, 0.0392156863, + 0.5647058823529412, 0.9647058824, 0.337254902, 0.0392156863, 0.5686274509803921, 0.968627451, + 0.3450980392, 0.0392156863, 0.5725490196078431, 0.968627451, 0.3529411765, 0.0352941176, + 0.5764705882352941, 0.968627451, 0.3607843137, 0.0352941176, 0.5803921568627451, 0.968627451, + 0.368627451, 0.0352941176, 0.5843137254901961, 0.968627451, 0.3725490196, 0.0352941176, + 0.5882352941176471, 0.968627451, 0.3803921569, 0.0352941176, 0.592156862745098, 0.968627451, + 0.3882352941, 0.0352941176, 0.596078431372549, 0.968627451, 0.3960784314, 0.0352941176, 0.6, + 0.968627451, 0.4, 0.0352941176, 0.6039215686274509, 0.968627451, 0.4078431373, 0.0352941176, + 0.6078431372549019, 0.968627451, 0.4156862745, 0.0352941176, 0.611764705882353, 0.968627451, + 0.4235294118, 0.031372549, 0.615686274509804, 0.9725490196, 0.431372549, 0.031372549, + 0.6196078431372549, 0.9725490196, 0.4352941176, 0.031372549, 0.6235294117647059, 0.9725490196, + 0.4431372549, 0.031372549, 0.6274509803921569, 0.9725490196, 0.4509803922, 0.031372549, + 0.6313725490196078, 0.9725490196, 0.4588235294, 0.031372549, 0.6352941176470588, 0.9725490196, + 0.462745098, 0.031372549, 0.6392156862745098, 0.9725490196, 0.4705882353, 0.031372549, + 0.6431372549019608, 0.9725490196, 0.4784313725, 0.031372549, 0.6470588235294118, 0.9725490196, + 0.4862745098, 0.031372549, 0.6509803921568628, 0.9725490196, 0.4941176471, 0.0274509804, + 0.6549019607843137, 0.9725490196, 0.4980392157, 0.0274509804, 0.6588235294117647, + 0.9725490196, 0.5058823529, 0.0274509804, 0.6627450980392157, 0.9764705882, 0.5137254902, + 0.0274509804, 0.6666666666666666, 0.9764705882, 0.5215686275, 0.0274509804, + 0.6705882352941176, 0.9764705882, 0.5254901961, 0.0274509804, 0.6745098039215687, + 0.9764705882, 0.5333333333, 0.0274509804, 0.6784313725490196, 0.9764705882, 0.5411764706, + 0.0274509804, 0.6823529411764706, 0.9764705882, 0.5490196078, 0.0274509804, + 0.6862745098039216, 0.9764705882, 0.5529411765, 0.0274509804, 0.6901960784313725, + 0.9764705882, 0.5607843137, 0.0235294118, 0.6941176470588235, 0.9764705882, 0.568627451, + 0.0235294118, 0.6980392156862745, 0.9764705882, 0.5764705882, 0.0235294118, + 0.7019607843137254, 0.9764705882, 0.5843137255, 0.0235294118, 0.7058823529411765, + 0.9764705882, 0.5882352941, 0.0235294118, 0.7098039215686275, 0.9764705882, 0.5960784314, + 0.0235294118, 0.7137254901960784, 0.9803921569, 0.6039215686, 0.0235294118, + 0.7176470588235294, 0.9803921569, 0.6117647059, 0.0235294118, 0.7215686274509804, + 0.9803921569, 0.6156862745, 0.0235294118, 0.7254901960784313, 0.9803921569, 0.6235294118, + 0.0235294118, 0.7294117647058823, 0.9803921569, 0.631372549, 0.0196078431, 0.7333333333333333, + 0.9803921569, 0.6392156863, 0.0196078431, 0.7372549019607844, 0.9803921569, 0.6470588235, + 0.0196078431, 0.7411764705882353, 0.9803921569, 0.6509803922, 0.0196078431, + 0.7450980392156863, 0.9803921569, 0.6588235294, 0.0196078431, 0.7490196078431373, + 0.9803921569, 0.6666666667, 0.0196078431, 0.7529411764705882, 0.9803921569, 0.6745098039, + 0.0196078431, 0.7568627450980392, 0.9803921569, 0.6784313725, 0.0196078431, + 0.7607843137254902, 0.9843137255, 0.6862745098, 0.0196078431, 0.7647058823529411, + 0.9843137255, 0.6941176471, 0.0196078431, 0.7686274509803922, 0.9843137255, 0.7019607843, + 0.0156862745, 0.7725490196078432, 0.9843137255, 0.7098039216, 0.0156862745, + 0.7764705882352941, 0.9843137255, 0.7137254902, 0.0156862745, 0.7803921568627451, + 0.9843137255, 0.7215686275, 0.0156862745, 0.7843137254901961, 0.9843137255, 0.7294117647, + 0.0156862745, 0.788235294117647, 0.9843137255, 0.737254902, 0.0156862745, 0.792156862745098, + 0.9843137255, 0.7411764706, 0.0156862745, 0.796078431372549, 0.9843137255, 0.7490196078, + 0.0156862745, 0.8, 0.9843137255, 0.7529411765, 0.0156862745, 0.803921568627451, 0.9843137255, + 0.7607843137, 0.0156862745, 0.807843137254902, 0.9882352941, 0.768627451, 0.0156862745, + 0.8117647058823529, 0.9882352941, 0.768627451, 0.0156862745, 0.8156862745098039, 0.9843137255, + 0.7843137255, 0.0117647059, 0.8196078431372549, 0.9843137255, 0.8, 0.0117647059, + 0.8235294117647058, 0.9843137255, 0.8156862745, 0.0117647059, 0.8274509803921568, + 0.9803921569, 0.831372549, 0.0117647059, 0.8313725490196079, 0.9803921569, 0.8431372549, + 0.0117647059, 0.8352941176470589, 0.9803921569, 0.8588235294, 0.0078431373, + 0.8392156862745098, 0.9803921569, 0.8745098039, 0.0078431373, 0.8431372549019608, + 0.9764705882, 0.8901960784, 0.0078431373, 0.8470588235294118, 0.9764705882, 0.9058823529, + 0.0078431373, 0.8509803921568627, 0.9764705882, 0.9176470588, 0.0078431373, + 0.8549019607843137, 0.9764705882, 0.9333333333, 0.0039215686, 0.8588235294117647, + 0.9725490196, 0.9490196078, 0.0039215686, 0.8627450980392157, 0.9725490196, 0.9647058824, + 0.0039215686, 0.8666666666666667, 0.9725490196, 0.9803921569, 0.0039215686, + 0.8705882352941177, 0.9725490196, 0.9960784314, 0.0039215686, 0.8745098039215686, + 0.9725490196, 0.9960784314, 0.0039215686, 0.8784313725490196, 0.9725490196, 0.9960784314, + 0.0352941176, 0.8823529411764706, 0.9725490196, 0.9960784314, 0.0666666667, + 0.8862745098039215, 0.9725490196, 0.9960784314, 0.0980392157, 0.8901960784313725, + 0.9725490196, 0.9960784314, 0.1294117647, 0.8941176470588236, 0.9725490196, 0.9960784314, + 0.1647058824, 0.8980392156862745, 0.9764705882, 0.9960784314, 0.1960784314, + 0.9019607843137255, 0.9764705882, 0.9960784314, 0.2274509804, 0.9058823529411765, + 0.9764705882, 0.9960784314, 0.2549019608, 0.9098039215686274, 0.9764705882, 0.9960784314, + 0.2901960784, 0.9137254901960784, 0.9764705882, 0.9960784314, 0.3215686275, + 0.9176470588235294, 0.9803921569, 0.9960784314, 0.3529411765, 0.9215686274509803, + 0.9803921569, 0.9960784314, 0.3843137255, 0.9254901960784314, 0.9803921569, 0.9960784314, + 0.4156862745, 0.9294117647058824, 0.9803921569, 0.9960784314, 0.4509803922, + 0.9333333333333333, 0.9803921569, 0.9960784314, 0.4823529412, 0.9372549019607843, + 0.9843137255, 0.9960784314, 0.5137254902, 0.9411764705882354, 0.9843137255, 0.9960784314, + 0.5450980392, 0.9450980392156864, 0.9843137255, 0.9960784314, 0.5803921569, + 0.9490196078431372, 0.9843137255, 0.9960784314, 0.6117647059, 0.9529411764705882, + 0.9843137255, 0.9960784314, 0.6431372549, 0.9568627450980394, 0.9882352941, 0.9960784314, + 0.6745098039, 0.9607843137254903, 0.9882352941, 0.9960784314, 0.7058823529, + 0.9647058823529413, 0.9882352941, 0.9960784314, 0.7411764706, 0.9686274509803922, + 0.9882352941, 0.9960784314, 0.768627451, 0.9725490196078431, 0.9882352941, 0.9960784314, 0.8, + 0.9764705882352941, 0.9921568627, 0.9960784314, 0.831372549, 0.9803921568627451, 0.9921568627, + 0.9960784314, 0.8666666667, 0.984313725490196, 0.9921568627, 0.9960784314, 0.8980392157, + 0.9882352941176471, 0.9921568627, 0.9960784314, 0.9294117647, 0.9921568627450981, + 0.9921568627, 0.9960784314, 0.9607843137, 0.996078431372549, 0.9960784314, 0.9960784314, + 0.9607843137, 1.0, 0.9960784314, 0.9960784314, 0.9607843137, + ], + description: 'Siemens', + }, +]; + +export { colormaps }; diff --git a/extensions/cornerstone/src/utils/dicomLoaderService.js b/extensions/cornerstone/src/utils/dicomLoaderService.js index 79cb2053e..b9d5ea6dd 100644 --- a/extensions/cornerstone/src/utils/dicomLoaderService.js +++ b/extensions/cornerstone/src/utils/dicomLoaderService.js @@ -176,6 +176,7 @@ class DicomLoaderService { authorizationHeaders, wadoRoot, wadoUri, + instance, } = dataset; // Retrieve wadors or just try to fetch wadouri if (!someInvalidStrings(wadoRoot)) { @@ -188,6 +189,13 @@ class DicomLoaderService { ); } else if (!someInvalidStrings(wadoUri)) { return fetchIt(wadoUri, { headers: authorizationHeaders }); + } else if (!someInvalidStrings(instance?.url)) { + // make sure the url is absolute, remove the scope + // from it if it is not absolute. For instance it might be dicomweb:http://.... + // and we need to remove the dicomweb: part + const url = instance.url; + const absoluteUrl = url.startsWith('http') ? url : url.substring(url.indexOf(':') + 1); + return fetchIt(absoluteUrl, { headers: authorizationHeaders }); } } diff --git a/extensions/cornerstone/src/utils/getCornerstoneBlendMode.ts b/extensions/cornerstone/src/utils/getCornerstoneBlendMode.ts index 37dca4c10..11ff71021 100644 --- a/extensions/cornerstone/src/utils/getCornerstoneBlendMode.ts +++ b/extensions/cornerstone/src/utils/getCornerstoneBlendMode.ts @@ -1,6 +1,8 @@ import { Enums } from '@cornerstonejs/core'; const MIP = 'mip'; +const MINIP = 'minip'; +const AVG = 'avg'; export default function getCornerstoneBlendMode(blendMode: string): Enums.BlendModes { if (!blendMode) { @@ -11,5 +13,13 @@ export default function getCornerstoneBlendMode(blendMode: string): Enums.BlendM return Enums.BlendModes.MAXIMUM_INTENSITY_BLEND; } - throw new Error(); + if (blendMode.toLowerCase() === MINIP) { + return Enums.BlendModes.MINIMUM_INTENSITY_BLEND; + } + + if (blendMode.toLowerCase() === AVG) { + return Enums.BlendModes.AVERAGE_INTENSITY_BLEND; + } + + throw new Error(`Unsupported blend mode: ${blendMode}`); } diff --git a/extensions/cornerstone/src/utils/stackSync/calculateViewportRegistrations.ts b/extensions/cornerstone/src/utils/imageSliceSync/calculateViewportRegistrations.ts similarity index 100% rename from extensions/cornerstone/src/utils/stackSync/calculateViewportRegistrations.ts rename to extensions/cornerstone/src/utils/imageSliceSync/calculateViewportRegistrations.ts diff --git a/extensions/cornerstone/src/utils/stackSync/toggleStackImageSync.ts b/extensions/cornerstone/src/utils/imageSliceSync/toggleImageSliceSync.ts similarity index 75% rename from extensions/cornerstone/src/utils/stackSync/toggleStackImageSync.ts rename to extensions/cornerstone/src/utils/imageSliceSync/toggleImageSliceSync.ts index d18768f38..1a8e8cd78 100644 --- a/extensions/cornerstone/src/utils/stackSync/toggleStackImageSync.ts +++ b/extensions/cornerstone/src/utils/imageSliceSync/toggleImageSliceSync.ts @@ -1,20 +1,35 @@ -const STACK_SYNC_NAME = 'stackImageSync'; +const IMAGE_SLICE_SYNC_NAME = 'IMAGE_SLICE_SYNC'; -export default function toggleStackImageSync({ - toggledState, +export default function toggleImageSliceSync({ servicesManager, viewports: providedViewports, + syncId, }) { - if (!toggledState) { - return disableSync(STACK_SYNC_NAME, servicesManager); - } - const { syncGroupService, viewportGridService, displaySetService, cornerstoneViewportService } = servicesManager.services; + syncId ||= IMAGE_SLICE_SYNC_NAME; + const viewports = providedViewports || getReconstructableStackViewports(viewportGridService, displaySetService); + // Todo: right now we don't have a proper way to define specific + // viewports to add to synchronizers, and right now it is global or not + // after we do that, we should do fine grained control of the synchronizers + const someViewportHasSync = viewports.some(viewport => { + const syncStates = syncGroupService.getSynchronizersForViewport( + viewport.viewportOptions.viewportId + ); + + const imageSync = syncStates.find(syncState => syncState.id === syncId); + + return !!imageSync; + }); + + if (someViewportHasSync) { + return disableSync(syncId, servicesManager); + } + // create synchronization group and add the viewports to it. viewports.forEach(gridViewport => { const { viewportId } = gridViewport.viewportOptions; @@ -23,8 +38,8 @@ export default function toggleStackImageSync({ return; } syncGroupService.addViewportToSyncGroup(viewportId, viewport.getRenderingEngine().id, { - type: 'stackimage', - id: STACK_SYNC_NAME, + type: 'imageSlice', + id: syncId, source: true, target: true, }); diff --git a/extensions/cornerstone/src/utils/initViewTiming.ts b/extensions/cornerstone/src/utils/initViewTiming.ts index 8c4ad9f03..a9d1544c4 100644 --- a/extensions/cornerstone/src/utils/initViewTiming.ts +++ b/extensions/cornerstone/src/utils/initViewTiming.ts @@ -1,13 +1,7 @@ -import { log, Types } from '@ohif/core'; +import { log, Enums } from '@ohif/core'; import { EVENTS } from '@cornerstonejs/core'; -const { TimingEnum } = Types; - -const IMAGE_TIMING_KEYS = [ - TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES, - TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE, - TimingEnum.STUDY_TO_FIRST_IMAGE, -]; +const IMAGE_TIMING_KEYS = []; const imageTiming = { viewportsWaiting: 0, @@ -23,6 +17,18 @@ const imageTiming = { */ export default function initViewTiming({ element }) { + if (!IMAGE_TIMING_KEYS.length) { + // Work around a bug in WebPack that doesn't getting the enums initialized + // quite fast enough to be declared statically. + const { TimingEnum } = Enums; + + IMAGE_TIMING_KEYS.push( + TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES, + TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE, + TimingEnum.STUDY_TO_FIRST_IMAGE, + ); + } + if (!IMAGE_TIMING_KEYS.find(key => log.timingKeys[key])) { return; } @@ -34,6 +40,7 @@ function imageRenderedListener(evt) { if (evt.detail.viewportStatus === 'preRender') { return; } + const { TimingEnum } = Enums; log.timeEnd(TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE); log.timeEnd(TimingEnum.STUDY_TO_FIRST_IMAGE); log.timeEnd(TimingEnum.SCRIPT_TO_VIEW); diff --git a/extensions/cornerstone/src/utils/interleaveCenterLoader.ts b/extensions/cornerstone/src/utils/interleaveCenterLoader.ts index d8f64b5d8..16ed7eeeb 100644 --- a/extensions/cornerstone/src/utils/interleaveCenterLoader.ts +++ b/extensions/cornerstone/src/utils/interleaveCenterLoader.ts @@ -51,20 +51,27 @@ export default function interleaveCenterLoader({ * listen to it and as the other viewports are created we can set the volumes for them * since volumes are already started loading. */ - if (matchDetails.size !== viewportIdVolumeInputArrayMap.size) { + const uniqueViewportVolumeDisplaySetUIDs = new Set(); + viewportIdVolumeInputArrayMap.forEach((volumeInputArray, viewportId) => { + volumeInputArray.forEach(volumeInput => { + const { volumeId } = volumeInput; + uniqueViewportVolumeDisplaySetUIDs.add(volumeId); + }); + }); + + const uniqueMatchedDisplaySetUIDs = new Set(); + + matchDetails.forEach(matchDetail => { + const { displaySetsInfo } = matchDetail; + displaySetsInfo.forEach(({ displaySetInstanceUID }) => { + uniqueMatchedDisplaySetUIDs.add(displaySetInstanceUID); + }); + }); + + if (uniqueViewportVolumeDisplaySetUIDs.size !== uniqueMatchedDisplaySetUIDs.size) { return; } - // Check if all the matched volumes are loaded - for (const [_, details] of displaySetsMatchDetails.entries()) { - const { SeriesInstanceUID } = details; - - // HangingProtocol has matched, but don't have all the volumes created yet, so return - if (!Array.from(volumeIdMapsToLoad.values()).includes(SeriesInstanceUID)) { - return; - } - } - const volumeIds = Array.from(volumeIdMapsToLoad.keys()).slice(); // get volumes from cache const volumes = volumeIds.map(volumeId => { diff --git a/extensions/cornerstone/src/utils/interleaveTopToBottom.ts b/extensions/cornerstone/src/utils/interleaveTopToBottom.ts index 0caf40372..fc5447a42 100644 --- a/extensions/cornerstone/src/utils/interleaveTopToBottom.ts +++ b/extensions/cornerstone/src/utils/interleaveTopToBottom.ts @@ -37,6 +37,31 @@ export default function interleaveTopToBottom({ } } + const filteredMatchDetails = []; + const displaySetsToLoad = new Set(); + + // Check all viewports that have a displaySet to be loaded. In some cases + // (eg: line chart viewports which is not a Cornerstone viewport) the + // displaySet is created on the client and there are no instances to be + // downloaded. For those viewports the displaySet may have the `skipLoading` + // option set to true otherwise it may block the download of all other + // instances resulting in blank viewports. + Array.from(matchDetails.values()).forEach(curMatchDetails => { + const { displaySetsInfo } = curMatchDetails; + let numDisplaySetsToLoad = 0; + + displaySetsInfo.forEach(({ displaySetInstanceUID, displaySetOptions }) => { + if (!displaySetOptions?.options?.skipLoading) { + numDisplaySetsToLoad++; + displaySetsToLoad.add(displaySetInstanceUID); + } + }); + + if (numDisplaySetsToLoad) { + filteredMatchDetails.push(curMatchDetails); + } + }); + /** * The following is checking if all the viewports that were matched in the HP has been * successfully created their cornerstone viewport or not. Todo: This can be @@ -50,20 +75,27 @@ export default function interleaveTopToBottom({ * listen to it and as the other viewports are created we can set the volumes for them * since volumes are already started loading. */ - if (matchDetails.size !== viewportIdVolumeInputArrayMap.size) { + const uniqueViewportVolumeDisplaySetUIDs = new Set(); + viewportIdVolumeInputArrayMap.forEach((volumeInputArray, viewportId) => { + volumeInputArray.forEach(volumeInput => { + const { volumeId } = volumeInput; + uniqueViewportVolumeDisplaySetUIDs.add(volumeId); + }); + }); + + const uniqueMatchedDisplaySetUIDs = new Set(); + + matchDetails.forEach(matchDetail => { + const { displaySetsInfo } = matchDetail; + displaySetsInfo.forEach(({ displaySetInstanceUID }) => { + uniqueMatchedDisplaySetUIDs.add(displaySetInstanceUID); + }); + }); + + if (uniqueViewportVolumeDisplaySetUIDs.size !== uniqueMatchedDisplaySetUIDs.size) { return; } - // Check if all the matched volumes are loaded - for (const [_, details] of displaySetsMatchDetails.entries()) { - const { SeriesInstanceUID } = details; - - // HangingProtocol has matched, but don't have all the volumes created yet, so return - if (!Array.from(volumeIdMapsToLoad.values()).includes(SeriesInstanceUID)) { - return; - } - } - const volumeIds = Array.from(volumeIdMapsToLoad.keys()).slice(); // get volumes from cache const volumes = volumeIds.map(volumeId => { diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Angle.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/Angle.ts index 5708582a4..2e178f2d5 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Angle.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Angle.ts @@ -16,7 +16,8 @@ const Angle = { csToolsEventDetail, displaySetService, CornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -50,18 +51,20 @@ const Angle = { displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); - const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -126,7 +129,7 @@ This function is used to convert the measurement data to a format that is suitable for the report generation (e.g. for the csv report). The report returns a list of columns and corresponding values. */ -function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { +function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) { const columns = []; const values = []; @@ -159,7 +162,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { }; } -function getDisplayText(mappedAnnotations, displaySet) { +function getDisplayText(mappedAnnotations, displaySet, customizationService) { if (!mappedAnnotations || !mappedAnnotations.length) { return ''; } diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts index d5a7129da..373bd115c 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts @@ -14,7 +14,8 @@ const Length = { csToolsEventDetail, displaySetService, cornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -48,17 +49,18 @@ const Length = { displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); - const displayText = getDisplayText(mappedAnnotations, displaySet); + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -66,7 +68,6 @@ const Length = { toolName: metadata.toolName, displaySetInstanceUID: displaySet.displaySetInstanceUID, label: data.text, - text: data.text, displayText: displayText, data: data.cachedStats, type: getValueTypeFromToolType(toolName), @@ -106,7 +107,7 @@ function getMappedAnnotations(annotation, displaySetService) { return annotations; } -function getDisplayText(mappedAnnotations, displaySet) { +function getDisplayText(mappedAnnotations, displaySet, customizationService) { if (!mappedAnnotations) { return ''; } diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts index 11e1c7c02..6517445d2 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts @@ -11,7 +11,8 @@ const Bidirectional = { csToolsEventDetail, displaySetService, cornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -45,18 +46,20 @@ const Bidirectional = { displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); - const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -121,7 +124,7 @@ This function is used to convert the measurement data to a format that is suitable for the report generation (e.g. for the csv report). The report returns a list of columns and corresponding values. */ -function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { +function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) { const columns = []; const values = []; @@ -154,7 +157,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { }; } -function getDisplayText(mappedAnnotations, displaySet) { +function getDisplayText(mappedAnnotations, displaySet, customizationService) { if (!mappedAnnotations || !mappedAnnotations.length) { return ''; } diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts index 6ec79919b..88ca76862 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts @@ -2,6 +2,7 @@ import SUPPORTED_TOOLS from './constants/supportedTools'; import { getDisplayUnit } from './utils'; import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; import { utils } from '@ohif/core'; +import { getStatisticDisplayString } from './utils/getValueDisplayString'; const CircleROI = { toAnnotation: measurement => {}, @@ -9,7 +10,8 @@ const CircleROI = { csToolsEventDetail, DisplaySetService, CornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -43,18 +45,20 @@ const CircleROI = { displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; const mappedAnnotations = getMappedAnnotations(annotation, DisplaySetService); - const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -124,7 +128,7 @@ This function is used to convert the measurement data to a format that is suitable for the report generation (e.g. for the csv report). The report returns a list of columns and corresponding values. */ -function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { +function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) { const columns = []; const values = []; @@ -162,7 +166,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { }; } -function getDisplayText(mappedAnnotations, displaySet) { +function getDisplayText(mappedAnnotations, displaySet, customizationService) { if (!mappedAnnotations || !mappedAnnotations.length) { return ''; } @@ -190,11 +194,7 @@ function getDisplayText(mappedAnnotations, displaySet) { mappedAnnotations.forEach(mappedAnnotation => { const { unit, max, SeriesNumber } = mappedAnnotation; - let maxStr = ''; - if (max) { - const roundedMax = utils.roundNumber(max, 2); - maxStr = `Max: ${roundedMax} ${getDisplayUnit(unit)} `; - } + const maxStr = getStatisticDisplayString(max, unit, 'max'); const str = `${maxStr}(S:${SeriesNumber}${instanceText}${frameText})`; if (!displayText.includes(str)) { diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/CobbAngle.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/CobbAngle.ts index f61356c29..631f0d029 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/CobbAngle.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/CobbAngle.ts @@ -16,7 +16,8 @@ const CobbAngle = { csToolsEventDetail, displaySetService, CornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -50,18 +51,20 @@ const CobbAngle = { displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); - const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -126,7 +129,7 @@ This function is used to convert the measurement data to a format that is suitable for the report generation (e.g. for the csv report). The report returns a list of columns and corresponding values. */ -function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { +function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) { const columns = []; const values = []; @@ -159,7 +162,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { }; } -function getDisplayText(mappedAnnotations, displaySet) { +function getDisplayText(mappedAnnotations, displaySet, customizationService) { if (!mappedAnnotations || !mappedAnnotations.length) { return ''; } diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts index a38b6f835..c406b9972 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts @@ -2,6 +2,7 @@ import SUPPORTED_TOOLS from './constants/supportedTools'; import { getDisplayUnit } from './utils'; import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; import { utils } from '@ohif/core'; +import { getStatisticDisplayString } from './utils/getValueDisplayString'; const EllipticalROI = { toAnnotation: measurement => {}, @@ -9,7 +10,8 @@ const EllipticalROI = { csToolsEventDetail, displaySetService, cornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -43,18 +45,20 @@ const EllipticalROI = { displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); - const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -124,7 +128,7 @@ This function is used to convert the measurement data to a format that is suitable for the report generation (e.g. for the csv report). The report returns a list of columns and corresponding values. */ -function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { +function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) { const columns = []; const values = []; @@ -162,7 +166,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { }; } -function getDisplayText(mappedAnnotations, displaySet) { +function getDisplayText(mappedAnnotations, displaySet, customizationService) { if (!mappedAnnotations || !mappedAnnotations.length) { return ''; } @@ -189,12 +193,7 @@ function getDisplayText(mappedAnnotations, displaySet) { mappedAnnotations.forEach(mappedAnnotation => { const { unit, max, SeriesNumber } = mappedAnnotation; - let maxStr = ''; - if (max) { - const roundedMax = utils.roundNumber(max, 2); - maxStr = `Max: ${roundedMax} ${getDisplayUnit(unit)} `; - } - + const maxStr = getStatisticDisplayString(max, unit, 'max'); const str = `${maxStr}(S:${SeriesNumber}${instanceText}${frameText})`; if (!displayText.includes(str)) { displayText.push(str); diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts index 5c5072d2c..bb1319b24 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts @@ -15,7 +15,8 @@ const Length = { csToolsEventDetail, displaySetService, cornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -32,11 +33,7 @@ const Length = { throw new Error('Tool not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes( + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes( referencedImageId, cornerstoneViewportService, viewportId @@ -53,18 +50,20 @@ const Length = { displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); - const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -98,11 +97,8 @@ function getMappedAnnotations(annotation, displaySetService) { throw new Error('Non-acquisition plane measurement mapping not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - frameNumber, - } = getSOPInstanceAttributes(referencedImageId); + const { SOPInstanceUID, SeriesInstanceUID, frameNumber } = + getSOPInstanceAttributes(referencedImageId); const displaySet = displaySetService.getDisplaySetForSOPInstanceUID( SOPInstanceUID, @@ -131,7 +127,7 @@ This function is used to convert the measurement data to a format that is suitable for the report generation (e.g. for the csv report). The report returns a list of columns and corresponding values. */ -function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { +function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) { const columns = []; const values = []; @@ -166,7 +162,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { }; } -function getDisplayText(mappedAnnotations, displaySet) { +function getDisplayText(mappedAnnotations, displaySet, customizationService) { if (!mappedAnnotations || !mappedAnnotations.length) { return ''; } @@ -174,13 +170,7 @@ function getDisplayText(mappedAnnotations, displaySet) { const displayText = []; // Area is the same for all series - const { - length, - SeriesNumber, - SOPInstanceUID, - frameNumber, - unit, - } = mappedAnnotations[0]; + const { length, SeriesNumber, SOPInstanceUID, frameNumber, unit } = mappedAnnotations[0]; const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); @@ -196,9 +186,7 @@ function getDisplayText(mappedAnnotations, displaySet) { return displayText; } const roundedLength = utils.roundNumber(length, 2); - displayText.push( - `${roundedLength} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})` - ); + displayText.push(`${roundedLength} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})`); return displayText; } diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/LivewireContour.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/LivewireContour.ts new file mode 100644 index 000000000..4c2d3f56f --- /dev/null +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/LivewireContour.ts @@ -0,0 +1,161 @@ +import SUPPORTED_TOOLS from './constants/supportedTools'; +import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; +import { getDisplayUnit } from './utils'; +import { utils } from '@ohif/core'; + +/** + * Represents a mapping utility for Livewire measurements. + */ +const LivewireContour = { + toAnnotation: measurement => {}, + + /** + * Maps cornerstone annotation event data to measurement service format. + * + * @param {Object} csToolsEventDetail Cornerstone event data + * @param {DisplaySetService} DisplaySetService Service for managing display sets + * @param {CornerstoneViewportService} CornerstoneViewportService Service for managing viewports + * @param {Function} getValueTypeFromToolType Function to get value type from tool type + * @returns {Measurement} Measurement instance + */ + toMeasurement: ( + csToolsEventDetail, + DisplaySetService, + CornerstoneViewportService, + getValueTypeFromToolType, + customizationService + ) => { + const { annotation } = csToolsEventDetail; + const { metadata, data, annotationUID } = annotation; + + if (!metadata || !data) { + console.warn('Livewire tool: Missing metadata or data'); + return null; + } + + const { toolName, referencedImageId, FrameOfReferenceUID } = metadata; + const validToolType = SUPPORTED_TOOLS.includes(toolName); + if (!validToolType) { + throw new Error(`Tool ${toolName} not supported`); + } + + const { SOPInstanceUID, SeriesInstanceUID, frameNumber, StudyInstanceUID } = + getSOPInstanceAttributes(referencedImageId); + + let displaySet; + if (SOPInstanceUID) { + displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + } else { + displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID); + } + + return { + uid: annotationUID, + SOPInstanceUID, + FrameOfReferenceUID, + points: data.contour.polyline, + textBox: data.handles.textBox, + metadata, + frameNumber, + referenceSeriesUID: SeriesInstanceUID, + referenceStudyUID: StudyInstanceUID, + toolName: metadata.toolName, + displaySetInstanceUID: displaySet.displaySetInstanceUID, + label: data.label, + displayText: getDisplayText(annotation, displaySet, customizationService), + data: data.cachedStats, + type: getValueTypeFromToolType(toolName), + getReport: () => getColumnValueReport(annotation, customizationService), + }; + }, +}; + +/** + * This function is used to convert the measurement data to a + * format that is suitable for report generation (e.g. for the csv report). + * The report returns a list of columns and corresponding values. + * + * @param {object} annotation + * @returns {object} Report's content from this tool + */ +function getColumnValueReport(annotation, customizationService) { + const columns = []; + const values = []; + + /** Add type */ + columns.push('AnnotationType'); + values.push('Cornerstone:Livewire'); + + /** Add cachedStats */ + const { metadata, data } = annotation; + + /** Add FOR */ + if (metadata.FrameOfReferenceUID) { + columns.push('FrameOfReferenceUID'); + values.push(metadata.FrameOfReferenceUID); + } + + /** Add points */ + if (data.contour.polyline) { + /** + * Points has the form of [[x1, y1, z1], [x2, y2, z2], ...] + * convert it to string of [[x1 y1 z1];[x2 y2 z2];...] + * so that it can be used in the CSV report + */ + columns.push('points'); + values.push(data.contour.polyline.map(p => p.join(' ')).join(';')); + } + + return { columns, values }; +} + +/** + * Retrieves the display text for an annotation in a display set. + * + * @param {Object} annotation - The annotation object. + * @param {Object} displaySet - The display set object. + * @returns {string[]} - An array of display text. + */ +function getDisplayText(annotation, displaySet, customizationService) { + const { metadata, data } = annotation; + + if (!data.cachedStats || !data.cachedStats[`imageId:${metadata.referencedImageId}`]) { + return []; + } + + const { area, areaUnit } = data.cachedStats[`imageId:${metadata.referencedImageId}`]; + + const { SOPInstanceUID, frameNumber } = getSOPInstanceAttributes(metadata.referencedImageId); + + const displayText = []; + + const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); + let InstanceNumber; + if (instance) { + InstanceNumber = instance.InstanceNumber; + } + + const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : ''; + const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : ''; + + const { SeriesNumber } = displaySet; + if (SeriesNumber) { + displayText.push(`S: ${SeriesNumber}${instanceText}${frameText}`); + } + + if (area) { + /** + * Add Area + * Area sometimes becomes undefined if `preventHandleOutsideImage` is off + */ + const roundedArea = utils.roundNumber(area || 0, 2); + displayText.push(`${roundedArea} ${getDisplayUnit(areaUnit)}`); + } + + return displayText; +} + +export default LivewireContour; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/PlanarFreehandROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/PlanarFreehandROI.ts index b6db513b6..d3c837c2a 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/PlanarFreehandROI.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/PlanarFreehandROI.ts @@ -1,22 +1,31 @@ import SUPPORTED_TOOLS from './constants/supportedTools'; import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; +import { getDisplayUnit } from './utils'; +import { utils } from '@ohif/core'; +/** + * Represents a mapping utility for Planar Freehand ROI measurements. + */ const PlanarFreehandROI = { toAnnotation: measurement => {}, /** * Maps cornerstone annotation event data to measurement service format. * - * @param {Object} cornerstone Cornerstone event data - * @return {Measurement} Measurement instance + * @param {Object} csToolsEventDetail Cornerstone event data + * @param {DisplaySetService} DisplaySetService Service for managing display sets + * @param {CornerstoneViewportService} CornerstoneViewportService Service for managing viewports + * @param {Function} getValueTypeFromToolType Function to get value type from tool type + * @returns {Measurement} Measurement instance */ toMeasurement: ( csToolsEventDetail, DisplaySetService, CornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { - const { annotation, viewportId } = csToolsEventDetail; + const { annotation } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; if (!metadata || !data) { @@ -26,19 +35,14 @@ const PlanarFreehandROI = { const { toolName, referencedImageId, FrameOfReferenceUID } = metadata; const validToolType = SUPPORTED_TOOLS.includes(toolName); - if (!validToolType) { - throw new Error('Tool not supported'); + throw new Error(`Tool ${toolName} not supported`); } - const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes( - referencedImageId, - CornerstoneViewportService, - viewportId - ); + const { SOPInstanceUID, SeriesInstanceUID, frameNumber, StudyInstanceUID } = + getSOPInstanceAttributes(referencedImageId); let displaySet; - if (SOPInstanceUID) { displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( SOPInstanceUID, @@ -48,91 +52,134 @@ const PlanarFreehandROI = { displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; - - const mappedAnnotations = getMappedAnnotations(annotation, DisplaySetService); - - const displayText = getDisplayText(mappedAnnotations); - const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); - return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, - points, + points: data.contour.polyline, + textBox: data.handles.textBox, metadata, + frameNumber, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, toolName: metadata.toolName, displaySetInstanceUID: displaySet.displaySetInstanceUID, label: data.label, - displayText: displayText, - data: { ...data, ...data.cachedStats }, + displayText: getDisplayText(annotation, displaySet, customizationService), + data: data.cachedStats, type: getValueTypeFromToolType(toolName), - getReport, + getReport: () => getColumnValueReport(annotation, customizationService), }; }, }; /** - * It maps an imaging library annotation to a list of simplified annotation properties. - * - * @param {Object} annotationData - * @param {Object} DisplaySetService - * @returns - */ -function getMappedAnnotations(annotationData, DisplaySetService) { - const { metadata, data } = annotationData; - const { label } = data; - const { referencedImageId } = metadata; - - const annotations = []; - - const { SOPInstanceUID: _SOPInstanceUID, SeriesInstanceUID: _SeriesInstanceUID } = - getSOPInstanceAttributes(referencedImageId) || {}; - - if (!_SOPInstanceUID || !_SeriesInstanceUID) { - return annotations; - } - - const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( - _SOPInstanceUID, - _SeriesInstanceUID - ); - - const { SeriesNumber, SeriesInstanceUID } = displaySet; - - annotations.push({ - SeriesInstanceUID, - SeriesNumber, - label, - data, - }); - - return annotations; -} - -/** - * TBD - * This function is used to convert the measurement data to a format that is suitable for the report generation (e.g. for the csv report). + * This function is used to convert the measurement data to a + * format that is suitable for report generation (e.g. for the csv report). * The report returns a list of columns and corresponding values. - * @param {*} mappedAnnotations - * @param {*} points - * @param {*} FrameOfReferenceUID - * @returns Object representing the report's content for this tool. + * + * @param {object} annotation + * @returns {object} Report's content from this tool */ -function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { +function getColumnValueReport(annotation, customizationService) { + const { PlanarFreehandROI } = customizationService.get('cornerstone.measurements'); + const { report } = PlanarFreehandROI; const columns = []; const values = []; - return { - columns, - values, - }; + /** Add type */ + columns.push('AnnotationType'); + values.push('Cornerstone:PlanarFreehandROI'); + + /** Add cachedStats */ + const { metadata, data } = annotation; + const stats = data.cachedStats[`imageId:${metadata.referencedImageId}`]; + + report.forEach(({ name, value }) => { + columns.push(name); + stats[value] ? values.push(stats[value]) : values.push('not available'); + }); + + /** Add FOR */ + if (metadata.FrameOfReferenceUID) { + columns.push('FrameOfReferenceUID'); + values.push(metadata.FrameOfReferenceUID); + } + + /** Add points */ + if (data.contour.polyline) { + columns.push('points'); + values.push(data.contour.polyline.map(p => p.join(' ')).join(';')); + } + + return { columns, values }; } -function getDisplayText(mappedAnnotations) { - return ''; +/** + * Retrieves the display text for an annotation in a display set. + * + * @param {Object} annotation - The annotation object. + * @param {Object} displaySet - The display set object. + * @returns {string[]} - An array of display text. + */ +function getDisplayText(annotation, displaySet, customizationService) { + const { PlanarFreehandROI } = customizationService.get('cornerstone.measurements'); + const { displayText } = PlanarFreehandROI; + + const { metadata, data } = annotation; + + if (!data.cachedStats || !data.cachedStats[`imageId:${metadata.referencedImageId}`]) { + return []; + } + + const { SOPInstanceUID, frameNumber } = getSOPInstanceAttributes(metadata.referencedImageId); + + const displayTextArray = []; + + const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); + let InstanceNumber; + if (instance) { + InstanceNumber = instance.InstanceNumber; + } + + const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : ''; + const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : ''; + + const { SeriesNumber } = displaySet; + if (SeriesNumber) { + displayTextArray.push(`S: ${SeriesNumber}${instanceText}${frameText}`); + } + + const stats = data.cachedStats[`imageId:${metadata.referencedImageId}`]; + + const roundValues = values => { + if (Array.isArray(values)) { + return values.map(value => { + if (isNaN(value)) { + return value; + } + return utils.roundNumber(value); + }); + } + return isNaN(values) ? values : utils.roundNumber(values); + }; + + const findUnitForValue = (displayTextItems, value) => + displayTextItems.find(({ type, for: filter }) => type === 'unit' && filter.includes(value)) + ?.value; + + const formatDisplayText = (displayName, result, unit) => + `${displayName}: ${Array.isArray(result) ? roundValues(result).join(', ') : roundValues(result)} ${unit}`; + + displayText.forEach(({ displayName, value, type }) => { + if (type === 'value') { + const result = stats[value]; + const unit = stats[findUnitForValue(displayText, value)] || ''; + displayTextArray.push(formatDisplayText(displayName, result, unit)); + } + }); + + return displayTextArray; } export default PlanarFreehandROI; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Probe.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/Probe.ts new file mode 100644 index 000000000..554df1842 --- /dev/null +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Probe.ts @@ -0,0 +1,187 @@ +import SUPPORTED_TOOLS from './constants/supportedTools'; +import { getDisplayUnit } from './utils'; +import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; +import { utils } from '@ohif/core'; + +const Probe = { + toAnnotation: measurement => {}, + + /** + * Maps cornerstone annotation event data to measurement service format. + * + * @param {Object} cornerstone Cornerstone event data + * @return {Measurement} Measurement instance + */ + toMeasurement: ( + csToolsEventDetail, + displaySetService, + CornerstoneViewportService, + getValueTypeFromToolType, + customizationService + ) => { + const { annotation, viewportId } = csToolsEventDetail; + const { metadata, data, annotationUID } = annotation; + + if (!metadata || !data) { + console.warn('Length tool: Missing metadata or data'); + return null; + } + + const { toolName, referencedImageId, FrameOfReferenceUID } = metadata; + const validToolType = SUPPORTED_TOOLS.includes(toolName); + + if (!validToolType) { + throw new Error('Tool not supported'); + } + + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = + getSOPInstanceAttributes(referencedImageId); + + let displaySet; + + if (SOPInstanceUID) { + displaySet = displaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + } else { + displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); + } + + const { points } = data.handles; + + const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); + + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService); + + return { + uid: annotationUID, + SOPInstanceUID, + FrameOfReferenceUID, + points, + metadata, + referenceSeriesUID: SeriesInstanceUID, + referenceStudyUID: StudyInstanceUID, + frameNumber: mappedAnnotations?.[0]?.frameNumber || 1, + toolName: metadata.toolName, + displaySetInstanceUID: displaySet.displaySetInstanceUID, + label: data.label, + displayText: displayText, + data: data.cachedStats, + type: getValueTypeFromToolType(toolName), + getReport, + }; + }, +}; + +function getMappedAnnotations(annotation, DisplaySetService) { + const { metadata, data } = annotation; + const { cachedStats } = data; + const { referencedImageId } = metadata; + const targets = Object.keys(cachedStats); + + if (!targets.length) { + return; + } + + const annotations = []; + Object.keys(cachedStats).forEach(targetId => { + const targetStats = cachedStats[targetId]; + + if (!referencedImageId) { + throw new Error('Non-acquisition plane measurement mapping not supported'); + } + + const { SOPInstanceUID, SeriesInstanceUID, frameNumber } = + getSOPInstanceAttributes(referencedImageId); + + const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID, + frameNumber + ); + + const { SeriesNumber } = displaySet; + const { value } = targetStats; + const unit = 'HU'; + + annotations.push({ + SeriesInstanceUID, + SOPInstanceUID, + SeriesNumber, + frameNumber, + unit, + value, + }); + }); + + return annotations; +} + +/* +This function is used to convert the measurement data to a format that is +suitable for the report generation (e.g. for the csv report). The report +returns a list of columns and corresponding values. +*/ +function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) { + const columns = []; + const values = []; + + // Add Type + columns.push('AnnotationType'); + values.push('Cornerstone:Probe'); + + mappedAnnotations.forEach(annotation => { + const { value, unit } = annotation; + columns.push(`Probe (${unit})`); + values.push(value); + }); + + if (FrameOfReferenceUID) { + columns.push('FrameOfReferenceUID'); + values.push(FrameOfReferenceUID); + } + + if (points) { + columns.push('points'); + values.push(points.map(p => p.join(' ')).join(';')); + } + + return { + columns, + values, + }; +} + +function getDisplayText(mappedAnnotations, displaySet, customizationService) { + if (!mappedAnnotations || !mappedAnnotations.length) { + return ''; + } + + const displayText = []; + + const { value, unit, SeriesNumber, SOPInstanceUID, frameNumber } = mappedAnnotations[0]; + + const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); + + let InstanceNumber; + if (instance) { + InstanceNumber = instance.InstanceNumber; + } + + const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : ''; + const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : ''; + if (value === undefined) { + return displayText; + } + const roundedValue = utils.roundNumber(value, 2); + displayText.push( + `${roundedValue} ${getDisplayUnit(unit)} (S: ${SeriesNumber}${instanceText}${frameText})` + ); + + return displayText; +} + +export default Probe; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts index c7cdb3e41..fe94bfa27 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts @@ -2,6 +2,7 @@ import SUPPORTED_TOOLS from './constants/supportedTools'; import { getDisplayUnit } from './utils'; import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; import { utils } from '@ohif/core'; +import { getStatisticDisplayString } from './utils/getValueDisplayString'; const RectangleROI = { toAnnotation: measurement => {}, @@ -9,7 +10,8 @@ const RectangleROI = { csToolsEventDetail, DisplaySetService, CornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -43,18 +45,20 @@ const RectangleROI = { displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; const mappedAnnotations = getMappedAnnotations(annotation, DisplaySetService); - const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -110,6 +114,7 @@ function getMappedAnnotations(annotation, DisplaySetService) { unit: modalityUnit, mean, stdDev, + metadata, max, area, areaUnit, @@ -124,7 +129,7 @@ This function is used to convert the measurement data to a format that is suitable for the report generation (e.g. for the csv report). The report returns a list of columns and corresponding values. */ -function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { +function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) { const columns = []; const values = []; @@ -162,7 +167,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { }; } -function getDisplayText(mappedAnnotations, displaySet) { +function getDisplayText(mappedAnnotations, displaySet, customizationService) { if (!mappedAnnotations || !mappedAnnotations.length) { return ''; } @@ -190,11 +195,7 @@ function getDisplayText(mappedAnnotations, displaySet) { mappedAnnotations.forEach(mappedAnnotation => { const { unit, max, SeriesNumber } = mappedAnnotation; - let maxStr = ''; - if (max) { - const roundedMax = utils.roundNumber(max, 2); - maxStr = `Max: ${roundedMax} ${getDisplayUnit(unit)} `; - } + const maxStr = getStatisticDisplayString(max, unit, 'max'); const str = `${maxStr}(S:${SeriesNumber}${instanceText}${frameText})`; if (!displayText.includes(str)) { diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/SplineROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/SplineROI.ts new file mode 100644 index 000000000..b1bac9f54 --- /dev/null +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/SplineROI.ts @@ -0,0 +1,187 @@ +import SUPPORTED_TOOLS from './constants/supportedTools'; +import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; +import { utils } from '@ohif/core'; + +/** + * Represents a mapping utility for Spline ROI measurements. + */ +const SplineROI = { + toAnnotation: measurement => {}, + + /** + * Maps cornerstone annotation event data to measurement service format. + * + * @param {Object} csToolsEventDetail Cornerstone event data + * @param {DisplaySetService} DisplaySetService Service for managing display sets + * @param {CornerstoneViewportService} CornerstoneViewportService Service for managing viewports + * @param {Function} getValueTypeFromToolType Function to get value type from tool type + * @returns {Measurement} Measurement instance + */ + toMeasurement: ( + csToolsEventDetail, + DisplaySetService, + CornerstoneViewportService, + getValueTypeFromToolType, + customizationService + ) => { + const { annotation } = csToolsEventDetail; + const { metadata, data, annotationUID } = annotation; + + if (!metadata || !data) { + console.warn('SplineROI tool: Missing metadata or data'); + return null; + } + + const { toolName, referencedImageId, FrameOfReferenceUID } = metadata; + const validToolType = SUPPORTED_TOOLS.includes(toolName); + if (!validToolType) { + throw new Error(`Tool ${toolName} not supported`); + } + + const { SOPInstanceUID, SeriesInstanceUID, frameNumber, StudyInstanceUID } = + getSOPInstanceAttributes(referencedImageId); + + let displaySet; + if (SOPInstanceUID) { + displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + } else { + displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID); + } + + return { + uid: annotationUID, + SOPInstanceUID, + FrameOfReferenceUID, + points: data.contour.polyline, + textBox: data.handles.textBox, + metadata, + frameNumber, + referenceSeriesUID: SeriesInstanceUID, + referenceStudyUID: StudyInstanceUID, + toolName: metadata.toolName, + displaySetInstanceUID: displaySet.displaySetInstanceUID, + label: data.label, + displayText: getDisplayText(annotation, displaySet, customizationService), + data: data.cachedStats, + type: getValueTypeFromToolType(toolName), + getReport: () => getColumnValueReport(annotation, customizationService), + }; + }, +}; + +/** + * This function is used to convert the measurement data to a + * format that is suitable for report generation (e.g. for the csv report). + * The report returns a list of columns and corresponding values. + * + * @param {object} annotation + * @returns {object} Report's content from this tool + */ +function getColumnValueReport(annotation, customizationService) { + const { SplineROI } = customizationService.get('cornerstone.measurements'); + const { report } = SplineROI; + const columns = []; + const values = []; + + /** Add type */ + columns.push('AnnotationType'); + values.push('Cornerstone:SplineROI'); + + /** Add cachedStats */ + const { metadata, data } = annotation; + const stats = data.cachedStats[`imageId:${metadata.referencedImageId}`]; + + report.forEach(({ name, value }) => { + columns.push(name); + stats[value] ? values.push(stats[value]) : values.push('not available'); + }); + + /** Add FOR */ + if (metadata.FrameOfReferenceUID) { + columns.push('FrameOfReferenceUID'); + values.push(metadata.FrameOfReferenceUID); + } + + /** Add points */ + if (data.contour.polyline) { + /** + * Points has the form of [[x1, y1, z1], [x2, y2, z2], ...] + * convert it to string of [[x1 y1 z1];[x2 y2 z2];...] + * so that it can be used in the CSV report + */ + columns.push('points'); + values.push(data.contour.polyline.map(p => p.join(' ')).join(';')); + } + + return { columns, values }; +} + +/** + * Retrieves the display text for an annotation in a display set. + * + * @param {Object} annotation - The annotation object. + * @param {Object} displaySet - The display set object. + * @returns {string[]} - An array of display text. + */ +function getDisplayText(annotation, displaySet, customizationService) { + const { SplineROI } = customizationService.get('cornerstone.measurements'); + const { displayText } = SplineROI; + const { metadata, data } = annotation; + + if (!data.cachedStats || !data.cachedStats[`imageId:${metadata.referencedImageId}`]) { + return []; + } + const { SOPInstanceUID, frameNumber } = getSOPInstanceAttributes(metadata.referencedImageId); + + const displayTextArray = []; + + const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); + let InstanceNumber; + if (instance) { + InstanceNumber = instance.InstanceNumber; + } + + const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : ''; + const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : ''; + + const { SeriesNumber } = displaySet; + if (SeriesNumber) { + displayTextArray.push(`S: ${SeriesNumber}${instanceText}${frameText}`); + } + + const stats = data.cachedStats[`imageId:${metadata.referencedImageId}`]; + + const roundValues = values => { + if (Array.isArray(values)) { + return values.map(value => { + if (isNaN(value)) { + return value; + } + return utils.roundNumber(value); + }); + } + return isNaN(values) ? values : utils.roundNumber(values); + }; + + const findUnitForValue = (displayTextItems, value) => + displayTextItems.find(({ type, for: filter }) => type === 'unit' && filter.includes(value)) + ?.value; + + const formatDisplayText = (displayName, result, unit) => + `${displayName}: ${Array.isArray(result) ? roundValues(result).join(', ') : roundValues(result)} ${unit}`; + + displayText.forEach(({ displayName, value, type }) => { + if (type === 'value') { + const result = stats[value]; + const unit = stats[findUnitForValue(displayText, value)] || ''; + displayTextArray.push(formatDisplayText(displayName, result, unit)); + } + }); + + return displayTextArray; +} + +export default SplineROI; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/UltrasoundDirectional.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/UltrasoundDirectional.ts new file mode 100644 index 000000000..f88973ae9 --- /dev/null +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/UltrasoundDirectional.ts @@ -0,0 +1,206 @@ +import SUPPORTED_TOOLS from './constants/supportedTools'; +import { getDisplayUnit } from './utils'; +import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; +import { utils } from '@ohif/core'; + +const UltrasoundDirectional = { + toAnnotation: measurement => {}, + + /** + * Maps cornerstone annotation event data to measurement service format. + * + * @param {Object} cornerstone Cornerstone event data + * @return {Measurement} Measurement instance + */ + toMeasurement: ( + csToolsEventDetail, + displaySetService, + CornerstoneViewportService, + getValueTypeFromToolType, + customizationService + ) => { + const { annotation, viewportId } = csToolsEventDetail; + const { metadata, data, annotationUID } = annotation; + + if (!metadata || !data) { + console.warn('Length tool: Missing metadata or data'); + return null; + } + + const { toolName, referencedImageId, FrameOfReferenceUID } = metadata; + const validToolType = SUPPORTED_TOOLS.includes(toolName); + + if (!validToolType) { + throw new Error('Tool not supported'); + } + + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = + getSOPInstanceAttributes(referencedImageId); + + let displaySet; + + if (SOPInstanceUID) { + displaySet = displaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + } else { + displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); + } + + const { points } = data.handles; + + const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); + + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService); + + return { + uid: annotationUID, + SOPInstanceUID, + FrameOfReferenceUID, + points, + metadata, + referenceSeriesUID: SeriesInstanceUID, + referenceStudyUID: StudyInstanceUID, + frameNumber: mappedAnnotations?.[0]?.frameNumber || 1, + toolName: metadata.toolName, + displaySetInstanceUID: displaySet.displaySetInstanceUID, + label: data.label, + displayText: displayText, + data: data.cachedStats, + type: getValueTypeFromToolType(toolName), + getReport, + }; + }, +}; + +function getMappedAnnotations(annotation, DisplaySetService) { + const { metadata, data } = annotation; + const { cachedStats } = data; + const { referencedImageId } = metadata; + const targets = Object.keys(cachedStats); + + if (!targets.length) { + return; + } + + const annotations = []; + Object.keys(cachedStats).forEach(targetId => { + const targetStats = cachedStats[targetId]; + + if (!referencedImageId) { + throw new Error('Non-acquisition plane measurement mapping not supported'); + } + + const { SOPInstanceUID, SeriesInstanceUID, frameNumber } = + getSOPInstanceAttributes(referencedImageId); + + const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID, + frameNumber + ); + + const { SeriesNumber } = displaySet; + const { xValues, yValues, units, isUnitless, isHorizontal } = targetStats; + + annotations.push({ + SeriesInstanceUID, + SOPInstanceUID, + SeriesNumber, + frameNumber, + xValues, + yValues, + units, + isUnitless, + isHorizontal, + }); + }); + + return annotations; +} + +/* +This function is used to convert the measurement data to a format that is +suitable for the report generation (e.g. for the csv report). The report +returns a list of columns and corresponding values. +*/ +function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) { + const columns = []; + const values = []; + + // Add Type + columns.push('AnnotationType'); + values.push('Cornerstone:UltrasoundDirectional'); + + mappedAnnotations.forEach(annotation => { + const { xValues, yValues, units, isUnitless } = annotation; + if (isUnitless) { + columns.push('Length' + units[0]); + values.push(utils.roundNumber(xValues[0], 2)); + } else { + const dist1 = Math.abs(xValues[1] - xValues[0]); + const dist2 = Math.abs(yValues[1] - yValues[0]); + columns.push('Time' + units[0]); + values.push(utils.roundNumber(dist1, 2)); + columns.push('Length' + units[1]); + values.push(utils.roundNumber(dist2, 2)); + } + }); + + if (FrameOfReferenceUID) { + columns.push('FrameOfReferenceUID'); + values.push(FrameOfReferenceUID); + } + + if (points) { + columns.push('points'); + values.push(points.map(p => p.join(' ')).join(';')); + } + + return { + columns, + values, + }; +} + +function getDisplayText(mappedAnnotations, displaySet, customizationService) { + if (!mappedAnnotations || !mappedAnnotations.length) { + return ''; + } + + const displayText = []; + + const { xValues, yValues, units, isUnitless, SeriesNumber, SOPInstanceUID, frameNumber } = + mappedAnnotations[0]; + + const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); + + let InstanceNumber; + if (instance) { + InstanceNumber = instance.InstanceNumber; + } + + const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : ''; + const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : ''; + const seriesText = `(S: ${SeriesNumber}${instanceText}${frameText})`; + + if (xValues === undefined || yValues === undefined) { + return displayText; + } + + if (isUnitless) { + displayText.push(`${utils.roundNumber(xValues[0], 2)} ${units[0]} ${seriesText}`); + } else { + const dist1 = Math.abs(xValues[1] - xValues[0]); + const dist2 = Math.abs(yValues[1] - yValues[0]); + displayText.push(`${utils.roundNumber(dist1)} ${units[0]} ${seriesText}`); + displayText.push(`${utils.roundNumber(dist2)} ${units[1]} ${seriesText}`); + } + + return displayText; +} + +export default UltrasoundDirectional; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js b/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js index e1e8a1755..9f7bbb04a 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js @@ -9,4 +9,8 @@ export default [ 'Probe', 'RectangleROI', 'PlanarFreehandROI', + 'SplineROI', + 'LivewireContour', + 'Probe', + 'UltrasoundDirectionalTool', ]; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts index e3ccf4e51..0cc991fe7 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts @@ -8,11 +8,16 @@ import CobbAngle from './CobbAngle'; import Angle from './Angle'; import PlanarFreehandROI from './PlanarFreehandROI'; import RectangleROI from './RectangleROI'; +import SplineROI from './SplineROI'; +import LivewireContour from './LivewireContour'; +import Probe from './Probe'; +import UltrasoundDirectional from './UltrasoundDirectional'; const measurementServiceMappingsFactory = ( measurementService: MeasurementService, displaySetService, - cornerstoneViewportService + cornerstoneViewportService, + customizationService ) => { /** * Maps measurement service format object to cornerstone annotation object. @@ -39,6 +44,10 @@ const measurementServiceMappingsFactory = ( ArrowAnnotate: POINT, CobbAngle: ANGLE, Angle: ANGLE, + SplineROI: POLYLINE, + LivewireContour: POLYLINE, + Probe: POINT, + UltrasoundDirectional: POLYLINE, }; return TOOL_TYPE_TO_VALUE_TYPE[toolType]; @@ -52,7 +61,8 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ { @@ -68,7 +78,8 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ // TODO -> We should eventually do something like shortAxis + longAxis, @@ -91,7 +102,8 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ { @@ -107,7 +119,8 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ { @@ -123,7 +136,8 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ { @@ -139,7 +153,42 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService + ), + matchingCriteria: [ + { + valueType: MeasurementService.VALUE_TYPES.POLYLINE, + }, + ], + }, + + SplineROI: { + toAnnotation: SplineROI.toAnnotation, + toMeasurement: csToolsAnnotation => + SplineROI.toMeasurement( + csToolsAnnotation, + displaySetService, + cornerstoneViewportService, + _getValueTypeFromToolType, + customizationService + ), + matchingCriteria: [ + { + valueType: MeasurementService.VALUE_TYPES.POLYLINE, + }, + ], + }, + + LivewireContour: { + toAnnotation: LivewireContour.toAnnotation, + toMeasurement: csToolsAnnotation => + LivewireContour.toMeasurement( + csToolsAnnotation, + displaySetService, + cornerstoneViewportService, + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ { @@ -155,7 +204,26 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService + ), + matchingCriteria: [ + { + valueType: MeasurementService.VALUE_TYPES.POINT, + points: 1, + }, + ], + }, + + Probe: { + toAnnotation: Probe.toAnnotation, + toMeasurement: csToolsAnnotation => + Probe.toMeasurement( + csToolsAnnotation, + displaySetService, + cornerstoneViewportService, + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ { @@ -172,7 +240,8 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ { @@ -188,7 +257,8 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ { @@ -196,6 +266,23 @@ const measurementServiceMappingsFactory = ( }, ], }, + UltrasoundDirectional: { + toAnnotation: UltrasoundDirectional.toAnnotation, + toMeasurement: csToolsAnnotation => + UltrasoundDirectional.toMeasurement( + csToolsAnnotation, + displaySetService, + cornerstoneViewportService, + _getValueTypeFromToolType, + customizationService + ), + matchingCriteria: [ + { + valueType: MeasurementService.VALUE_TYPES.POLYLINE, + points: 2, + }, + ], + }, }; return factories; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getValueDisplayString.js b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getValueDisplayString.js new file mode 100644 index 000000000..aa0fe316c --- /dev/null +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getValueDisplayString.js @@ -0,0 +1,12 @@ +import { utils } from '@ohif/core'; +import getDisplayUnit from './getDisplayUnit'; + +export const getStatisticDisplayString = (numbers, unit, key) => { + if (Array.isArray(numbers) && numbers.length > 0) { + const results = numbers.map(number => utils.roundNumber(number, 2)); + return `${key.charAt(0).toUpperCase() + key.slice(1)}: ${results.join(', ')} ${getDisplayUnit(unit)}`; + } + + const result = utils.roundNumber(numbers, 2); + return `${key.charAt(0).toUpperCase() + key.slice(1)}: ${result} ${getDisplayUnit(unit)}`; +}; diff --git a/extensions/cornerstone/src/utils/toggleVOISliceSync.ts b/extensions/cornerstone/src/utils/toggleVOISliceSync.ts new file mode 100644 index 000000000..0677cff37 --- /dev/null +++ b/extensions/cornerstone/src/utils/toggleVOISliceSync.ts @@ -0,0 +1,94 @@ +const VOI_SYNC_NAME = 'VOI_SYNC'; + +const getSyncId = modality => `${VOI_SYNC_NAME}_${modality}`; + +export default function toggleVOISliceSync({ + servicesManager, + viewports: providedViewports, + syncId, +}) { + const { syncGroupService, viewportGridService, displaySetService, cornerstoneViewportService } = + servicesManager.services; + + const viewports = + providedViewports || groupViewportsByModality(viewportGridService, displaySetService); + + // Todo: right now we don't have a proper way to define specific + // viewports to add to synchronizers, and right now it is global or not + // after we do that, we should do fine grained control of the synchronizers + + // we can apply voi sync within each modality group + for (const [modality, modalityViewports] of Object.entries(viewports)) { + const syncIdToUse = syncId || getSyncId(modality); + + const someViewportHasSync = modalityViewports.some(viewport => { + const syncStates = syncGroupService.getSynchronizersForViewport( + viewport.viewportOptions.viewportId + ); + + const imageSync = syncStates.find(syncState => syncState.id === syncIdToUse); + + return !!imageSync; + }); + + if (someViewportHasSync) { + return disableSync(modalityViewports, syncIdToUse, servicesManager); + } + + // create synchronization group and add the modalityViewports to it. + modalityViewports.forEach(gridViewport => { + const { viewportId } = gridViewport.viewportOptions; + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + if (!viewport) { + return; + } + syncGroupService.addViewportToSyncGroup(viewportId, viewport.getRenderingEngine().id, { + type: 'voi', + id: syncIdToUse, + source: true, + target: true, + }); + }); + } +} + +function disableSync(modalityViewports, syncId, servicesManager) { + const { syncGroupService, cornerstoneViewportService } = servicesManager.services; + + const viewports = modalityViewports; + viewports.forEach(gridViewport => { + const { viewportId } = gridViewport.viewportOptions; + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + if (!viewport) { + return; + } + syncGroupService.removeViewportFromSyncGroup( + viewport.id, + viewport.getRenderingEngine().id, + syncId + ); + }); +} + +function groupViewportsByModality(viewportGridService, displaySetService) { + let { viewports } = viewportGridService.getState(); + + viewports = [...viewports.values()]; + + // group the viewports by modality + return viewports.reduce((acc, viewport) => { + const { displaySetInstanceUIDs } = viewport; + // Todo: add proper fusion support + const displaySetInstanceUID = displaySetInstanceUIDs[0]; + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); + + const modality = displaySet.Modality; + if (!acc[modality]) { + acc[modality] = []; + } + + acc[modality].push(viewport); + + return acc; + }, {}); +} diff --git a/extensions/default/CHANGELOG.md b/extensions/default/CHANGELOG.md index de6d50fd8..4cf2d5680 100644 --- a/extensions/default/CHANGELOG.md +++ b/extensions/default/CHANGELOG.md @@ -3,7 +3,853 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -# [3.7.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.7.0) (2023-10-11) +# [3.8.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.92...v3.8.0-beta.93) (2024-04-29) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.91...v3.8.0-beta.92) (2024-04-28) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.90...v3.8.0-beta.91) (2024-04-25) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.89...v3.8.0-beta.90) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.88...v3.8.0-beta.89) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.87...v3.8.0-beta.88) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.86...v3.8.0-beta.87) (2024-04-19) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.85...v3.8.0-beta.86) (2024-04-19) + + +### Bug Fixes + +* **layouts:** and fix thumbnail in touch and update migration guide for 3.8 release ([#4052](https://github.com/OHIF/Viewers/issues/4052)) ([d250d04](https://github.com/OHIF/Viewers/commit/d250d04580883446fcb8d748b2a97c5c198922af)) + + + + + +# [3.8.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.84...v3.8.0-beta.85) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.83...v3.8.0-beta.84) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.82...v3.8.0-beta.83) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.81...v3.8.0-beta.82) (2024-04-17) + + +### Bug Fixes + +* **bugs:** enhancements and bug fixes - more ([#4043](https://github.com/OHIF/Viewers/issues/4043)) ([3754c22](https://github.com/OHIF/Viewers/commit/3754c224b4dab28182adb0a41e37d890942144d8)) + + + + + +# [3.8.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.80...v3.8.0-beta.81) (2024-04-16) + + +### Bug Fixes + +* **viewport:** Reset viewport state and fix CINE looping, thumbnail resolution, and dynamic tool settings ([#4037](https://github.com/OHIF/Viewers/issues/4037)) ([f99a0bf](https://github.com/OHIF/Viewers/commit/f99a0bfb31434aa137bbb3ed1f9eef1dfcc09025)) + + + + + +# [3.8.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.79...v3.8.0-beta.80) (2024-04-16) + + +### Bug Fixes + +* **bugs:** enhancements and bug fixes ([#4036](https://github.com/OHIF/Viewers/issues/4036)) ([e80fc6f](https://github.com/OHIF/Viewers/commit/e80fc6f47708e1d6b1a1e1de438196a4b74ec637)) + + + + + +# [3.8.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.78...v3.8.0-beta.79) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.77...v3.8.0-beta.78) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.76...v3.8.0-beta.77) (2024-04-10) + + +### Bug Fixes + +* **dicom-video:** Update get direct func for dicom json to use url if present and fix config argument ([#4017](https://github.com/OHIF/Viewers/issues/4017)) ([4f99244](https://github.com/OHIF/Viewers/commit/4f99244d864427d69be6f863cb7a6a78411adb12)) + + + + + +# [3.8.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.75...v3.8.0-beta.76) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.74...v3.8.0-beta.75) (2024-04-10) + + +### Bug Fixes + +* Microscopy bulkdata and image retrieve ([#3894](https://github.com/OHIF/Viewers/issues/3894)) ([7fac49b](https://github.com/OHIF/Viewers/commit/7fac49b4492b4bd5e9ece8e2e2b0fa2faa840d7f)) + + + + + +# [3.8.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.73...v3.8.0-beta.74) (2024-04-10) + + +### Features + +* **4D:** Add 4D dynamic volume rendering and new pre-clinical 4d pt/ct mode ([#3664](https://github.com/OHIF/Viewers/issues/3664)) ([d57e8bc](https://github.com/OHIF/Viewers/commit/d57e8bc1571c6da4effaa492ee2d162c552365a2)) + + + + + +# [3.8.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.72...v3.8.0-beta.73) (2024-04-08) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.71...v3.8.0-beta.72) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.70...v3.8.0-beta.71) (2024-04-05) + + +### Features + +* **advanced-roi-tools:** new tools and icon updates and overlay bug fixes ([#4014](https://github.com/OHIF/Viewers/issues/4014)) ([cea27d4](https://github.com/OHIF/Viewers/commit/cea27d438d1de2c1ec90cbaefdc2b31a1d9980a1)) + + + + + +# [3.8.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.69...v3.8.0-beta.70) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.68...v3.8.0-beta.69) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.67...v3.8.0-beta.68) (2024-04-03) + + +### Features + +* **segmentation:** Enhanced segmentation panel design for TMTV ([#3988](https://github.com/OHIF/Viewers/issues/3988)) ([9f3235f](https://github.com/OHIF/Viewers/commit/9f3235ff096636aafa88d8a42859e8dc85d9036d)) + + + + + +# [3.8.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.66...v3.8.0-beta.67) (2024-04-02) + + +### Features + +* **ViewportActionMenu:** window level per viewport / new patient info / colorbars/ 3D presets and 3D volume rendering ([#3963](https://github.com/OHIF/Viewers/issues/3963)) ([b7f90e3](https://github.com/OHIF/Viewers/commit/b7f90e3951845396f99b69f0a74fc56b2ffeada1)) + + + + + +# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28) + + +### Bug Fixes + +* **new layout:** address black screen bugs ([#4008](https://github.com/OHIF/Viewers/issues/4008)) ([158a181](https://github.com/OHIF/Viewers/commit/158a1816703e0ad66cae08cb9bd1ffb93bbd8d43)) + + + + + +# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28) + + +### Features + +* **layout:** new layout selector with 3D volume rendering ([#3923](https://github.com/OHIF/Viewers/issues/3923)) ([617043f](https://github.com/OHIF/Viewers/commit/617043fe0da5de91fbea4ac33a27f1df16ae1ca6)) + + + + + +# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27) + + +### Features + +* **toolbar:** new Toolbar to enable reactive state synchronization ([#3983](https://github.com/OHIF/Viewers/issues/3983)) ([566b25a](https://github.com/OHIF/Viewers/commit/566b25a54425399096864bd263193646556011a5)) + + + + + +# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25) + + +### Features + +* **worklist:** new investigational use text ([#3999](https://github.com/OHIF/Viewers/issues/3999)) ([45b68e8](https://github.com/OHIF/Viewers/commit/45b68e841dcb9e28a2ea991c37ee7ac4a8c5b71e)) + + + + + +# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + + +### Bug Fixes + +* **demo:** Deploy issue ([#3951](https://github.com/OHIF/Viewers/issues/3951)) ([21e8a2b](https://github.com/OHIF/Viewers/commit/21e8a2bd0b7cc72f90a31e472d285d761be15d30)) + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + + +### Bug Fixes + +* 🐛 Sort merge results based on default data source (input) ([#3903](https://github.com/OHIF/Viewers/issues/3903)) ([5bba98e](https://github.com/OHIF/Viewers/commit/5bba98ed848bdf46b5ba4fc4708527cced3308b5)) + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + + +### Bug Fixes + +* catch errors in getPTImageIdInstanceMetadata ([#3897](https://github.com/OHIF/Viewers/issues/3897)) ([a47aeb8](https://github.com/OHIF/Viewers/commit/a47aeb8bd729dcb8d2cfc13b27a31b0dd88f11ad)) + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + + +### Bug Fixes + +* 🐛 Check merge key for merge data source ([#3901](https://github.com/OHIF/Viewers/issues/3901)) ([911d672](https://github.com/OHIF/Viewers/commit/911d67283536b2fe7930948f2819ea0ad66e2a32)) + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + + +### Features + +* **hp:** enable OHIF to run with partial metadata for large studies at the cost of less effective hanging protocol ([#3804](https://github.com/OHIF/Viewers/issues/3804)) ([0049f4c](https://github.com/OHIF/Viewers/commit/0049f4c0303f0b6ea995972326fc8784259f5a47)) + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + + +### Features + +* **ui:** sidePanel expandedWidth ([#3728](https://github.com/OHIF/Viewers/issues/3728)) ([61bf22c](https://github.com/OHIF/Viewers/commit/61bf22c6f80e764bdf5c3b56bb0124a95aa0f793)) + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + + +### Features + +* improve disableEditing flag ([#3875](https://github.com/OHIF/Viewers/issues/3875)) ([2049c09](https://github.com/OHIF/Viewers/commit/2049c0936c86f819604c243d3dc7b3fe971b5b2c)) + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + + +### Bug Fixes + +* PDF display request in v3 ([#3878](https://github.com/OHIF/Viewers/issues/3878)) ([9865030](https://github.com/OHIF/Viewers/commit/98650302c7575f0aea386e32cfc4112c378035e6)) + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + + +### Features + +* **customizationService:** Enable saving and loading of private tags in SRs ([#3842](https://github.com/OHIF/Viewers/issues/3842)) ([e1f55e6](https://github.com/OHIF/Viewers/commit/e1f55e65f2d2a34136ad5d0b1ada77d337a0ea23)) + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + + +### Features + +* **i18n:** enhanced i18n support ([#3761](https://github.com/OHIF/Viewers/issues/3761)) ([d14a8f0](https://github.com/OHIF/Viewers/commit/d14a8f0199db95cd9e85866a011b64d6bf830d57)) + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + + +### Bug Fixes + +* **SM:** drag and drop is now fixed for SM ([#3813](https://github.com/OHIF/Viewers/issues/3813)) ([f1a6764](https://github.com/OHIF/Viewers/commit/f1a67647aed635437b188cea7cf5d5a8fb974bbe)) + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + + +### Features + +* Merge Data Source ([#3788](https://github.com/OHIF/Viewers/issues/3788)) ([c4ff2c2](https://github.com/OHIF/Viewers/commit/c4ff2c2f09546ce8b72eab9c5e7beed611e3cab0)) + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + + +### Features + +* **url:** Add SeriesInstanceUIDs wado query param ([#3746](https://github.com/OHIF/Viewers/issues/3746)) ([b694228](https://github.com/OHIF/Viewers/commit/b694228dd535e4b97cb86a1dc085b6e8716bdaf3)) + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + + +### Features + +* **dicomJSON:** Add Loading Other Display Sets and JSON Metadata Generation script ([#3777](https://github.com/OHIF/Viewers/issues/3777)) ([43b1c17](https://github.com/OHIF/Viewers/commit/43b1c17209502e4876ad59bae09ed9442eda8024)) + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + + +### Features + +* **hp callback:** Add viewport ready callback ([#3772](https://github.com/OHIF/Viewers/issues/3772)) ([bf252bc](https://github.com/OHIF/Viewers/commit/bf252bcec2aae3a00479fdcb732110b344bcf2c0)) + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + + +### Bug Fixes + +* **thumbnail:** Avoid multiple promise creations for thumbnails ([#3756](https://github.com/OHIF/Viewers/issues/3756)) ([b23eeff](https://github.com/OHIF/Viewers/commit/b23eeff93745769e67e60c33d75293d6242c5ec9)) + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + + +### Features + +* **i18n:** enhanced i18n support ([#3730](https://github.com/OHIF/Viewers/issues/3730)) ([330e11c](https://github.com/OHIF/Viewers/commit/330e11c7ff0151e1096e19b8ffdae7d64cae280e)) + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + + +### Bug Fixes + +* **toolbar:** allow customizable toolbar for active viewport and allow active tool to be deactivated via a click ([#3608](https://github.com/OHIF/Viewers/issues/3608)) ([dd6d976](https://github.com/OHIF/Viewers/commit/dd6d9768bbca1d3cc472e8c1e6d85822500b96ef)) + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + + +### Bug Fixes + +* **sr:** dcm4chee requires the patient name for an SR to match what is in the original study ([#3739](https://github.com/OHIF/Viewers/issues/3739)) ([d98439f](https://github.com/OHIF/Viewers/commit/d98439fe7f3825076dbc87b664a1d1480ff414d3)) + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) **Note:** Version bump only for package @ohif/extension-default diff --git a/extensions/default/assets/images/CT-AAA.png b/extensions/default/assets/images/CT-AAA.png new file mode 100644 index 000000000..67c6bf778 Binary files /dev/null and b/extensions/default/assets/images/CT-AAA.png differ diff --git a/extensions/default/assets/images/CT-AAA2.png b/extensions/default/assets/images/CT-AAA2.png new file mode 100644 index 000000000..4c51a6c28 Binary files /dev/null and b/extensions/default/assets/images/CT-AAA2.png differ diff --git a/extensions/default/assets/images/CT-Air.png b/extensions/default/assets/images/CT-Air.png new file mode 100644 index 000000000..a65680aa4 Binary files /dev/null and b/extensions/default/assets/images/CT-Air.png differ diff --git a/extensions/default/assets/images/CT-Bone.png b/extensions/default/assets/images/CT-Bone.png new file mode 100644 index 000000000..7c3f8c9ef Binary files /dev/null and b/extensions/default/assets/images/CT-Bone.png differ diff --git a/extensions/default/assets/images/CT-Bones.png b/extensions/default/assets/images/CT-Bones.png new file mode 100644 index 000000000..441d6bf3a Binary files /dev/null and b/extensions/default/assets/images/CT-Bones.png differ diff --git a/extensions/default/assets/images/CT-Cardiac.png b/extensions/default/assets/images/CT-Cardiac.png new file mode 100644 index 000000000..3f9daadb3 Binary files /dev/null and b/extensions/default/assets/images/CT-Cardiac.png differ diff --git a/extensions/default/assets/images/CT-Cardiac2.png b/extensions/default/assets/images/CT-Cardiac2.png new file mode 100644 index 000000000..a281b2425 Binary files /dev/null and b/extensions/default/assets/images/CT-Cardiac2.png differ diff --git a/extensions/default/assets/images/CT-Cardiac3.png b/extensions/default/assets/images/CT-Cardiac3.png new file mode 100644 index 000000000..0b8773ef8 Binary files /dev/null and b/extensions/default/assets/images/CT-Cardiac3.png differ diff --git a/extensions/default/assets/images/CT-Chest-Contrast-Enhanced.png b/extensions/default/assets/images/CT-Chest-Contrast-Enhanced.png new file mode 100644 index 000000000..be165b4c0 Binary files /dev/null and b/extensions/default/assets/images/CT-Chest-Contrast-Enhanced.png differ diff --git a/extensions/default/assets/images/CT-Chest-Vessels.png b/extensions/default/assets/images/CT-Chest-Vessels.png new file mode 100644 index 000000000..23f8732c5 Binary files /dev/null and b/extensions/default/assets/images/CT-Chest-Vessels.png differ diff --git a/extensions/default/assets/images/CT-Coronary-Arteries-2.png b/extensions/default/assets/images/CT-Coronary-Arteries-2.png new file mode 100644 index 000000000..1b6b16100 Binary files /dev/null and b/extensions/default/assets/images/CT-Coronary-Arteries-2.png differ diff --git a/extensions/default/assets/images/CT-Coronary-Arteries-3.png b/extensions/default/assets/images/CT-Coronary-Arteries-3.png new file mode 100644 index 000000000..088a28611 Binary files /dev/null and b/extensions/default/assets/images/CT-Coronary-Arteries-3.png differ diff --git a/extensions/default/assets/images/CT-Coronary-Arteries.png b/extensions/default/assets/images/CT-Coronary-Arteries.png new file mode 100644 index 000000000..3b32f1b73 Binary files /dev/null and b/extensions/default/assets/images/CT-Coronary-Arteries.png differ diff --git a/extensions/default/assets/images/CT-Cropped-Volume-Bone.png b/extensions/default/assets/images/CT-Cropped-Volume-Bone.png new file mode 100644 index 000000000..13c0922ed Binary files /dev/null and b/extensions/default/assets/images/CT-Cropped-Volume-Bone.png differ diff --git a/extensions/default/assets/images/CT-Fat.png b/extensions/default/assets/images/CT-Fat.png new file mode 100644 index 000000000..9cdd78a25 Binary files /dev/null and b/extensions/default/assets/images/CT-Fat.png differ diff --git a/extensions/default/assets/images/CT-Liver-Vasculature.png b/extensions/default/assets/images/CT-Liver-Vasculature.png new file mode 100644 index 000000000..b33856d12 Binary files /dev/null and b/extensions/default/assets/images/CT-Liver-Vasculature.png differ diff --git a/extensions/default/assets/images/CT-Lung.png b/extensions/default/assets/images/CT-Lung.png new file mode 100644 index 000000000..158f3d7b2 Binary files /dev/null and b/extensions/default/assets/images/CT-Lung.png differ diff --git a/extensions/default/assets/images/CT-MIP.png b/extensions/default/assets/images/CT-MIP.png new file mode 100644 index 000000000..30a93561e Binary files /dev/null and b/extensions/default/assets/images/CT-MIP.png differ diff --git a/extensions/default/assets/images/CT-Muscle.png b/extensions/default/assets/images/CT-Muscle.png new file mode 100644 index 000000000..76ecdc41d Binary files /dev/null and b/extensions/default/assets/images/CT-Muscle.png differ diff --git a/extensions/default/assets/images/CT-Pulmonary-Arteries.png b/extensions/default/assets/images/CT-Pulmonary-Arteries.png new file mode 100644 index 000000000..4558000e0 Binary files /dev/null and b/extensions/default/assets/images/CT-Pulmonary-Arteries.png differ diff --git a/extensions/default/assets/images/CT-Soft-Tissue.png b/extensions/default/assets/images/CT-Soft-Tissue.png new file mode 100644 index 000000000..f03690019 Binary files /dev/null and b/extensions/default/assets/images/CT-Soft-Tissue.png differ diff --git a/extensions/default/assets/images/DTI-FA-Brain.png b/extensions/default/assets/images/DTI-FA-Brain.png new file mode 100644 index 000000000..964354622 Binary files /dev/null and b/extensions/default/assets/images/DTI-FA-Brain.png differ diff --git a/extensions/default/assets/images/MR-Angio.png b/extensions/default/assets/images/MR-Angio.png new file mode 100644 index 000000000..f54d6fa5a Binary files /dev/null and b/extensions/default/assets/images/MR-Angio.png differ diff --git a/extensions/default/assets/images/MR-Default.png b/extensions/default/assets/images/MR-Default.png new file mode 100644 index 000000000..f8bf302c1 Binary files /dev/null and b/extensions/default/assets/images/MR-Default.png differ diff --git a/extensions/default/assets/images/MR-MIP.png b/extensions/default/assets/images/MR-MIP.png new file mode 100644 index 000000000..8b3e91a24 Binary files /dev/null and b/extensions/default/assets/images/MR-MIP.png differ diff --git a/extensions/default/assets/images/MR-T2-Brain.png b/extensions/default/assets/images/MR-T2-Brain.png new file mode 100644 index 000000000..8b1f7a550 Binary files /dev/null and b/extensions/default/assets/images/MR-T2-Brain.png differ diff --git a/extensions/default/assets/images/VolumeRendering.png b/extensions/default/assets/images/VolumeRendering.png new file mode 100644 index 000000000..8d7313ea2 Binary files /dev/null and b/extensions/default/assets/images/VolumeRendering.png differ diff --git a/extensions/default/babel.config.js b/extensions/default/babel.config.js new file mode 100644 index 000000000..325ca2a8e --- /dev/null +++ b/extensions/default/babel.config.js @@ -0,0 +1 @@ +module.exports = require('../../babel.config.js'); diff --git a/extensions/default/jest.config.js b/extensions/default/jest.config.js new file mode 100644 index 000000000..ba90c0c47 --- /dev/null +++ b/extensions/default/jest.config.js @@ -0,0 +1,17 @@ +const base = require('../../jest.config.base.js'); +const pkg = require('./package'); + +module.exports = { + ...base, + name: pkg.name, + displayName: pkg.name, + moduleNameMapper: { + ...base.moduleNameMapper, + '@ohif/(.*)': '/../../platform/$1/src', + }, + // rootDir: "../.." + // testMatch: [ + // //`/platform/${pack.name}/**/*.spec.js` + // "/platform/app/**/*.test.js" + // ] +}; diff --git a/extensions/default/package.json b/extensions/default/package.json index a0875c8f3..58d5edd9d 100644 --- a/extensions/default/package.json +++ b/extensions/default/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-default", - "version": "3.7.0", + "version": "3.8.0-beta.93", "description": "Common/default features and functionality for basic image viewing", "author": "OHIF Core Team", "license": "MIT", @@ -23,6 +23,8 @@ "ohif-extension" ], "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "dev:dicom-pdf": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", @@ -30,10 +32,10 @@ "start": "yarn run dev" }, "peerDependencies": { - "@ohif/core": "3.7.0", - "@ohif/i18n": "3.7.0", - "dcmjs": "^0.29.5", - "dicomweb-client": "^0.10.2", + "@ohif/core": "3.8.0-beta.93", + "@ohif/i18n": "3.8.0-beta.93", + "dcmjs": "^0.29.12", + "dicomweb-client": "^0.10.4", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/extensions/default/src/Components/LineChartViewport/LineChartViewport.tsx b/extensions/default/src/Components/LineChartViewport/LineChartViewport.tsx new file mode 100644 index 000000000..4f6115695 --- /dev/null +++ b/extensions/default/src/Components/LineChartViewport/LineChartViewport.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { LineChart } from '@ohif/ui'; + +const LineChartViewport = ({ displaySets }) => { + const displaySet = displaySets[0]; + const { axis: chartAxis, series: chartSeries } = displaySet.instance.chartData; + + return ( + + ); +}; + +export { LineChartViewport as default }; diff --git a/extensions/default/src/Components/LineChartViewport/index.ts b/extensions/default/src/Components/LineChartViewport/index.ts new file mode 100644 index 000000000..0871906e0 --- /dev/null +++ b/extensions/default/src/Components/LineChartViewport/index.ts @@ -0,0 +1 @@ +export { default } from './LineChartViewport'; diff --git a/extensions/default/src/Components/SidePanelWithServices.tsx b/extensions/default/src/Components/SidePanelWithServices.tsx index 23e9841b0..5ca05df4e 100644 --- a/extensions/default/src/Components/SidePanelWithServices.tsx +++ b/extensions/default/src/Components/SidePanelWithServices.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import { SidePanel } from '@ohif/ui'; -import { PanelService, ServicesManager } from '@ohif/core'; +import { PanelService, ServicesManager, Types } from '@ohif/core'; export type SidePanelWithServicesProps = { servicesManager: ServicesManager; @@ -8,51 +8,82 @@ export type SidePanelWithServicesProps = { className: string; activeTabIndex: number; tabs: any; + expandedWidth?: number; }; const SidePanelWithServices = ({ servicesManager, side, - className, activeTabIndex: activeTabIndexProp, - tabs, -}) => { + tabs: tabsProp, + expandedWidth, + ...props +}: SidePanelWithServicesProps) => { const panelService: PanelService = servicesManager?.services?.panelService; // Tracks whether this SidePanel has been opened at least once since this SidePanel was inserted into the DOM. // Thus going to the Study List page and back to the viewer resets this flag for a SidePanel. const [hasBeenOpened, setHasBeenOpened] = useState(false); const [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp); + const [tabs, setTabs] = useState(tabsProp ?? panelService.getPanels(side)); + + const handleSidePanelOpen = useCallback(() => { + setHasBeenOpened(true); + }, []); + + const handleActiveTabIndexChange = useCallback(({ activeTabIndex }) => { + setActiveTabIndex(activeTabIndex); + }, []); + + /** update the active tab index from outside */ + useEffect(() => { + setActiveTabIndex(activeTabIndexProp); + }, [activeTabIndexProp]); useEffect(() => { - if (panelService) { - const activatePanelSubscription = panelService.subscribe( - panelService.EVENTS.ACTIVATE_PANEL, - (activatePanelEvent: Types.ActivatePanelEvent) => { - if (!hasBeenOpened || activatePanelEvent.forceActive) { - const tabIndex = tabs.findIndex(tab => tab.id === activatePanelEvent.panelId); - if (tabIndex !== -1) { - setActiveTabIndex(tabIndex); - } + const { unsubscribe } = panelService.subscribe( + panelService.EVENTS.PANELS_CHANGED, + panelChangedEvent => { + if (panelChangedEvent.position !== side) { + return; + } + + setTabs(panelService.getPanels(side)); + } + ); + + return () => { + unsubscribe(); + }; + }, [panelService, side]); + + useEffect(() => { + const activatePanelSubscription = panelService.subscribe( + panelService.EVENTS.ACTIVATE_PANEL, + (activatePanelEvent: Types.ActivatePanelEvent) => { + if (!hasBeenOpened || activatePanelEvent.forceActive) { + const tabIndex = tabs.findIndex(tab => tab.id === activatePanelEvent.panelId); + if (tabIndex !== -1) { + setActiveTabIndex(tabIndex); } } - ); + } + ); - return () => { - activatePanelSubscription.unsubscribe(); - }; - } + return () => { + activatePanelSubscription.unsubscribe(); + }; }, [tabs, hasBeenOpened, panelService]); return ( { - setHasBeenOpened(true); - }} + activeTabIndex={activeTabIndex} + onOpen={handleSidePanelOpen} + onActiveTabIndexChange={handleActiveTabIndexChange} + expandedWidth={expandedWidth} > ); }; diff --git a/extensions/default/src/CustomizableContextMenu/ContextMenuController.tsx b/extensions/default/src/CustomizableContextMenu/ContextMenuController.tsx index 9d65b971d..2e0a73819 100644 --- a/extensions/default/src/CustomizableContextMenu/ContextMenuController.tsx +++ b/extensions/default/src/CustomizableContextMenu/ContextMenuController.tsx @@ -1,6 +1,7 @@ import * as ContextMenuItemsBuilder from './ContextMenuItemsBuilder'; import ContextMenu from '../../../../platform/ui/src/components/ContextMenu/ContextMenu'; import { CommandsManager, ServicesManager, Types } from '@ohif/core'; +import { annotation as CsAnnotation } from '@cornerstonejs/tools'; import { Menu, MenuItem, Point, ContextMenuProps } from './types'; /** @@ -47,7 +48,18 @@ export default class ContextMenuController { const { event, subMenu, menuId, menus, selectorProps } = contextMenuProps; - console.log('Getting items from', menus); + const annotationManager = CsAnnotation.state.getAnnotationManager(); + const { locking } = CsAnnotation; + const targetAnnotationId = selectorProps?.nearbyToolData?.annotationUID as string; + const isLocked = locking.isAnnotationLocked( + annotationManager.getAnnotation(targetAnnotationId) + ); + + if (isLocked) { + console.warn('Annotation is locked.'); + return; + } + const items = ContextMenuItemsBuilder.getMenuItems( selectorProps || contextMenuProps, event, diff --git a/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.test.js b/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.test.js index 00ee469dd..2231f7503 100644 --- a/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.test.js +++ b/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.test.js @@ -1,14 +1,14 @@ -import ContextMenuItemsBuilder from './ContextMenuItemsBuilder'; +import * as ContextMenuItemsBuilder from './ContextMenuItemsBuilder'; const menus = [ { id: 'one', - selector: ({ value }) => value === 'one', + selector: ({ value } = {}) => value === 'one', items: [], }, { id: 'two', - selector: ({ value }) => value === 'two', + selector: ({ value } = {}) => value === 'two', items: [], }, { @@ -17,13 +17,13 @@ const menus = [ }, ]; -const menuBuilder = new ContextMenuItemsBuilder(); - describe('ContextMenuItemsBuilder', () => { test('findMenuDefault', () => { - expect(menuBuilder.findMenuDefault(menus, {})).toBe(menus[2]); - expect(menuBuilder.findMenuDefault(menus, { value: 'two' })).toBe(menus[1]); - expect(menuBuilder.findMenuDefault([], {})).toBeUndefined(); - expect(menuBuilder.findMenuDefault(undefined, undefined)).toBeNull(); + expect(ContextMenuItemsBuilder.findMenuDefault(menus, {})).toBe(menus[2]); + expect( + ContextMenuItemsBuilder.findMenuDefault(menus, { selectorProps: { value: 'two' } }) + ).toBe(menus[1]); + expect(ContextMenuItemsBuilder.findMenuDefault([], {})).toBeUndefined(); + expect(ContextMenuItemsBuilder.findMenuDefault(undefined, undefined)).toBeNull(); }); }); diff --git a/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.ts b/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.ts index 8b8b98532..88860b2a1 100644 --- a/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.ts +++ b/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.ts @@ -70,8 +70,6 @@ export function findMenu(menus: Menu[], props?: Types.IProps, menuIdFilter?: str current = findIt.next(); } - console.log('Menu chosen', menu?.id || 'NONE'); - return menu; } diff --git a/extensions/default/src/CustomizableContextMenu/defaultContextMenu.ts b/extensions/default/src/CustomizableContextMenu/defaultContextMenu.ts index 29a760c79..486530a09 100644 --- a/extensions/default/src/CustomizableContextMenu/defaultContextMenu.ts +++ b/extensions/default/src/CustomizableContextMenu/defaultContextMenu.ts @@ -12,6 +12,9 @@ const defaultContextMenu = { commands: [ { commandName: 'deleteMeasurement', + // we only have support for cornerstoneTools context menu since + // they are svg based + context: 'CORNERSTONE', }, ], }, diff --git a/extensions/default/src/DicomJSONDataSource/index.js b/extensions/default/src/DicomJSONDataSource/index.js index c4bfb75ce..deb50e252 100644 --- a/extensions/default/src/DicomJSONDataSource/index.js +++ b/extensions/default/src/DicomJSONDataSource/index.js @@ -25,6 +25,23 @@ let _store = { // } }; +function wrapSequences(obj) { + return Object.keys(obj).reduce( + (acc, key) => { + if (typeof obj[key] === 'object' && obj[key] !== null) { + // Recursively wrap sequences for nested objects + acc[key] = wrapSequences(obj[key]); + } else { + acc[key] = obj[key]; + } + if (key.endsWith('Sequence')) { + acc[key] = OHIF.utils.addAccessors(acc[key]); + } + return acc; + }, + Array.isArray(obj) ? [] : {} + ); +} const getMetaDataByURL = url => { return _store.urls.find(metaData => metaData.url === url); }; @@ -149,7 +166,7 @@ function createDicomJSONApi(dicomJsonConfig) { * or is already retrieved, or a promise to a URL for such use if a BulkDataURI */ directURL: params => { - return getDirectURL(wadoRoot, params); + return getDirectURL(dicomJsonConfig, params); }, series: { metadata: async ({ StudyInstanceUID, madeInClient = false, customSort } = {}) => { @@ -190,8 +207,14 @@ function createDicomJSONApi(dicomJsonConfig) { const numberOfSeries = series.length; series.forEach((series, index) => { const instances = series.instances.map(instance => { + // for instance.metadata if the key ends with sequence then + // we need to add a proxy to the first item in the sequence + // so that we can access the value of the sequence + // by using sequenceName.value + const modifiedMetadata = wrapSequences(instance.metadata); + const obj = { - ...instance.metadata, + ...modifiedMetadata, url: instance.url, imageId: instance.url, ...series, diff --git a/extensions/default/src/DicomWebDataSource/index.js b/extensions/default/src/DicomWebDataSource/index.js index b265e4a07..23c5329c4 100644 --- a/extensions/default/src/DicomWebDataSource/index.js +++ b/extensions/default/src/DicomWebDataSource/index.js @@ -28,19 +28,38 @@ const EXPLICIT_VR_LITTLE_ENDIAN = '1.2.840.10008.1.2.1'; const metadataProvider = classes.MetadataProvider; /** + * Creates a DICOM Web API based on the provided configuration. * - * @param {string} name - Data source name - * @param {string} wadoUriRoot - Legacy? (potentially unused/replaced) - * @param {string} qidoRoot - Base URL to use for QIDO requests - * @param {string} wadoRoot - Base URL to use for WADO requests - * @param {boolean} qidoSupportsIncludeField - Whether QIDO supports the "Include" option to request additional fields in response - * @param {string} imageRengering - wadors | ? (unsure of where/how this is used) - * @param {string} thumbnailRendering - wadors | ? (unsure of where/how this is used) - * @param {bool} supportsReject - Whether the server supports reject calls (i.e. DCM4CHEE) - * @param {bool} lazyLoadStudy - "enableStudyLazyLoad"; Request series meta async instead of blocking - * @param {string|bool} singlepart - indicates of the retrieves can fetch singlepart. Options are bulkdata, video, image or boolean true + * @param {object} dicomWebConfig - Configuration for the DICOM Web API + * @param {string} dicomWebConfig.name - Data source name + * @param {string} dicomWebConfig.wadoUriRoot - Legacy? (potentially unused/replaced) + * @param {string} dicomWebConfig.qidoRoot - Base URL to use for QIDO requests + * @param {string} dicomWebConfig.wadoRoot - Base URL to use for WADO requests + * @param {string} dicomWebConfig.wadoUri - Base URL to use for WADO URI requests + * @param {boolean} dicomWebConfig.qidoSupportsIncludeField - Whether QIDO supports the "Include" option to request additional fields in response + * @param {string} dicomWebConfig.imageRendering - wadors | ? (unsure of where/how this is used) + * @param {string} dicomWebConfig.thumbnailRendering - wadors | ? (unsure of where/how this is used) + * @param {boolean} dicomWebConfig.supportsReject - Whether the server supports reject calls (i.e. DCM4CHEE) + * @param {boolean} dicomWebConfig.lazyLoadStudy - "enableStudyLazyLoad"; Request series meta async instead of blocking + * @param {string|boolean} dicomWebConfig.singlepart - indicates if the retrieves can fetch singlepart. Options are bulkdata, video, image, or boolean true + * @param {string} dicomWebConfig.requestTransferSyntaxUID - Transfer syntax to request from the server + * @param {object} dicomWebConfig.acceptHeader - Accept header to use for requests + * @param {boolean} dicomWebConfig.omitQuotationForMultipartRequest - Whether to omit quotation marks for multipart requests + * @param {boolean} dicomWebConfig.supportsFuzzyMatching - Whether the server supports fuzzy matching + * @param {boolean} dicomWebConfig.supportsWildcard - Whether the server supports wildcard matching + * @param {boolean} dicomWebConfig.supportsNativeDICOMModel - Whether the server supports the native DICOM model + * @param {boolean} dicomWebConfig.enableStudyLazyLoad - Whether to enable study lazy loading + * @param {boolean} dicomWebConfig.enableRequestTag - Whether to enable request tag + * @param {boolean} dicomWebConfig.enableStudyLazyLoad - Whether to enable study lazy loading + * @param {boolean} dicomWebConfig.bulkDataURI - Whether to enable bulkDataURI + * @param {function} dicomWebConfig.onConfiguration - Function that is called after the configuration is initialized + * @param {boolean} dicomWebConfig.staticWado - Whether to use the static WADO client + * @param {object} userAuthenticationService - User authentication service + * @param {object} userAuthenticationService.getAuthorizationHeader - Function that returns the authorization header + * @returns {object} - DICOM Web API object */ -function createDicomWebApi(dicomWebConfig, userAuthenticationService) { +function createDicomWebApi(dicomWebConfig, servicesManager) { + const { userAuthenticationService, customizationService } = servicesManager.services; let dicomWebConfigCopy, qidoConfig, wadoConfig, @@ -140,7 +159,13 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { instances: { search: (studyInstanceUid, queryParameters) => { qidoDicomWebClient.headers = getAuthrorizationHeader(); - qidoSearch.call(undefined, qidoDicomWebClient, studyInstanceUid, null, queryParameters); + return qidoSearch.call( + undefined, + qidoDicomWebClient, + studyInstanceUid, + null, + queryParameters + ); }, }, }, @@ -184,6 +209,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { sortCriteria, sortFunction, madeInClient = false, + returnPromises = false, } = {}) => { if (!StudyInstanceUID) { throw new Error('Unable to query for SeriesMetadata without StudyInstanceUID'); @@ -195,7 +221,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { filters, sortCriteria, sortFunction, - madeInClient + madeInClient, + returnPromises ); } @@ -211,7 +238,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { }, store: { - dicom: async (dataset, request) => { + dicom: async (dataset, request, dicomDict) => { wadoDicomWebClient.headers = getAuthrorizationHeader(); if (dataset instanceof ArrayBuffer) { const options = { @@ -220,21 +247,26 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { }; await wadoDicomWebClient.storeInstances(options); } else { - const meta = { - FileMetaInformationVersion: dataset._meta?.FileMetaInformationVersion?.Value, - MediaStorageSOPClassUID: dataset.SOPClassUID, - MediaStorageSOPInstanceUID: dataset.SOPInstanceUID, - TransferSyntaxUID: EXPLICIT_VR_LITTLE_ENDIAN, - ImplementationClassUID, - ImplementationVersionName, - }; + let effectiveDicomDict = dicomDict; - const denaturalized = denaturalizeDataset(meta); - const dicomDict = new DicomDict(denaturalized); + if (!dicomDict) { + const meta = { + FileMetaInformationVersion: dataset._meta?.FileMetaInformationVersion?.Value, + MediaStorageSOPClassUID: dataset.SOPClassUID, + MediaStorageSOPInstanceUID: dataset.SOPInstanceUID, + TransferSyntaxUID: EXPLICIT_VR_LITTLE_ENDIAN, + ImplementationClassUID, + ImplementationVersionName, + }; - dicomDict.dict = denaturalizeDataset(dataset); + const denaturalized = denaturalizeDataset(meta); + const defaultDicomDict = new DicomDict(denaturalized); + defaultDicomDict.dict = denaturalizeDataset(dataset); - const part10Buffer = dicomDict.write(); + effectiveDicomDict = defaultDicomDict; + } + + const part10Buffer = effectiveDicomDict.write(); const options = { datasets: [part10Buffer], @@ -262,7 +294,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { enableStudyLazyLoad, filters, sortCriteria, - sortFunction + sortFunction, + dicomWebConfig ); // first naturalize the data @@ -314,6 +347,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { Object.keys(instancesPerSeries).forEach(seriesInstanceUID => DicomMetadataStore.addInstances(instancesPerSeries[seriesInstanceUID], madeInClient) ); + + return seriesSummaryMetadata; }, _retrieveSeriesMetadataAsync: async ( @@ -321,7 +356,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { filters, sortCriteria, sortFunction, - madeInClient = false + madeInClient = false, + returnPromises = false ) => { const enableStudyLazyLoad = true; wadoDicomWebClient.headers = generateWadoHeader(); @@ -333,7 +369,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { enableStudyLazyLoad, filters, sortCriteria, - sortFunction + sortFunction, + dicomWebConfig ); /** @@ -357,11 +394,12 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { // in which case it isn't necessary to re-read this. if (value && value.BulkDataURI && !value.Value) { // Provide a method to fetch bulkdata - value.retrieveBulkData = () => { + value.retrieveBulkData = (options = {}) => { // handle the scenarios where bulkDataURI is relative path fixBulkDataURI(value, naturalized, dicomWebConfig); - const options = { + const { mediaType } = options; + const useOptions = { // The bulkdata fetches work with either multipart or // singlepart, so set multipart to false to let the server // decide which type to respond with. @@ -372,9 +410,13 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { // isn't well specified in the standard, but is needed in // any implementation that stores static copies of the metadata StudyInstanceUID: naturalized.StudyInstanceUID, + mediaTypes: mediaType + ? [{ mediaType }, { mediaType: 'application/octet-stream' }] + : undefined, + ...options, }; // Todo: this needs to be from wado dicom web client - return qidoDicomWebClient.retrieveBulkData(options).then(val => { + return qidoDicomWebClient.retrieveBulkData(useOptions).then(val => { // There are DICOM PDF cases where the first ArrayBuffer in the array is // the bulk data and DICOM video cases where the second ArrayBuffer is // the bulk data. Here we play it safe and do a find. @@ -395,7 +437,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { const naturalizedInstances = instances.map(addRetrieveBulkData); // Adding instanceMetadata to OHIF MetadataProvider - naturalizedInstances.forEach((instance, index) => { + naturalizedInstances.forEach(instance => { instance.wadoRoot = dicomWebConfig.wadoRoot; instance.wadoUri = dicomWebConfig.wadoUri; @@ -422,7 +464,10 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { } function setSuccessFlag() { - const study = DicomMetadataStore.getStudy(StudyInstanceUID, madeInClient); + const study = DicomMetadataStore.getStudy(StudyInstanceUID); + if (!study) { + return; + } study.isLoaded = true; } @@ -434,13 +479,24 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { DicomMetadataStore.addSeriesMetadata(seriesSummaryMetadata, madeInClient); - const seriesDeliveredPromises = seriesPromises.map(promise => - promise.then(instances => { + const seriesDeliveredPromises = seriesPromises.map(promise => { + if (!returnPromises) { + promise?.start(); + } + return promise.then(instances => { storeInstances(instances); - }) - ); - await Promise.all(seriesDeliveredPromises); - setSuccessFlag(); + }); + }); + + if (returnPromises) { + Promise.all(seriesDeliveredPromises).then(() => setSuccessFlag()); + return seriesPromises; + } else { + await Promise.all(seriesDeliveredPromises); + setSuccessFlag(); + } + + return seriesSummaryMetadata; }, deleteStudyMetadataPromise, getImageIdsForDisplaySet(displaySet) { @@ -470,7 +526,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { return imageIds; }, - getImageIdsForInstance({ instance, frame }) { + getImageIdsForInstance({ instance, frame = undefined }) { const imageIds = getImageId({ instance, frame, diff --git a/extensions/default/src/DicomWebDataSource/retrieveStudyMetadata.js b/extensions/default/src/DicomWebDataSource/retrieveStudyMetadata.js index e4882d05e..f05115333 100644 --- a/extensions/default/src/DicomWebDataSource/retrieveStudyMetadata.js +++ b/extensions/default/src/DicomWebDataSource/retrieveStudyMetadata.js @@ -1,3 +1,4 @@ +import retrieveMetadataFiltered from './utils/retrieveMetadataFiltered.js'; import RetrieveMetadata from './wado/retrieveMetadata.js'; const moduleName = 'RetrieveStudyMetadata'; @@ -5,14 +6,17 @@ const moduleName = 'RetrieveStudyMetadata'; const StudyMetaDataPromises = new Map(); /** - * Retrieves study metadata + * Retrieves study metadata. * - * @param {Object} server Object with server configuration parameters + * @param {Object} dicomWebClient The DICOMWebClient instance to be used for series load * @param {string} StudyInstanceUID The UID of the Study to be retrieved - * @param {boolean} enabledStudyLazyLoad Whether the study metadata should be loaded asynchronously. - * @param {function} storeInstancesCallback A callback used to store the retrieved instance metadata. - * @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process - * @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against + * @param {boolean} enableStudyLazyLoad Whether the study metadata should be loaded asynchronously. + * @param {Object} [filters] Object containing filters to be applied on retrieve metadata process + * @param {string} [filters.seriesInstanceUID] Series instance uid to filter results against + * @param {array} [filters.SeriesInstanceUIDs] Series instance uids to filter results against + * @param {function} [sortCriteria] Sort criteria function + * @param {function} [sortFunction] Sort function + * * @returns {Promise} that will be resolved with the metadata or rejected with the error */ export function retrieveStudyMetadata( @@ -21,7 +25,8 @@ export function retrieveStudyMetadata( enableStudyLazyLoad, filters, sortCriteria, - sortFunction + sortFunction, + dicomWebConfig = {} ) { // @TODO: Whenever a study metadata request has failed, its related promise will be rejected once and for all // and further requests for that metadata will always fail. On failure, we probably need to remove the @@ -34,37 +39,51 @@ export function retrieveStudyMetadata( throw new Error(`${moduleName}: Required 'StudyInstanceUID' parameter not provided.`); } + const promiseId = `${dicomWebConfig.name}:${StudyInstanceUID}`; + // Already waiting on result? Return cached promise - if (StudyMetaDataPromises.has(StudyInstanceUID)) { - return StudyMetaDataPromises.get(StudyInstanceUID); + if (StudyMetaDataPromises.has(promiseId)) { + return StudyMetaDataPromises.get(promiseId); } - // Create a promise to handle the data retrieval - const promise = new Promise((resolve, reject) => { - RetrieveMetadata( + let promise; + + if (filters && filters.SeriesInstanceUIDs) { + promise = retrieveMetadataFiltered( dicomWebClient, StudyInstanceUID, enableStudyLazyLoad, filters, sortCriteria, sortFunction - ).then(function (data) { - resolve(data); - }, reject); - }); + ); + } else { + // Create a promise to handle the data retrieval + promise = new Promise((resolve, reject) => { + RetrieveMetadata( + dicomWebClient, + StudyInstanceUID, + enableStudyLazyLoad, + filters, + sortCriteria, + sortFunction + ).then(function (data) { + resolve(data); + }, reject); + }); + } // Store the promise in cache - StudyMetaDataPromises.set(StudyInstanceUID, promise); + StudyMetaDataPromises.set(promiseId, promise); return promise; } /** * Delete the cached study metadata retrieval promise to ensure that the browser will - * re-retrieve the study metadata when it is next requested + * re-retrieve the study metadata when it is next requested. * * @param {String} StudyInstanceUID The UID of the Study to be removed from cache - * */ export function deleteStudyMetadataPromise(StudyInstanceUID) { if (StudyMetaDataPromises.has(StudyInstanceUID)) { diff --git a/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts b/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts index 1ba2aad90..519e8109c 100644 --- a/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts +++ b/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts @@ -1,4 +1,25 @@ import { api } from 'dicomweb-client'; +import fixMultipart from './fixMultipart'; + +const { DICOMwebClient } = api; + +const anyDicomwebClient = DICOMwebClient as any; + +// Ugly over-ride, but the internals aren't otherwise accessible. +if (!anyDicomwebClient._orig_buildMultipartAcceptHeaderFieldValue) { + anyDicomwebClient._orig_buildMultipartAcceptHeaderFieldValue = + anyDicomwebClient._buildMultipartAcceptHeaderFieldValue; + anyDicomwebClient._buildMultipartAcceptHeaderFieldValue = function (mediaTypes, acceptableTypes) { + if (mediaTypes.length === 1 && mediaTypes[0].mediaType.endsWith('/*')) { + return '*/*'; + } else { + return anyDicomwebClient._orig_buildMultipartAcceptHeaderFieldValue( + mediaTypes, + acceptableTypes + ); + } + }; +} /** * An implementation of the static wado client, that fetches data from @@ -7,6 +28,7 @@ import { api } from 'dicomweb-client'; * performing searches doesn't work. This version fixes the query issue * by manually implementing a query option. */ + export default class StaticWadoClient extends api.DICOMwebClient { static studyFilterKeys = { studyinstanceuid: '0020000D', @@ -24,9 +46,50 @@ export default class StaticWadoClient extends api.DICOMwebClient { modality: '00080060', }; - constructor(qidoConfig) { - super(qidoConfig); - this.staticWado = qidoConfig.staticWado; + protected config; + protected staticWado; + + constructor(config) { + super(config); + this.staticWado = config.staticWado; + this.config = config; + } + + /** + * Handle improperly specified multipart/related return type. + * Note if the response is SUPPOSED to be multipart encoded already, then this + * will double-decode it. + * + * @param options + * @returns De-multiparted response data. + * + */ + public retrieveBulkData(options): Promise { + const shouldFixMultipart = this.config.fixBulkdataMultipart !== false; + const useOptions = { + ...options, + }; + if (this.staticWado) { + useOptions.mediaTypes = [{ mediaType: 'application/*' }]; + } + return super + .retrieveBulkData(useOptions) + .then(result => (shouldFixMultipart ? fixMultipart(result) : result)); + } + + /** + * Retrieves instance frames using the image/* media type when configured + * to do so (static wado back end). + */ + public retrieveInstanceFrames(options) { + if (this.staticWado) { + return super.retrieveInstanceFrames({ + ...options, + mediaTypes: [{ mediaType: 'image/*' }], + }); + } else { + return super.retrieveInstanceFrames(options); + } } /** diff --git a/extensions/default/src/DicomWebDataSource/utils/findIndexOfString.ts b/extensions/default/src/DicomWebDataSource/utils/findIndexOfString.ts new file mode 100644 index 000000000..f5bbd3583 --- /dev/null +++ b/extensions/default/src/DicomWebDataSource/utils/findIndexOfString.ts @@ -0,0 +1,47 @@ +function checkToken(token, data, dataOffset): boolean { + if (dataOffset + token.length > data.length) { + return false; + } + + let endIndex = dataOffset; + + for (let i = 0; i < token.length; i++) { + if (token[i] !== data[endIndex++]) { + return false; + } + } + + return true; +} + +function stringToUint8Array(str: string): Uint8Array { + const uint = new Uint8Array(str.length); + + for (let i = 0, j = str.length; i < j; i++) { + uint[i] = str.charCodeAt(i); + } + + return uint; +} + +function findIndexOfString( + data: Uint8Array, + str: string, + offset?: number +): number { + offset = offset || 0; + + const token = stringToUint8Array(str); + + for (let i = offset; i < data.length; i++) { + if (token[0] === data[i]) { + // console.log('match @', i); + if (checkToken(token, data, i)) { + return i; + } + } + } + + return -1; +} +export default findIndexOfString; diff --git a/extensions/default/src/DicomWebDataSource/utils/fixMultipart.ts b/extensions/default/src/DicomWebDataSource/utils/fixMultipart.ts new file mode 100644 index 000000000..77e3071a2 --- /dev/null +++ b/extensions/default/src/DicomWebDataSource/utils/fixMultipart.ts @@ -0,0 +1,70 @@ +import findIndexOfString from './findIndexOfString'; + +/** + * Fix multipart data coming back from the retrieve bulkdata request, but + * incorrectly tagged as application/octet-stream. Some servers don't handle + * the response type correctly, and this method is relatively robust about + * detecting multipart data correctly. It will only extract one value. + */ +export default function fixMultipart(arrayData) { + const data = new Uint8Array(arrayData[0]); + // Don't know the exact minimum length, but it is at least 25 to encode multipart + if (data.length < 25) { + return arrayData; + } + const dashIndex = findIndexOfString(data, '--'); + if (dashIndex > 6) { + return arrayData; + } + const tokenIndex = findIndexOfString(data, '\r\n\r\n', dashIndex); + if (tokenIndex > 512) { + // Allow for 512 characters in the header - there is no apriori limit, but + // this seems ok for now as we only expect it to have content type in it. + return arrayData; + } + const header = uint8ArrayToString(data, 0, tokenIndex); + // Now find the boundary marker + const responseHeaders = header.split('\r\n'); + const boundary = findBoundary(responseHeaders); + + if (!boundary) { + return arrayData; + } + // Start of actual data is 4 characters after the token + const offset = tokenIndex + 4; + + const endIndex = findIndexOfString(data, boundary, offset); + if (endIndex === -1) { + return arrayData; + } + + return [data.slice(offset, endIndex - 2).buffer]; +} + +export function findBoundary(header: string[]): string { + for (let i = 0; i < header.length; i++) { + if (header[i].substr(0, 2) === '--') { + return header[i]; + } + } +} + +export function findContentType(header: string[]): string { + for (let i = 0; i < header.length; i++) { + if (header[i].substr(0, 13) === 'Content-Type:') { + return header[i].substr(13).trim(); + } + } +} + +export function uint8ArrayToString(data, offset, length) { + offset = offset || 0; + length = length || data.length - offset; + let str = ''; + + for (let i = offset; i < offset + length; i++) { + str += String.fromCharCode(data[i]); + } + + return str; +} diff --git a/extensions/default/src/DicomWebDataSource/utils/retrieveMetadataFiltered.js b/extensions/default/src/DicomWebDataSource/utils/retrieveMetadataFiltered.js new file mode 100644 index 000000000..3e147fd48 --- /dev/null +++ b/extensions/default/src/DicomWebDataSource/utils/retrieveMetadataFiltered.js @@ -0,0 +1,56 @@ +import RetrieveMetadata from '../wado/retrieveMetadata'; + +/** + * Retrieve metadata filtered. + * + * @param {*} dicomWebClient The DICOMWebClient instance to be used for series load + * @param {*} StudyInstanceUID The UID of the Study to be retrieved + * @param {*} enableStudyLazyLoad Whether the study metadata should be loaded asynchronously + * @param {object} filters Object containing filters to be applied on retrieve metadata process + * @param {string} [filters.seriesInstanceUID] Series instance uid to filter results against + * @param {array} [filters.SeriesInstanceUIDs] Series instance uids to filter results against + * @param {function} [sortCriteria] Sort criteria function + * @param {function} [sortFunction] Sort function + * + * @returns + */ +function retrieveMetadataFiltered( + dicomWebClient, + StudyInstanceUID, + enableStudyLazyLoad, + filters, + sortCriteria, + sortFunction +) { + const { SeriesInstanceUIDs } = filters; + + return new Promise((resolve, reject) => { + const promises = SeriesInstanceUIDs.map(uid => { + const seriesSpecificFilters = Object.assign({}, filters, { + seriesInstanceUID: uid, + }); + + return RetrieveMetadata( + dicomWebClient, + StudyInstanceUID, + enableStudyLazyLoad, + seriesSpecificFilters, + sortCriteria, + sortFunction + ); + }); + + Promise.all(promises).then(results => { + const aggregatedResult = { preLoadData: [], promises: [] }; + + results.forEach(({ preLoadData, promises }) => { + aggregatedResult.preLoadData = aggregatedResult.preLoadData.concat(preLoadData); + aggregatedResult.promises = aggregatedResult.promises.concat(promises); + }); + + resolve(aggregatedResult); + }, reject); + }); +} + +export default retrieveMetadataFiltered; diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js index ba62cca1d..7bed25a1a 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js @@ -5,15 +5,20 @@ import RetrieveMetadataLoaderAsync from './retrieveMetadataLoaderAsync'; * Retrieve Study metadata from a DICOM server. If the server is configured to use lazy load, only the first series * will be loaded and the property "studyLoader" will be set to let consumer load remaining series as needed. * - * @param {Object} dicomWebClient The dicomweb-client. - * @param {string} studyInstanceUid The Study Instance UID of the study which needs to be loaded - * @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process - * @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against - * @returns {Object} A study descriptor object + * @param {*} dicomWebClient The DICOMWebClient instance to be used for series load + * @param {*} StudyInstanceUID The UID of the Study to be retrieved + * @param {*} enableStudyLazyLoad Whether the study metadata should be loaded asynchronously + * @param {object} filters Object containing filters to be applied on retrieve metadata process + * @param {string} [filters.seriesInstanceUID] Series instance uid to filter results against + * @param {array} [filters.SeriesInstanceUIDs] Series instance uids to filter results against + * @param {function} [sortCriteria] Sort criteria function + * @param {function} [sortFunction] Sort function + * + * @returns {Promise} A promises that resolves the study descriptor object */ async function RetrieveMetadata( dicomWebClient, - studyInstanceUid, + StudyInstanceUID, enableStudyLazyLoad, filters = {}, sortCriteria, @@ -24,7 +29,7 @@ async function RetrieveMetadata( const retrieveMetadataLoader = new RetrieveMetadataLoader( dicomWebClient, - studyInstanceUid, + StudyInstanceUID, filters, sortCriteria, sortFunction diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoader.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoader.js index da747c88b..e0143000c 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoader.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoader.js @@ -11,10 +11,17 @@ export default class RetrieveMetadataLoader { * @param {Object} client The dicomweb-client. * @param {Array} studyInstanceUID Study instance ui to be retrieved * @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process - * @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against - * @param {Function} [sortSeries] - Custom sort function for series + * @param {string} [filters.seriesInstanceUID] - series instance uid to filter results against + * @param {Object} [sortCriteria] - Custom sort criteria used for series + * @param {Function} [sortFunction] - Custom sort function for series */ - constructor(client, studyInstanceUID, filters = {}, sortCriteria, sortFunction) { + constructor( + client, + studyInstanceUID, + filters = {}, + sortCriteria = undefined, + sortFunction = undefined + ) { this.client = client; this.studyInstanceUID = studyInstanceUID; this.filters = filters; @@ -26,7 +33,6 @@ export default class RetrieveMetadataLoader { const preLoadData = await this.preLoad(); const loadData = await this.load(preLoadData); const postLoadData = await this.posLoad(loadData); - return postLoadData; } @@ -37,13 +43,9 @@ export default class RetrieveMetadataLoader { async runLoaders(loaders) { let result; for (const loader of loaders) { - try { - result = await loader(); - if (result && result.length) { - break; // closes iterator in case data is retrieved successfully - } - } catch (e) { - throw e; + result = await loader(); + if (result && result.length) { + break; // closes iterator in case data is retrieved successfully } } diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js index bca309429..0c9a65603 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js @@ -1,12 +1,65 @@ import dcmjs from 'dcmjs'; -import { sortStudySeries, sortingCriteria } from '@ohif/core/src/utils/sortStudy'; +import { sortStudySeries } from '@ohif/core/src/utils/sortStudy'; import RetrieveMetadataLoader from './retrieveMetadataLoader'; +// Series Date, Series Time, Series Description and Series Number to be included +// in the series metadata query result +const includeField = ['00080021', '00080031', '0008103E', '00200011'].join(','); + +export class DeferredPromise { + metadata = undefined; + processFunction = undefined; + internalPromise = undefined; + thenFunction = undefined; + rejectFunction = undefined; + + setMetadata(metadata) { + this.metadata = metadata; + } + setProcessFunction(func) { + this.processFunction = func; + } + getPromise() { + return this.start(); + } + start() { + if (this.internalPromise) { + return this.internalPromise; + } + this.internalPromise = this.processFunction(); + // in case then and reject functions called before start + if (this.thenFunction) { + this.then(this.thenFunction); + this.thenFunction = undefined; + } + if (this.rejectFunction) { + this.reject(this.rejectFunction); + this.rejectFunction = undefined; + } + return this.internalPromise; + } + then(func) { + if (this.internalPromise) { + return this.internalPromise.then(func); + } else { + this.thenFunction = func; + } + } + reject(func) { + if (this.internalPromise) { + return this.internalPromise.reject(func); + } else { + this.rejectFunction = func; + } + } +} /** - * Creates an immutable series loader object which loads each series sequentially using the iterator interface + * Creates an immutable series loader object which loads each series sequentially using the iterator interface. + * * @param {DICOMWebClient} dicomWebClient The DICOMWebClient instance to be used for series load * @param {string} studyInstanceUID The Study Instance UID from which series will be loaded * @param {Array} seriesInstanceUIDList A list of Series Instance UIDs + * * @returns {Object} Returns an object which supports loading of instances from each of given Series Instance UID */ function makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDList) { @@ -14,12 +67,17 @@ function makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDList) hasNext() { return seriesInstanceUIDList.length > 0; }, - async next() { - const seriesInstanceUID = seriesInstanceUIDList.shift(); - return client.retrieveSeriesMetadata({ - studyInstanceUID, - seriesInstanceUID, + next() { + const { seriesInstanceUID, metadata } = seriesInstanceUIDList.shift(); + const promise = new DeferredPromise(); + promise.setMetadata(metadata); + promise.setProcessFunction(() => { + return client.retrieveSeriesMetadata({ + studyInstanceUID, + seriesInstanceUID, + }); }); + return promise; }, }); } @@ -38,15 +96,22 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader const preLoaders = []; const { studyInstanceUID, filters: { seriesInstanceUID } = {}, client } = this; + // asking to include Series Date, Series Time, Series Description + // and Series Number in the series metadata returned to better sort series + // in preLoad function + let options = { + studyInstanceUID, + queryParams: { + includefield: includeField, + }, + }; + if (seriesInstanceUID) { - const options = { - studyInstanceUID, - queryParams: { SeriesInstanceUID: seriesInstanceUID }, - }; + options.queryParams.SeriesInstanceUID = seriesInstanceUID; preLoaders.push(client.searchForSeries.bind(client, options)); } // Fallback preloader - preLoaders.push(client.searchForSeries.bind(client, { studyInstanceUID })); + preLoaders.push(client.searchForSeries.bind(client, options)); yield* preLoaders; } @@ -60,24 +125,23 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary; const naturalized = result.map(naturalizeDataset); - return sortStudySeries( - naturalized, - sortCriteria || sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria, - sortFunction - ); + return sortStudySeries(naturalized, sortCriteria, sortFunction); } async load(preLoadData) { const { client, studyInstanceUID } = this; - const seriesInstanceUIDs = preLoadData.map(s => s.SeriesInstanceUID); + const seriesInstanceUIDs = preLoadData.map(seriesMetadata => { + return { seriesInstanceUID: seriesMetadata.SeriesInstanceUID, metadata: seriesMetadata }; + }); const seriesAsyncLoader = makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDs); const promises = []; while (seriesAsyncLoader.hasNext()) { - promises.push(seriesAsyncLoader.next()); + const promise = seriesAsyncLoader.next(); + promises.push(promise); } return { diff --git a/extensions/default/src/DicomWebProxyDataSource/index.js b/extensions/default/src/DicomWebProxyDataSource/index.js index ab1d8e49e..99007de3e 100644 --- a/extensions/default/src/DicomWebProxyDataSource/index.js +++ b/extensions/default/src/DicomWebProxyDataSource/index.js @@ -9,7 +9,7 @@ import { createDicomWebApi } from '../DicomWebDataSource/index'; * dicomWeb configuration array * */ -function createDicomWebProxyApi(dicomWebProxyConfig, UserAuthenticationService) { +function createDicomWebProxyApi(dicomWebProxyConfig, servicesManager) { const { name } = dicomWebProxyConfig; let dicomWebDelegate = undefined; @@ -28,7 +28,7 @@ function createDicomWebProxyApi(dicomWebProxyConfig, UserAuthenticationService) dicomWebDelegate = createDicomWebApi( data.servers.dicomWeb[0].configuration, - UserAuthenticationService + servicesManager ); dicomWebDelegate.initialize({ params, query }); } diff --git a/extensions/default/src/MergeDataSource/index.test.js b/extensions/default/src/MergeDataSource/index.test.js new file mode 100644 index 000000000..2b915e329 --- /dev/null +++ b/extensions/default/src/MergeDataSource/index.test.js @@ -0,0 +1,203 @@ +import { DicomMetadataStore, IWebApiDataSource } from '@ohif/core'; +import { + mergeMap, + callForAllDataSourcesAsync, + callForAllDataSources, + callForDefaultDataSource, + callByRetrieveAETitle, + createMergeDataSourceApi, +} from './index'; + +jest.mock('@ohif/core'); + +describe('MergeDataSource', () => { + let path, + sourceName, + mergeConfig, + extensionManager, + series1, + series2, + series3, + series4, + mergeKey, + tagFunc, + dataSourceAndSeriesMap, + dataSourceAndUIDsMap, + dataSourceAndDSMap, + pathSync; + + beforeAll(() => { + path = 'query.series.search'; + pathSync = 'getImageIdsForInstance'; + tagFunc = jest.fn((data, sourceName) => + data.map(item => ({ ...item, RetrieveAETitle: sourceName })) + ); + sourceName = 'dicomweb1'; + mergeKey = 'seriesInstanceUid'; + series1 = { [mergeKey]: '123' }; + series2 = { [mergeKey]: '234' }; + series3 = { [mergeKey]: '345' }; + series4 = { [mergeKey]: '456' }; + mergeConfig = { + seriesMerge: { + dataSourceNames: ['dicomweb1', 'dicomweb2'], + defaultDataSourceName: 'dicomweb1', + }, + }; + dataSourceAndSeriesMap = { + dataSource1: series1, + dataSource2: series2, + dataSource3: series3, + }; + dataSourceAndUIDsMap = { + dataSource1: ['123'], + dataSource2: ['234'], + dataSource3: ['345'], + }; + dataSourceAndDSMap = { + dataSource1: { + displaySet: { + StudyInstanceUID: '123', + SeriesInstanceUID: '123', + }, + }, + dataSource2: { + displaySet: { + StudyInstanceUID: '234', + SeriesInstanceUID: '234', + }, + }, + dataSource3: { + displaySet: { + StudyInstanceUID: '345', + SeriesInstanceUID: '345', + }, + }, + }; + extensionManager = { + dataSourceDefs: { + dataSource1: { + sourceName: 'dataSource1', + configuration: {}, + }, + dataSource2: { + sourceName: 'dataSource2', + configuration: {}, + }, + dataSource3: { + sourceName: 'dataSource3', + configuration: {}, + }, + }, + getDataSources: jest.fn(dataSourceName => [ + { + [path]: jest.fn().mockResolvedValue([dataSourceAndSeriesMap[dataSourceName]]), + }, + ]), + }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('callForAllDataSourcesAsync', () => { + it('should call the correct functions and return the merged data', async () => { + /** Arrange */ + extensionManager.getDataSources = jest.fn(dataSourceName => [ + { + [path]: jest.fn().mockResolvedValue([dataSourceAndSeriesMap[dataSourceName]]), + }, + ]); + + /** Act */ + const data = await callForAllDataSourcesAsync({ + mergeMap, + path, + args: [], + extensionManager, + dataSourceNames: ['dataSource1', 'dataSource2'], + }); + + /** Assert */ + expect(extensionManager.getDataSources).toHaveBeenCalledTimes(2); + expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource1'); + expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource2'); + expect(data).toEqual([series1, series2]); + }); + }); + + describe('callForAllDataSources', () => { + it('should call the correct functions and return the merged data', () => { + /** Arrange */ + extensionManager.getDataSources = jest.fn(dataSourceName => [ + { + [pathSync]: () => dataSourceAndUIDsMap[dataSourceName], + }, + ]); + + /** Act */ + const data = callForAllDataSources({ + path: pathSync, + args: [], + extensionManager, + dataSourceNames: ['dataSource2', 'dataSource3'], + }); + + /** Assert */ + expect(extensionManager.getDataSources).toHaveBeenCalledTimes(2); + expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource2'); + expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource3'); + expect(data).toEqual(['234', '345']); + }); + }); + + describe('callForDefaultDataSource', () => { + it('should call the correct function and return the data', () => { + /** Arrange */ + extensionManager.getDataSources = jest.fn(dataSourceName => [ + { + [pathSync]: () => dataSourceAndUIDsMap[dataSourceName], + }, + ]); + + /** Act */ + const data = callForDefaultDataSource({ + path: pathSync, + args: [], + extensionManager, + defaultDataSourceName: 'dataSource2', + }); + + /** Assert */ + expect(extensionManager.getDataSources).toHaveBeenCalledTimes(1); + expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource2'); + expect(data).toEqual(['234']); + }); + }); + + describe('callByRetrieveAETitle', () => { + it('should call the correct function and return the data', () => { + /** Arrange */ + DicomMetadataStore.getSeries.mockImplementationOnce(() => [series2]); + extensionManager.getDataSources = jest.fn(dataSourceName => [ + { + [pathSync]: () => dataSourceAndUIDsMap[dataSourceName], + }, + ]); + + /** Act */ + const data = callByRetrieveAETitle({ + path: pathSync, + args: [dataSourceAndDSMap['dataSource2']], + extensionManager, + defaultDataSourceName: 'dataSource2', + }); + + /** Assert */ + expect(extensionManager.getDataSources).toHaveBeenCalledTimes(1); + expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource2'); + expect(data).toEqual(['234']); + }); + }); +}); diff --git a/extensions/default/src/MergeDataSource/index.ts b/extensions/default/src/MergeDataSource/index.ts new file mode 100644 index 000000000..42e49c694 --- /dev/null +++ b/extensions/default/src/MergeDataSource/index.ts @@ -0,0 +1,293 @@ +import { DicomMetadataStore, IWebApiDataSource } from '@ohif/core'; +import { get, uniqBy } from 'lodash'; +import { + MergeConfig, + CallForAllDataSourcesAsyncOptions, + CallForAllDataSourcesOptions, + CallForDefaultDataSourceOptions, + CallByRetrieveAETitleOptions, + MergeMap, +} from './types'; + +export const mergeMap: MergeMap = { + 'query.studies.search': { + mergeKey: 'studyInstanceUid', + tagFunc: x => x, + }, + 'query.series.search': { + mergeKey: 'seriesInstanceUid', + tagFunc: (series, sourceName) => { + series.forEach(series => { + series.RetrieveAETitle = sourceName; + DicomMetadataStore.updateSeriesMetadata(series); + }); + return series; + }, + }, +}; + +/** + * Calls all data sources asynchronously and merges the results. + * @param {CallForAllDataSourcesAsyncOptions} options - The options for calling all data sources. + * @param {string} options.path - The path to the function to be called on each data source. + * @param {unknown[]} options.args - The arguments to be passed to the function. + * @param {ExtensionManager} options.extensionManager - The extension manager. + * @param {string[]} options.dataSourceNames - The names of the data sources to be called. + * @param {string} options.defaultDataSourceName - The name of the default data source. + * @returns {Promise} - A promise that resolves to the merged data from all data sources. + */ +export const callForAllDataSourcesAsync = async ({ + mergeMap, + path, + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, +}: CallForAllDataSourcesAsyncOptions) => { + const { mergeKey, tagFunc } = mergeMap[path] || { tagFunc: x => x }; + + /** Sort by default data source */ + const defs = Object.values(extensionManager.dataSourceDefs); + const defaultDataSourceDef = defs.find(def => def.sourceName === defaultDataSourceName); + const dataSourceDefs = defs.filter(def => def.sourceName !== defaultDataSourceName); + if (defaultDataSourceDef) { + dataSourceDefs.unshift(defaultDataSourceDef); + } + + const promises = []; + const sourceNames = []; + + for (const dataSourceDef of dataSourceDefs) { + const { configuration, sourceName } = dataSourceDef; + if (!!configuration && dataSourceNames.includes(sourceName)) { + const [dataSource] = extensionManager.getDataSources(sourceName); + const func = get(dataSource, path); + const promise = func.apply(dataSource, args); + promises.push(promise); + sourceNames.push(sourceName); + } + } + + const data = await Promise.allSettled(promises); + const mergedData = data.map((data, i) => tagFunc(data.value, sourceNames[i])); + + let results = []; + if (mergeKey) { + results = uniqBy(mergedData.flat(), obj => get(obj, mergeKey)); + } else { + results = mergedData.flat(); + } + + return results; +}; + +/** + * Calls all data sources that match the provided names and merges their data. + * @param options - The options for calling all data sources. + * @param options.path - The path to the function to be called on each data source. + * @param options.args - The arguments to be passed to the function. + * @param options.extensionManager - The extension manager instance. + * @param options.dataSourceNames - The names of the data sources to be called. + * @param options.defaultDataSourceName - The name of the default data source. + * @returns The merged data from all the matching data sources. + */ +export const callForAllDataSources = ({ + path, + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, +}: CallForAllDataSourcesOptions) => { + /** Sort by default data source */ + const defs = Object.values(extensionManager.dataSourceDefs); + const defaultDataSourceDef = defs.find(def => def.sourceName === defaultDataSourceName); + const dataSourceDefs = defs.filter(def => def.sourceName !== defaultDataSourceName); + if (defaultDataSourceDef) { + dataSourceDefs.unshift(defaultDataSourceDef); + } + + const mergedData = []; + for (const dataSourceDef of dataSourceDefs) { + const { configuration, sourceName } = dataSourceDef; + if (!!configuration && dataSourceNames.includes(sourceName)) { + const [dataSource] = extensionManager.getDataSources(sourceName); + const func = get(dataSource, path); + const data = func.apply(dataSource, args); + mergedData.push(data); + } + } + + return mergedData.flat(); +}; + +/** + * Calls the default data source function specified by the given path with the provided arguments. + * @param {CallForDefaultDataSourceOptions} options - The options for calling the default data source. + * @param {string} options.path - The path to the function within the default data source. + * @param {unknown[]} options.args - The arguments to pass to the function. + * @param {string} options.defaultDataSourceName - The name of the default data source. + * @param {ExtensionManager} options.extensionManager - The extension manager instance. + * @returns {unknown} - The result of calling the default data source function. + */ +export const callForDefaultDataSource = ({ + path, + args, + defaultDataSourceName, + extensionManager, +}: CallForDefaultDataSourceOptions) => { + const [dataSource] = extensionManager.getDataSources(defaultDataSourceName); + const func = get(dataSource, path); + return func.apply(dataSource, args); +}; + +/** + * Calls the data source specified by the RetrieveAETitle of the given display set. + * @typedef {Object} CallByRetrieveAETitleOptions + * @property {string} path - The path of the method to call on the data source. + * @property {any[]} args - The arguments to pass to the method. + * @property {string} defaultDataSourceName - The name of the default data source. + * @property {ExtensionManager} extensionManager - The extension manager. + */ +export const callByRetrieveAETitle = ({ + path, + args, + defaultDataSourceName, + extensionManager, +}: CallByRetrieveAETitleOptions) => { + const [displaySet] = args; + const seriesMetadata = DicomMetadataStore.getSeries( + displaySet.StudyInstanceUID, + displaySet.SeriesInstanceUID + ); + const [dataSource] = extensionManager.getDataSources( + seriesMetadata.RetrieveAETitle || defaultDataSourceName + ); + return dataSource[path](...args); +}; + +function createMergeDataSourceApi( + mergeConfig: MergeConfig, + servicesManager: unknown, + extensionManager +) { + const { seriesMerge } = mergeConfig; + const { dataSourceNames, defaultDataSourceName } = seriesMerge; + + const implementation = { + initialize: (...args: unknown[]) => + callForAllDataSources({ + path: 'initialize', + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, + }), + query: { + studies: { + search: (...args: unknown[]) => + callForAllDataSourcesAsync({ + mergeMap, + path: 'query.studies.search', + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, + }), + }, + series: { + search: (...args: unknown[]) => + callForAllDataSourcesAsync({ + mergeMap, + path: 'query.series.search', + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, + }), + }, + instances: { + search: (...args: unknown[]) => + callForAllDataSourcesAsync({ + mergeMap, + path: 'query.instances.search', + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, + }), + }, + }, + retrieve: { + bulkDataURI: (...args: unknown[]) => + callForAllDataSourcesAsync({ + mergeMap, + path: 'retrieve.bulkDataURI', + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, + }), + directURL: (...args: unknown[]) => + callForDefaultDataSource({ + path: 'retrieve.directURL', + args, + defaultDataSourceName, + extensionManager, + }), + series: { + metadata: (...args: unknown[]) => + callForAllDataSourcesAsync({ + mergeMap, + path: 'retrieve.series.metadata', + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, + }), + }, + }, + store: { + dicom: (...args: unknown[]) => + callForDefaultDataSource({ + path: 'store.dicom', + args, + defaultDataSourceName, + extensionManager, + }), + }, + deleteStudyMetadataPromise: (...args: unknown[]) => + callForAllDataSources({ + path: 'deleteStudyMetadataPromise', + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, + }), + getImageIdsForDisplaySet: (...args: unknown[]) => + callByRetrieveAETitle({ + path: 'getImageIdsForDisplaySet', + args, + defaultDataSourceName, + extensionManager, + }), + getImageIdsForInstance: (...args: unknown[]) => + callByRetrieveAETitle({ + path: 'getImageIdsForDisplaySet', + args, + defaultDataSourceName, + extensionManager, + }), + getStudyInstanceUIDs: (...args: unknown[]) => + callForAllDataSources({ + path: 'getStudyInstanceUIDs', + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, + }), + }; + + return IWebApiDataSource.create(implementation); +} + +export { createMergeDataSourceApi }; diff --git a/extensions/default/src/MergeDataSource/types.ts b/extensions/default/src/MergeDataSource/types.ts new file mode 100644 index 000000000..ef47b4ed2 --- /dev/null +++ b/extensions/default/src/MergeDataSource/types.ts @@ -0,0 +1,46 @@ +import { ExtensionManager } from '@ohif/core'; + +export type MergeMap = { + [key: string]: { + mergeKey: string; + tagFunc: (data: unknown[], sourceName: string) => unknown[]; + }; +}; + +export type CallForAllDataSourcesAsyncOptions = { + mergeMap: object; + path: string; + args: unknown[]; + dataSourceNames: string[]; + extensionManager: ExtensionManager; + defaultDataSourceName: string; +}; + +export type CallForAllDataSourcesOptions = { + path: string; + args: unknown[]; + dataSourceNames: string[]; + extensionManager: ExtensionManager; + defaultDataSourceName: string; +}; + +export type CallForDefaultDataSourceOptions = { + path: string; + args: unknown[]; + defaultDataSourceName: string; + extensionManager: ExtensionManager; +}; + +export type CallByRetrieveAETitleOptions = { + path: string; + args: unknown[]; + defaultDataSourceName: string; + extensionManager: ExtensionManager; +}; + +export type MergeConfig = { + seriesMerge: { + dataSourceNames: string[]; + defaultDataSourceName: string; + }; +}; diff --git a/extensions/default/src/Panels/ActionButtons.tsx b/extensions/default/src/Panels/ActionButtons.tsx deleted file mode 100644 index c21f8b6b6..000000000 --- a/extensions/default/src/Panels/ActionButtons.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { useTranslation } from 'react-i18next'; - -import { LegacyButton, LegacyButtonGroup } from '@ohif/ui'; - -function ActionButtons({ onExportClick, onCreateReportClick }) { - const { t } = useTranslation('MeasurementTable'); - - return ( - - - {/* TODO Revisit design of LegacyButtonGroup later - for now use LegacyButton for its children.*/} - - {t('Export CSV')} - - - {t('Create Report')} - - - - ); -} - -ActionButtons.propTypes = { - onExportClick: PropTypes.func, - onCreateReportClick: PropTypes.func, -}; - -ActionButtons.defaultProps = { - onExportClick: () => alert('Export'), - onCreateReportClick: () => alert('Create Report'), -}; - -export default ActionButtons; diff --git a/extensions/default/src/Panels/PanelMeasurementTable.tsx b/extensions/default/src/Panels/PanelMeasurementTable.tsx index 791d878aa..7cb491660 100644 --- a/extensions/default/src/Panels/PanelMeasurementTable.tsx +++ b/extensions/default/src/Panels/PanelMeasurementTable.tsx @@ -1,8 +1,15 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; +import { useTranslation } from 'react-i18next'; import { utils, ServicesManager } from '@ohif/core'; -import { MeasurementTable, Dialog, Input, useViewportGrid, ButtonEnums } from '@ohif/ui'; -import ActionButtons from './ActionButtons'; +import { + MeasurementTable, + Dialog, + Input, + useViewportGrid, + ButtonEnums, + ActionButtons, +} from '@ohif/ui'; import debounce from 'lodash.debounce'; import createReportDialogPrompt, { @@ -18,6 +25,8 @@ export default function PanelMeasurementTable({ commandsManager, extensionManager, }): React.FunctionComponent { + const { t } = useTranslation('MeasurementTable'); + const [viewportGrid, viewportGridService] = useViewportGrid(); const { activeViewportId, viewports } = viewportGrid; const { measurementService, uiDialogService, uiNotificationService, displaySetService } = ( @@ -209,7 +218,7 @@ export default function PanelMeasurementTable({ data-cy={'measurements-panel'} >
diff --git a/extensions/default/src/Panels/PanelStudyBrowser.tsx b/extensions/default/src/Panels/PanelStudyBrowser.tsx index e1b65d401..e1e6c30c0 100644 --- a/extensions/default/src/Panels/PanelStudyBrowser.tsx +++ b/extensions/default/src/Panels/PanelStudyBrowser.tsx @@ -25,7 +25,8 @@ function PanelStudyBrowser({ // doesn't have to have such an intense shape. This works well enough for now. // Tabs --> Studies --> DisplaySets --> Thumbnails const { StudyInstanceUIDs } = useImageViewer(); - const [{ activeViewportId, viewports }, viewportGridService] = useViewportGrid(); + const [{ activeViewportId, viewports, isHangingProtocolLayout }, viewportGridService] = + useViewportGrid(); const [activeTabName, setActiveTabName] = useState('primary'); const [expandedStudyInstanceUIDs, setExpandedStudyInstanceUIDs] = useState([ ...StudyInstanceUIDs, @@ -40,7 +41,8 @@ function PanelStudyBrowser({ try { updatedViewports = hangingProtocolService.getViewportsRequireUpdate( viewportId, - displaySetInstanceUID + displaySetInstanceUID, + isHangingProtocolLayout ); } catch (error) { console.warn(error); diff --git a/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx b/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx index a1852046c..9be92e25e 100644 --- a/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx +++ b/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import PropTypes from 'prop-types'; // import PanelStudyBrowser from './PanelStudyBrowser'; @@ -18,7 +18,10 @@ function WrappedPanelStudyBrowser({ commandsManager, extensionManager, servicesM // already determined our datasource const dataSource = extensionManager.getDataSources()[0]; const _getStudiesForPatientByMRN = getStudiesForPatientByMRN.bind(null, dataSource); - const _getImageSrcFromImageId = _createGetImageSrcFromImageIdFn(extensionManager); + const _getImageSrcFromImageId = useCallback( + _createGetImageSrcFromImageIdFn(extensionManager), + [] + ); const _requestDisplaySetCreationForStudy = requestDisplaySetCreationForStudy.bind( null, dataSource diff --git a/extensions/default/src/Panels/getImageSrcFromImageId.js b/extensions/default/src/Panels/getImageSrcFromImageId.js index 5cda3dff0..ae845b922 100644 --- a/extensions/default/src/Panels/getImageSrcFromImageId.js +++ b/extensions/default/src/Panels/getImageSrcFromImageId.js @@ -6,7 +6,7 @@ function getImageSrcFromImageId(cornerstone, imageId) { return new Promise((resolve, reject) => { const canvas = document.createElement('canvas'); cornerstone.utilities - .loadImageToCanvas({ canvas, imageId }) + .loadImageToCanvas({ canvas, imageId, thumbnail: true }) .then(imageId => { resolve(canvas.toDataURL()); }) diff --git a/extensions/default/src/SOPClassHandlers/chartSOPClassHandler.ts b/extensions/default/src/SOPClassHandlers/chartSOPClassHandler.ts new file mode 100644 index 000000000..58f561e99 --- /dev/null +++ b/extensions/default/src/SOPClassHandlers/chartSOPClassHandler.ts @@ -0,0 +1,96 @@ +import { Types, DisplaySetService, utils } from '@ohif/core'; + +import { id } from '../id'; + +type InstanceMetadata = Types.InstanceMetadata; + +const SOPClassHandlerName = 'chart'; + +const CHART_MODALITY = 'CHT'; + +// Private SOPClassUid for chart data +const ChartDataSOPClassUid = '1.9.451.13215.7.3.2.7.6.1'; + +const sopClassUids = [ChartDataSOPClassUid]; + +const makeChartDataDisplaySet = (instance, sopClassUids) => { + const { + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, + SeriesDescription, + SeriesNumber, + SeriesDate, + SOPClassUID, + } = instance; + + return { + Modality: CHART_MODALITY, + loading: false, + isReconstructable: false, + displaySetInstanceUID: utils.guid(), + SeriesDescription, + SeriesNumber, + SeriesDate, + SOPInstanceUID, + SeriesInstanceUID, + StudyInstanceUID, + SOPClassHandlerId: `${id}.sopClassHandlerModule.${SOPClassHandlerName}`, + SOPClassUID, + isDerivedDisplaySet: true, + isLoaded: true, + sopClassUids, + instance, + instances: [instance], + + /** + * Adds instances to the chart displaySet, rather than creating a new one + * when user moves to a different workflow step and gets back to a step that + * recreates the chart + */ + addInstances: function (instances: InstanceMetadata[], _displaySetService: DisplaySetService) { + this.instances.push(...instances); + this.instance = this.instances[this.instances.length - 1]; + + return this; + }, + }; +}; + +function getSopClassUids(instances) { + const uniqueSopClassUidsInSeries = new Set(); + instances.forEach(instance => { + uniqueSopClassUidsInSeries.add(instance.SOPClassUID); + }); + const sopClassUids = Array.from(uniqueSopClassUidsInSeries); + + return sopClassUids; +} + +function _getDisplaySetsFromSeries(instances) { + // If the series has no instances, stop here + if (!instances || !instances.length) { + throw new Error('No instances were provided'); + } + + const sopClassUids = getSopClassUids(instances); + const displaySets = instances.map(instance => { + if (instance.Modality === CHART_MODALITY) { + return makeChartDataDisplaySet(instance, sopClassUids); + } + + throw new Error('Unsupported modality'); + }); + + return displaySets; +} + +const chartHandler = { + name: SOPClassHandlerName, + sopClassUids, + getDisplaySetsFromSeries: instances => { + return _getDisplaySetsFromSeries(instances); + }, +}; + +export { chartHandler }; diff --git a/extensions/default/src/Toolbar/LegacyLayoutSelector.tsx b/extensions/default/src/Toolbar/LegacyLayoutSelector.tsx new file mode 100644 index 000000000..341f6686e --- /dev/null +++ b/extensions/default/src/Toolbar/LegacyLayoutSelector.tsx @@ -0,0 +1,88 @@ +import React, { useEffect, useState, useCallback } from 'react'; +import PropTypes from 'prop-types'; +import { LayoutSelector as OHIFLayoutSelector, ToolbarButton } from '@ohif/ui'; +import { ServicesManager } from '@ohif/core'; + +function LegacyLayoutSelectorWithServices({ servicesManager, ...props }) { + const { toolbarService } = servicesManager.services; + + const onSelection = useCallback( + props => { + toolbarService.recordInteraction({ + interactionType: 'action', + commands: [ + { + commandName: 'setViewportGridLayout', + commandOptions: { ...props }, + context: 'DEFAULT', + }, + ], + }); + }, + [toolbarService] + ); + + return ( + + ); +} + +function LayoutSelector({ rows, columns, className, onSelection, ...rest }) { + const [isOpen, setIsOpen] = useState(false); + + const closeOnOutsideClick = () => { + if (isOpen) { + setIsOpen(false); + } + }; + + useEffect(() => { + window.addEventListener('click', closeOnOutsideClick); + return () => { + window.removeEventListener('click', closeOnOutsideClick); + }; + }, [isOpen]); + + const onInteractionHandler = () => setIsOpen(!isOpen); + const DropdownContent = isOpen ? OHIFLayoutSelector : null; + + return ( + + ) + } + isActive={isOpen} + type="toggle" + /> + ); +} + +LayoutSelector.propTypes = { + rows: PropTypes.number, + columns: PropTypes.number, + onLayoutChange: PropTypes.func, + servicesManager: PropTypes.instanceOf(ServicesManager), +}; + +LayoutSelector.defaultProps = { + rows: 3, + columns: 3, + onLayoutChange: () => {}, +}; + +export default LegacyLayoutSelectorWithServices; diff --git a/extensions/default/src/Toolbar/Toolbar.tsx b/extensions/default/src/Toolbar/Toolbar.tsx index c714968a1..b2b52211d 100644 --- a/extensions/default/src/Toolbar/Toolbar.tsx +++ b/extensions/default/src/Toolbar/Toolbar.tsx @@ -1,44 +1,37 @@ -import React, { useEffect, useState, useCallback } from 'react'; +import React from 'react'; +import { Tooltip } from '@ohif/ui'; import classnames from 'classnames'; +import { useToolbar } from '@ohif/core'; -export default function Toolbar({ servicesManager }) { - const { toolbarService } = servicesManager.services; - const [toolbarButtons, setToolbarButtons] = useState([]); +export function Toolbar({ servicesManager, buttonSection = 'primary' }) { + const { toolbarButtons, onInteraction } = useToolbar({ + servicesManager, + buttonSection, + }); - useEffect(() => { - const { unsubscribe } = toolbarService.subscribe(toolbarService.EVENTS.TOOL_BAR_MODIFIED, () => - setToolbarButtons(toolbarService.getButtonSection('primary')) - ); - - return () => { - unsubscribe(); - }; - }, [toolbarService]); - - const onInteraction = useCallback( - args => toolbarService.recordInteraction(args), - [toolbarService] - ); + if (!toolbarButtons.length) { + return null; + } return ( <> {toolbarButtons.map(toolDef => { + if (!toolDef) { + return null; + } + const { id, Component, componentProps } = toolDef; - return ( - // The margin for separating the tools on the toolbar should go here and NOT in each individual component (button) item. - // This allows for the individual items to be included in other UI components where perhaps alternative margins are desired. -
- -
+ id={id} + onInteraction={onInteraction} + servicesManager={servicesManager} + {...componentProps} + /> ); + + return
{tool}
; })} ); diff --git a/extensions/default/src/Toolbar/ToolbarButtonGroupWithServices.tsx b/extensions/default/src/Toolbar/ToolbarButtonGroupWithServices.tsx new file mode 100644 index 000000000..0e6ad1cdd --- /dev/null +++ b/extensions/default/src/Toolbar/ToolbarButtonGroupWithServices.tsx @@ -0,0 +1,36 @@ +import { ToolbarButton, ButtonGroup } from '@ohif/ui'; +import React, { useCallback } from 'react'; + +function ToolbarButtonGroupWithServices({ groupId, items, onInteraction, size }) { + const getSplitButtonItems = useCallback( + items => + items.map((item, index) => ( + { + onInteraction({ + groupId, + itemId: item.id, + commands: item.commands, + }); + }} + // Note: this is necessary since tooltip will add + // default styles to the tooltip container which + // we don't want for groups + toolTipClassName="" + /> + )), + [onInteraction, groupId] + ); + + return {getSplitButtonItems(items)}; +} + +export default ToolbarButtonGroupWithServices; diff --git a/extensions/default/src/Toolbar/ToolbarButtonWithServices.tsx b/extensions/default/src/Toolbar/ToolbarButtonWithServices.tsx deleted file mode 100644 index 24dda712b..000000000 --- a/extensions/default/src/Toolbar/ToolbarButtonWithServices.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { ToolbarButton } from '@ohif/ui'; -import React, { useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; - -function ToolbarButtonWithServices({ - id, - type, - commands, - onInteraction, - servicesManager, - ...props -}) { - const { toolbarService } = servicesManager?.services || {}; - - const [buttonsState, setButtonState] = useState({ - primaryToolId: '', - toggles: {}, - groups: {}, - }); - const { primaryToolId } = buttonsState; - - const isActive = - (type === 'tool' && id === primaryToolId) || - (type === 'toggle' && buttonsState.toggles[id] === true); - - useEffect(() => { - const { unsubscribe } = toolbarService.subscribe( - toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED, - state => { - setButtonState({ ...state }); - } - ); - - return () => { - unsubscribe(); - }; - }, [toolbarService]); - - return ( - - ); -} - -ToolbarButtonWithServices.propTypes = { - id: PropTypes.string.isRequired, - type: PropTypes.oneOf(['tool', 'action', 'toggle']).isRequired, - commands: PropTypes.arrayOf( - PropTypes.shape({ - commandName: PropTypes.string.isRequired, - context: PropTypes.string, - }) - ), - onInteraction: PropTypes.func.isRequired, - servicesManager: PropTypes.shape({ - services: PropTypes.shape({ - toolbarService: PropTypes.shape({ - subscribe: PropTypes.func.isRequired, - state: PropTypes.shape({ - primaryToolId: PropTypes.string, - toggles: PropTypes.objectOf(PropTypes.bool), - groups: PropTypes.objectOf(PropTypes.object), - }).isRequired, - }).isRequired, - }).isRequired, - }).isRequired, -}; - -export default ToolbarButtonWithServices; diff --git a/extensions/default/src/Toolbar/ToolbarLayoutSelector.tsx b/extensions/default/src/Toolbar/ToolbarLayoutSelector.tsx index 430cd5aba..0f4b2e0c8 100644 --- a/extensions/default/src/Toolbar/ToolbarLayoutSelector.tsx +++ b/extensions/default/src/Toolbar/ToolbarLayoutSelector.tsx @@ -1,38 +1,142 @@ import React, { useEffect, useState, useCallback } from 'react'; import PropTypes from 'prop-types'; -import { LayoutSelector as OHIFLayoutSelector, ToolbarButton } from '@ohif/ui'; +import { LayoutSelector as OHIFLayoutSelector, ToolbarButton, LayoutPreset } from '@ohif/ui'; import { ServicesManager } from '@ohif/core'; -function ToolbarLayoutSelectorWithServices({ servicesManager, ...props }) { - const { toolbarService } = servicesManager.services; - - const onSelection = useCallback( - props => { - toolbarService.recordInteraction({ - interactionType: 'action', - commands: [ - { - commandName: 'setViewportGridLayout', - commandOptions: { ...props }, - context: 'DEFAULT', - }, - ], - }); +const defaultCommonPresets = [ + { + icon: 'layout-common-1x1', + commandOptions: { + numRows: 1, + numCols: 1, }, - [toolbarService] + }, + { + icon: 'layout-common-1x2', + commandOptions: { + numRows: 1, + numCols: 2, + }, + }, + { + icon: 'layout-common-2x2', + commandOptions: { + numRows: 2, + numCols: 2, + }, + }, + { + icon: 'layout-common-2x3', + commandOptions: { + numRows: 2, + numCols: 3, + }, + }, +]; + +const _areSelectorsValid = (hp, displaySets, hangingProtocolService) => { + if (!hp.displaySetSelectors || Object.values(hp.displaySetSelectors).length === 0) { + return true; + } + + return hangingProtocolService.areRequiredSelectorsValid( + Object.values(hp.displaySetSelectors), + displaySets[0] ); +}; + +const generateAdvancedPresets = ({ servicesManager }) => { + const { hangingProtocolService, viewportGridService, displaySetService } = + servicesManager.services; + + const hangingProtocols = Array.from(hangingProtocolService.protocols.values()); + + const viewportId = viewportGridService.getActiveViewportId(); + + if (!viewportId) { + return []; + } + const displaySetInsaneUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId); + + if (!displaySetInsaneUIDs) { + return []; + } + + const displaySets = displaySetInsaneUIDs.map(uid => displaySetService.getDisplaySetByUID(uid)); + + return hangingProtocols + .map(hp => { + if (!hp.isPreset) { + return null; + } + + const areValid = _areSelectorsValid(hp, displaySets, hangingProtocolService); + + return { + icon: hp.icon, + title: hp.name, + commandOptions: { + protocolId: hp.id, + }, + disabled: !areValid, + }; + }) + .filter(preset => preset !== null); +}; + +function ToolbarLayoutSelectorWithServices({ commandsManager, servicesManager, ...props }) { + const [isDisabled, setIsDisabled] = useState(false); + + const handleMouseEnter = () => { + setIsDisabled(false); + }; + + const onSelection = useCallback(props => { + commandsManager.run({ + commandName: 'setViewportGridLayout', + commandOptions: { ...props }, + }); + setIsDisabled(true); + }, []); + + const onSelectionPreset = useCallback(props => { + commandsManager.run({ + commandName: 'setHangingProtocol', + commandOptions: { ...props }, + }); + setIsDisabled(true); + }, []); return ( - +
+ +
); } -function LayoutSelector({ rows, columns, className, onSelection, ...rest }) { +function LayoutSelector({ + rows, + columns, + className, + onSelection, + onSelectionPreset, + servicesManager, + tooltipDisabled, + ...rest +}) { const [isOpen, setIsOpen] = useState(false); + const { customizationService } = servicesManager.services; + const commonPresets = customizationService.get('commonPresets') || defaultCommonPresets; + const advancedPresets = + customizationService.get('advancedPresets') || generateAdvancedPresets({ servicesManager }); + const closeOnOutsideClick = () => { if (isOpen) { setIsOpen(false); @@ -46,24 +150,69 @@ function LayoutSelector({ rows, columns, className, onSelection, ...rest }) { }; }, [isOpen]); - const onInteractionHandler = () => setIsOpen(!isOpen); + const onInteractionHandler = () => { + setIsOpen(!isOpen); + }; const DropdownContent = isOpen ? OHIFLayoutSelector : null; return ( +
+
+
Common
+ +
+ {commonPresets.map((preset, index) => ( + + ))} +
+ +
+ +
Advanced
+ +
+ {advancedPresets.map((preset, index) => ( + + ))} +
+
+ +
+
Custom
+ +

+ Hover to select

rows and columns

Click to apply +

+
+
) } isActive={isOpen} @@ -80,8 +229,8 @@ LayoutSelector.propTypes = { }; LayoutSelector.defaultProps = { + columns: 4, rows: 3, - columns: 3, onLayoutChange: () => {}, }; diff --git a/extensions/default/src/Toolbar/ToolbarSplitButtonWithServices.tsx b/extensions/default/src/Toolbar/ToolbarSplitButtonWithServices.tsx index a8943b16f..5dfee4ad5 100644 --- a/extensions/default/src/Toolbar/ToolbarSplitButtonWithServices.tsx +++ b/extensions/default/src/Toolbar/ToolbarSplitButtonWithServices.tsx @@ -1,11 +1,8 @@ -import { SplitButton, Icon, ToolbarButton } from '@ohif/ui'; -import React, { useEffect, useState } from 'react'; +import { SplitButton, ToolbarButton } from '@ohif/ui'; +import React, { useCallback } from 'react'; import PropTypes from 'prop-types'; -import classNames from 'classnames'; function ToolbarSplitButtonWithServices({ - isRadio, - isAction, groupId, primary, secondary, @@ -16,123 +13,35 @@ function ToolbarSplitButtonWithServices({ }) { const { toolbarService } = servicesManager?.services; - const handleItemClick = (item, index) => { - const { id, type, commands } = item; - onInteraction({ - groupId, - itemId: id, - interactionType: type, - commands, - }); - - setState(state => ({ - ...state, - primary: !isAction && isRadio ? { ...item, index } : state.primary, - isExpanded: false, - items: getSplitButtonItems(items).filter(item => - isRadio && !isAction ? item.index !== index : true - ), - })); - }; - /* Bubbles up individual item clicks */ - const getSplitButtonItems = items => - items.map((item, index) => ({ - ...item, - index, - onClick: () => handleItemClick(item, index), - })); - - const [buttonsState, setButtonState] = useState({ - primaryToolId: '', - toggles: {}, - groups: {}, - }); - - const [state, setState] = useState({ - primary, - items: getSplitButtonItems(items).filter(item => - isRadio && !isAction ? item.id !== primary.id : true - ), - }); - - const { primaryToolId, toggles } = buttonsState; - - const isPrimaryToggle = state.primary.type === 'toggle'; - - const isPrimaryActive = - (state.primary.type === 'tool' && primaryToolId === state.primary.id) || - (isPrimaryToggle && toggles[state.primary.id] === true); + const getSplitButtonItems = useCallback( + items => + items.map((item, index) => ({ + ...item, + index, + onClick: () => { + onInteraction({ + groupId, + itemId: item.id, + commands: item.commands, + }); + }, + })), + [] + ); const PrimaryButtonComponent = - toolbarService?.getButtonComponentForUIType(state.primary.uiType) ?? ToolbarButton; + toolbarService?.getButtonComponentForUIType(primary.uiType) ?? ToolbarButton; - useEffect(() => { - const { unsubscribe } = toolbarService.subscribe( - toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED, - state => { - setButtonState({ ...state }); - } - ); - - return () => { - unsubscribe(); - }; - }, [toolbarService]); - - const updatedItems = state.items.map(item => { - const isActive = item.type === 'tool' && primaryToolId === item.id; - - // We could have added the - // item.type === 'toggle' && toggles[item.id] === true - // too but that makes the button active when the toggle is active under it - // which feels weird - return { - ...item, - isActive, - }; - }); - - const DefaultListItemRenderer = ({ type, icon, label, t, id }) => { - const isActive = type === 'toggle' && toggles[id] === true; - - return ( -
- {icon && ( - - - - )} - {t(label)} -
- ); - }; - - const listItemRenderer = renderer || DefaultListItemRenderer; + const listItemRenderer = renderer; return ( item.isActive)} - isToggle={isPrimaryToggle} onInteraction={onInteraction} Component={props => ( show({ content: AboutModal, - title: 'About OHIF Viewer', + title: t('AboutModal:About OHIF Viewer'), contentProps: { versionNumber, commitHash }, + containerDimensions: 'max-w-4xl max-h-4xl', }), }, { @@ -62,8 +61,9 @@ function ViewerHeader({ hotkeysManager, extensionManager, servicesManager }) { icon: 'settings', onClick: () => show({ - title: t('UserPreferencesModal:User Preferences'), + title: t('UserPreferencesModal:User preferences'), content: UserPreferences, + containerDimensions: 'w-[70%] max-w-[900px]', contentProps: { hotkeyDefaults: hotkeysManager.getValidHotkeyDefinitions(hotkeyDefaults), hotkeyDefinitions, @@ -105,9 +105,18 @@ function ViewerHeader({ hotkeysManager, extensionManager, servicesManager }) { isReturnEnabled={!!appConfig.showStudyList} onClickReturnButton={onClickReturnButton} WhiteLabeling={appConfig.whiteLabeling} + showPatientInfo={appConfig.showPatientInfo} + servicesManager={servicesManager} + Secondary={ + + } + appConfig={appConfig} > -
+
diff --git a/extensions/default/src/ViewerLayout/index.tsx b/extensions/default/src/ViewerLayout/index.tsx index e045bca33..b47cca83b 100644 --- a/extensions/default/src/ViewerLayout/index.tsx +++ b/extensions/default/src/ViewerLayout/index.tsx @@ -1,7 +1,7 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import PropTypes from 'prop-types'; -import { SidePanel, ErrorBoundary, LoadingIndicatorProgress } from '@ohif/ui'; +import { ErrorBoundary, LoadingIndicatorProgress, InvestigationalUseDialog } from '@ohif/ui'; import { ServicesManager, HangingProtocolService, CommandsManager } from '@ohif/core'; import { useAppConfig } from '@state'; import ViewerHeader from './ViewerHeader'; @@ -16,16 +16,24 @@ function ViewerLayout({ // From Modes viewports, ViewportGridComp, - leftPanels = [], - rightPanels = [], - leftPanelDefaultClosed = false, - rightPanelDefaultClosed = false, + leftPanelClosed = false, + rightPanelClosed = false, }): React.FunctionComponent { const [appConfig] = useAppConfig(); - const { hangingProtocolService } = servicesManager.services; + const { panelService, hangingProtocolService } = servicesManager.services; const [showLoadingIndicator, setShowLoadingIndicator] = useState(appConfig.showLoadingIndicator); + const hasPanels = useCallback( + (side): boolean => !!panelService.getPanels(side).length, + [panelService] + ); + + const [hasRightPanels, setHasRightPanels] = useState(hasPanels('right')); + const [hasLeftPanels, setHasLeftPanels] = useState(hasPanels('left')); + const [leftPanelClosedState, setLeftPanelClosed] = useState(leftPanelClosed); + const [rightPanelClosedState, setRightPanelClosed] = useState(rightPanelClosed); + /** * Set body classes (tailwindcss) that don't allow vertical * or horizontal overflow (no scrolling). Also guarantee window @@ -43,35 +51,13 @@ function ViewerLayout({ const getComponent = id => { const entry = extensionManager.getModuleEntry(id); - if (!entry) { + if (!entry || !entry.component) { throw new Error( - `${id} is not valid for an extension module. Please verify your configuration or ensure that the extension is properly registered. It's also possible that your mode is utilizing a module from an extension that hasn't been included in its dependencies (add the extension to the "extensionDependencies" array in your mode's index.js file)` + `${id} is not valid for an extension module or no component found from extension ${id}. Please verify your configuration or ensure that the extension is properly registered. It's also possible that your mode is utilizing a module from an extension that hasn't been included in its dependencies (add the extension to the "extensionDependencies" array in your mode's index.js file). Check the reference string to the extension in your Mode configuration` ); } - let content; - if (entry && entry.component) { - content = entry.component; - } else { - throw new Error( - `No component found from extension ${id}. Check the reference string to the extension in your Mode configuration` - ); - } - - return { entry, content }; - }; - - const getPanelData = id => { - const { content, entry } = getComponent(id); - - return { - id: entry.id, - iconName: entry.iconName, - iconLabel: entry.iconLabel, - label: entry.label, - name: entry.name, - content, - }; + return { entry, content: entry.component }; }; useEffect(() => { @@ -100,8 +86,26 @@ function ViewerLayout({ }; }; - const leftPanelComponents = leftPanels.map(getPanelData); - const rightPanelComponents = rightPanels.map(getPanelData); + useEffect(() => { + const { unsubscribe } = panelService.subscribe( + panelService.EVENTS.PANELS_CHANGED, + ({ options }) => { + setHasLeftPanels(hasPanels('left')); + setHasRightPanels(hasPanels('right')); + if (options?.leftPanelClosed !== undefined) { + setLeftPanelClosed(options.leftPanelClosed); + } + if (options?.rightPanelClosed !== undefined) { + setRightPanelClosed(options.rightPanelClosed); + } + } + ); + + return () => { + unsubscribe(); + }; + }, [panelService, hasPanels]); + const viewportComponents = viewports.map(getViewportComponentData); return ( @@ -110,6 +114,7 @@ function ViewerLayout({ hotkeysManager={hotkeysManager} extensionManager={extensionManager} servicesManager={servicesManager} + appConfig={appConfig} />
{showLoadingIndicator && } {/* LEFT SIDEPANELS */} - {leftPanelComponents.length ? ( + {hasLeftPanels ? ( @@ -140,18 +144,19 @@ function ViewerLayout({
- {rightPanelComponents.length ? ( + {hasRightPanels ? ( ) : null}
+ +
); } @@ -166,8 +171,8 @@ ViewerLayout.propTypes = { // From modes leftPanels: PropTypes.array, rightPanels: PropTypes.array, - leftPanelDefaultClosed: PropTypes.bool.isRequired, - rightPanelDefaultClosed: PropTypes.bool.isRequired, + leftPanelClosed: PropTypes.bool.isRequired, + rightPanelClosed: PropTypes.bool.isRequired, /** Responsible for rendering our grid of viewports; provided by consuming application */ children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired, viewports: PropTypes.array, diff --git a/extensions/default/src/commandsModule.ts b/extensions/default/src/commandsModule.ts index 3f22e3589..76507b203 100644 --- a/extensions/default/src/commandsModule.ts +++ b/extensions/default/src/commandsModule.ts @@ -18,6 +18,7 @@ export type HangingProtocolParams = { stageIndex?: number; activeStudyUID?: string; stageId?: string; + reset?: false; }; export type UpdateViewportDisplaySetParams = { @@ -25,15 +26,6 @@ export type UpdateViewportDisplaySetParams = { excludeNonImageModalities?: boolean; }; -/** - * Determine if a command is a hanging protocol one. - * For now, just use the two hanging protocol commands that are in this - * commands module, but if others get added elsewhere this may need enhancing. - */ -const isHangingProtocolCommand = command => - command && - (command.commandName === 'setHangingProtocol' || command.commandName === 'toggleHangingProtocol'); - const commandsModule = ({ servicesManager, commandsManager, @@ -46,7 +38,6 @@ const commandsModule = ({ viewportGridService, displaySetService, stateSyncService, - toolbarService, } = (servicesManager as ServicesManager).services; // Define a context menu controller for use with any context menus @@ -107,38 +98,6 @@ const commandsModule = ({ measurementService.clear(); }, - /** - * Toggles off all tools which contain a commandName of setHangingProtocol - * or toggleHangingProtocol, and which match/don't match the protocol id/stage - */ - toggleHpTools: () => { - const { - protocol, - stageIndex: toggleStageIndex, - stage, - } = hangingProtocolService.getActiveProtocol(); - const enableListener = button => { - if (!button.id) { - return; - } - const { commands, items } = button.props || button; - if (items) { - items.forEach(enableListener); - } - const hpCommand = commands?.find?.(isHangingProtocolCommand); - if (!hpCommand) { - return; - } - const { protocolId, stageIndex, stageId } = hpCommand.commandOptions; - const isActive = - (!protocolId || protocolId === protocol.id) && - (stageIndex === undefined || stageIndex === toggleStageIndex) && - (!stageId || stageId === stage.id); - toolbarService.setToggled(button.id, isActive); - }; - Object.values(toolbarService.getButtons()).forEach(enableListener); - }, - /** * Sets the specified protocol * 1. Records any existing state using the viewport grid service @@ -170,14 +129,12 @@ const commandsModule = ({ stageIndex, reset = false, }: HangingProtocolParams): boolean => { - const primaryToolBeforeHPChange = toolbarService.getActivePrimaryTool(); try { // Stores in the state the display set selector id to displaySetUID mapping // Pass in viewportId for the active viewport. This item will get set as // the activeViewportId const state = viewportGridService.getState(); const hpInfo = hangingProtocolService.getState(); - const { protocol: oldProtocol } = hangingProtocolService.getActiveProtocol(); const stateSyncReduce = reuseCachedLayouts(state, hangingProtocolService, stateSyncService); const { hangingProtocolStageIndexMap, viewportGridStore, displaySetSelectorMap } = stateSyncReduce; @@ -240,44 +197,9 @@ const commandsModule = ({ `${activeStudyUID || hpInfo.activeStudyUID}:activeDisplaySet:0` ]; stateSyncService.store(stateSyncReduce); - // This is a default action applied - const { protocol } = hangingProtocolService.getActiveProtocol(); - actions.toggleHpTools(); - - // try to use the same tool in the new hanging protocol stage - const primaryButton = toolbarService.getButton(primaryToolBeforeHPChange); - if (primaryButton) { - // is there any type of interaction on this button, if not it might be in the - // items. This is a bit of a hack, but it works for now. - - let interactionType = primaryButton.props?.interactionType; - - if (!interactionType && primaryButton.props?.items) { - const firstItem = primaryButton.props.items[0]; - interactionType = firstItem.props?.interactionType || firstItem.props?.type; - } - - if (interactionType) { - toolbarService.recordInteraction({ - interactionType, - ...primaryButton.props, - }); - } - } - - // Send the notification about updating the state - if (protocolId !== hpInfo.protocolId) { - // The old protocol callbacks are used for turning off things - // like crosshairs when moving to the new HP - commandsManager.run(oldProtocol.callbacks?.onProtocolExit); - // The new protocol callback is used for things like - // activating modes etc. - } - commandsManager.run(protocol.callbacks?.onProtocolEnter); return true; } catch (e) { console.error(e); - actions.toggleHpTools(); uiNotificationService.show({ title: 'Apply Hanging Protocol', message: 'The hanging protocol could not be applied.', @@ -349,7 +271,7 @@ const commandsModule = ({ /** * Changes the viewport grid layout in terms of the MxN layout. */ - setViewportGridLayout: ({ numRows, numCols }) => { + setViewportGridLayout: ({ numRows, numCols, isHangingProtocolLayout = false }) => { const { protocol } = hangingProtocolService.getActiveProtocol(); const onLayoutChange = protocol.callbacks?.onLayoutChange; if (commandsManager.run(onLayoutChange, { numRows, numCols }) === false) { @@ -364,6 +286,7 @@ const commandsModule = ({ const findOrCreateViewport = layoutFindOrCreate.bind( null, hangingProtocolService, + isHangingProtocolLayout, stateReduce.viewportsByPosition ); @@ -371,6 +294,7 @@ const commandsModule = ({ numRows, numCols, findOrCreateViewport, + isHangingProtocolLayout, }); stateSyncService.store(stateReduce); }; @@ -380,7 +304,7 @@ const commandsModule = ({ toggleOneUp() { const viewportGridState = viewportGridService.getState(); - const { activeViewportId, viewports, layout } = viewportGridState; + const { activeViewportId, viewports, layout, isHangingProtocolLayout } = viewportGridState; const { displaySetInstanceUIDs, displaySetOptions, viewportOptions } = viewports.get(activeViewportId); @@ -407,7 +331,8 @@ const commandsModule = ({ .map(displaySetInstanceUID => hangingProtocolService.getViewportsRequireUpdate( viewportIdToUpdate, - displaySetInstanceUID + displaySetInstanceUID, + isHangingProtocolLayout ) ) .flat(); @@ -444,6 +369,7 @@ const commandsModule = ({ activeViewportId: viewportIdToUpdate, layoutOptions, findOrCreateViewport, + isHangingProtocolLayout: true, }); } else { // We are not in one-up, so toggle to one up. @@ -468,6 +394,7 @@ const commandsModule = ({ numRows: 1, numCols: 1, findOrCreateViewport, + isHangingProtocolLayout: true, }); // Subscribe to ANY (i.e. manual and hanging protocol) layout changes so that @@ -523,6 +450,7 @@ const commandsModule = ({ displaySetInstanceUID, onClose: UIModalService.hide, }, + containerDimensions: 'w-[70%] max-w-[900px]', title: 'DICOM Tag Browser', }); }, @@ -585,7 +513,8 @@ const commandsModule = ({ currentDisplaySets.sort(dsSortFn); - const { activeViewportId, viewports } = viewportGridService.getState(); + const { activeViewportId, viewports, isHangingProtocolLayout } = + viewportGridService.getState(); const { displaySetInstanceUIDs } = viewports.get(activeViewportId); @@ -619,7 +548,8 @@ const commandsModule = ({ try { updatedViewports = hangingProtocolService.getViewportsRequireUpdate( activeViewportId, - displaySetInstanceUID + displaySetInstanceUID, + isHangingProtocolLayout ); } catch (error) { console.warn(error); @@ -647,56 +577,38 @@ const commandsModule = ({ }, clearMeasurements: { commandFn: actions.clearMeasurements, - storeContexts: [], - options: {}, }, displayNotification: { commandFn: actions.displayNotification, - storeContexts: [], - options: {}, }, setHangingProtocol: { commandFn: actions.setHangingProtocol, - storeContexts: [], - options: {}, }, toggleHangingProtocol: { commandFn: actions.toggleHangingProtocol, - storeContexts: [], - options: {}, }, navigateHistory: { commandFn: actions.navigateHistory, - storeContexts: [], - options: {}, }, nextStage: { commandFn: actions.deltaStage, - storeContexts: [], options: { direction: 1 }, }, previousStage: { commandFn: actions.deltaStage, - storeContexts: [], options: { direction: -1 }, }, setViewportGridLayout: { commandFn: actions.setViewportGridLayout, - storeContexts: [], - options: {}, }, toggleOneUp: { commandFn: actions.toggleOneUp, - storeContexts: [], - options: {}, }, openDICOMTagViewer: { commandFn: actions.openDICOMTagViewer, }, updateViewportDisplaySet: { commandFn: actions.updateViewportDisplaySet, - storeContexts: [], - options: {}, }, }; diff --git a/extensions/default/src/components/ProgressDropdownWithService/ProgressDropdownWithService.tsx b/extensions/default/src/components/ProgressDropdownWithService/ProgressDropdownWithService.tsx new file mode 100644 index 000000000..e3ed34f3f --- /dev/null +++ b/extensions/default/src/components/ProgressDropdownWithService/ProgressDropdownWithService.tsx @@ -0,0 +1,109 @@ +import React, { useEffect, useState, useCallback, ReactElement } from 'react'; +import { ServicesManager } from '@ohif/core'; +import { ProgressDropdown } from '@ohif/ui'; + +const workflowStepsToDropdownOptions = (steps = []) => + steps.map(step => ({ + label: step.name, + value: step.id, + info: step.info, + activated: false, + completed: false, + })); + +function ProgressDropdownWithService({ + servicesManager, +}: { + servicesManager: ServicesManager; +}): ReactElement { + const { workflowStepsService } = servicesManager.services; + const [activeStepId, setActiveStepId] = useState(workflowStepsService.activeWorkflowStep?.id); + + const [dropdownOptions, setDropdownOptions] = useState( + workflowStepsToDropdownOptions(workflowStepsService.workflowSteps) + ); + + const setCurrentAndPreviousOptionsAsCompleted = useCallback(currentOption => { + if (currentOption.completed) { + return; + } + + setDropdownOptions(prevOptions => { + const newOptionsState = [...prevOptions]; + const startIndex = newOptionsState.findIndex(option => option.value === currentOption.value); + + for (let i = startIndex; i >= 0; i--) { + const option = newOptionsState[i]; + + if (option.completed) { + break; + } + + newOptionsState[i] = { + ...option, + completed: true, + }; + } + + return newOptionsState; + }); + }, []); + + const handleDropdownChange = useCallback( + ({ selectedOption }) => { + if (!selectedOption) { + return; + } + + // TODO: Steps should be marked as completed after user has + // completed some action when required (not implemented) + setCurrentAndPreviousOptionsAsCompleted(selectedOption); + setActiveStepId(selectedOption.value); + }, + [setCurrentAndPreviousOptionsAsCompleted] + ); + + useEffect(() => { + let timeoutId; + + if (activeStepId) { + // We've used setTimeout to give it more time to update the UI since + // create3DFilterableFromDataArray from Texture.js may take 600+ ms to run + // when there is a new series to load in the next step but that resulted + // in the followed React error when updating the content from left/right panels + // and all component states were being lost: + // Error: Can't perform a React state update on an unmounted component + workflowStepsService.setActiveWorkflowStep(activeStepId); + } + + return () => clearTimeout(timeoutId); + }, [activeStepId, workflowStepsService]); + + useEffect(() => { + const { unsubscribe: unsubStepsChanged } = workflowStepsService.subscribe( + workflowStepsService.EVENTS.STEPS_CHANGED, + () => setDropdownOptions(workflowStepsToDropdownOptions(workflowStepsService.workflowSteps)) + ); + + const { unsubscribe: unsubActiveStepChanged } = workflowStepsService.subscribe( + workflowStepsService.EVENTS.ACTIVE_STEP_CHANGED, + + () => setActiveStepId(workflowStepsService.activeWorkflowStep.id) + ); + + return () => { + unsubStepsChanged(); + unsubActiveStepChanged(); + }; + }, [servicesManager, workflowStepsService]); + + return ( + + ); +} + +export default ProgressDropdownWithService; diff --git a/extensions/default/src/components/ProgressDropdownWithService/index.js b/extensions/default/src/components/ProgressDropdownWithService/index.js new file mode 100644 index 000000000..c609cf014 --- /dev/null +++ b/extensions/default/src/components/ProgressDropdownWithService/index.js @@ -0,0 +1 @@ +export { default } from './ProgressDropdownWithService'; diff --git a/extensions/default/src/findViewportsByPosition.ts b/extensions/default/src/findViewportsByPosition.ts index 6b8b54ab7..7d0d10f83 100644 --- a/extensions/default/src/findViewportsByPosition.ts +++ b/extensions/default/src/findViewportsByPosition.ts @@ -16,6 +16,7 @@ import { StateSyncService } from '@ohif/core'; */ export const findOrCreateViewport = ( hangingProtocolService, + isHangingProtocolLayout, viewportsByPosition, position: number, positionId: string, @@ -31,8 +32,13 @@ export const findOrCreateViewport = ( if (!options.inDisplay) { options.inDisplay = [...viewportsByPosition.initialInDisplay]; } - // See if there is a default viewport for new views. - const missing = hangingProtocolService.getMissingViewport(protocolId, stageIndex, options); + + // See if there is a default viewport for new views + const missing = hangingProtocolService.getMissingViewport( + isHangingProtocolLayout ? protocolId : 'default', + stageIndex, + options + ); if (missing) { const displaySetInstanceUIDs = missing.displaySetsInfo.map(it => it.displaySetInstanceUID); options.inDisplay.push(...displaySetInstanceUIDs); @@ -44,6 +50,13 @@ export const findOrCreateViewport = ( }, }; } + + // and lastly if there is no default viewport, then we see if we can grab the + // viewportsByPosition at the position index and use that + // const candidate = Object.values(viewportsByPosition)[position]; + + // // if it has something to display, then we can use it + // return candidate?.displaySetInstanceUIDs ? candidate : {}; return {}; }; diff --git a/extensions/default/src/getCustomizationModule.tsx b/extensions/default/src/getCustomizationModule.tsx index 1da3f1871..b2d471594 100644 --- a/extensions/default/src/getCustomizationModule.tsx +++ b/extensions/default/src/getCustomizationModule.tsx @@ -1,6 +1,7 @@ import { CustomizationService } from '@ohif/core'; import React from 'react'; import DataSourceSelector from './Panels/DataSourceSelector'; +import ProgressDropdownWithService from './components/ProgressDropdownWithService'; import DataSourceConfigurationComponent from './Components/DataSourceConfigurationComponent'; import { GoogleCloudDataSourceConfigurationAPI } from './DataSourceConfigurationAPI/GoogleCloudDataSourceConfigurationAPI'; @@ -93,8 +94,8 @@ export default function getCustomizationModule({ servicesManager, extensionManag instance && this.attribute ? instance[this.attribute] : this.contentF && typeof this.contentF === 'function' - ? this.contentF(props) - : null; + ? this.contentF(props) + : null; if (!value) { return null; } @@ -154,6 +155,11 @@ export default function getCustomizationModule({ servicesManager, extensionManag extensionManager ), }, + + { + id: 'progressDropdownWithServiceComponent', + component: ProgressDropdownWithService, + }, ], }, ]; diff --git a/extensions/default/src/getDataSourcesModule.js b/extensions/default/src/getDataSourcesModule.js index 6eab56a2f..8c918e0c6 100644 --- a/extensions/default/src/getDataSourcesModule.js +++ b/extensions/default/src/getDataSourcesModule.js @@ -6,6 +6,7 @@ import { createDicomWebApi } from './DicomWebDataSource/index.js'; import { createDicomJSONApi } from './DicomJSONDataSource/index.js'; import { createDicomLocalApi } from './DicomLocalDataSource/index.js'; import { createDicomWebProxyApi } from './DicomWebProxyDataSource/index.js'; +import { createMergeDataSourceApi } from './MergeDataSource/index'; /** * @@ -32,6 +33,11 @@ function getDataSourcesModule() { type: 'localApi', createDataSource: createDicomLocalApi, }, + { + name: 'merge', + type: 'mergeApi', + createDataSource: createMergeDataSourceApi, + }, ]; } diff --git a/extensions/default/src/getDisplaySetMessages.ts b/extensions/default/src/getDisplaySetMessages.ts index 842c0bcd1..80bffe0b5 100644 --- a/extensions/default/src/getDisplaySetMessages.ts +++ b/extensions/default/src/getDisplaySetMessages.ts @@ -10,9 +10,15 @@ import checkSingleFrames from './utils/validations/checkSingleFrames'; */ export default function getDisplaySetMessages( instances: Array, - isReconstructable: boolean + isReconstructable: boolean, + isDynamicVolume: boolean ): DisplaySetMessageList { const messages = new DisplaySetMessageList(); + + if (isDynamicVolume) { + return messages; + } + if (!instances.length) { messages.addMessage(DisplaySetMessage.CODES.NO_VALID_INSTANCES); return; diff --git a/extensions/default/src/getHangingProtocolModule.js b/extensions/default/src/getHangingProtocolModule.js index 596e3031d..f9ad9bbbb 100644 --- a/extensions/default/src/getHangingProtocolModule.js +++ b/extensions/default/src/getHangingProtocolModule.js @@ -14,6 +14,7 @@ const defaultProtocol = { editableBy: {}, protocolMatchingRules: [], toolGroupIds: ['default'], + hpInitiationCriteria: { minSeriesLoaded: 1 }, // -1 would be used to indicate active only, whereas other values are // the number of required priors referenced - so 0 means active with // 0 or more priors. diff --git a/extensions/default/src/getPanelModule.tsx b/extensions/default/src/getPanelModule.tsx index 559f98fca..e294805df 100644 --- a/extensions/default/src/getPanelModule.tsx +++ b/extensions/default/src/getPanelModule.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { WrappedPanelStudyBrowser, PanelMeasurementTable } from './Panels'; +import i18n from 'i18next'; // TODO: // - No loading UI exists yet @@ -22,7 +23,7 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager }) name: 'seriesList', iconName: 'tab-studies', iconLabel: 'Studies', - label: 'Studies', + label: i18n.t('SidePanel:Studies'), component: WrappedPanelStudyBrowser.bind(null, { commandsManager, extensionManager, @@ -33,8 +34,8 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager }) name: 'measure', iconName: 'tab-linear', iconLabel: 'Measure', - label: 'Measurements', - secondaryLabel: 'Measurements', + label: i18n.t('SidePanel:Measurements'), + secondaryLabel: i18n.t('SidePanel:Measurements'), component: wrappedMeasurementPanel, }, ]; diff --git a/extensions/default/src/getSopClassHandlerModule.js b/extensions/default/src/getSopClassHandlerModule.js index 9c1580a6c..28a677ff7 100644 --- a/extensions/default/src/getSopClassHandlerModule.js +++ b/extensions/default/src/getSopClassHandlerModule.js @@ -5,23 +5,81 @@ import isDisplaySetReconstructable from '@ohif/core/src/utils/isDisplaySetRecons import { id } from './id'; import getDisplaySetMessages from './getDisplaySetMessages'; import getDisplaySetsFromUnsupportedSeries from './getDisplaySetsFromUnsupportedSeries'; +import { chartHandler } from './SOPClassHandlers/chartSOPClassHandler'; +const DEFAULT_VOLUME_LOADER_SCHEME = 'cornerstoneStreamingImageVolume'; +const DYNAMIC_VOLUME_LOADER_SCHEME = 'cornerstoneStreamingDynamicImageVolume'; const sopClassHandlerName = 'stack'; +let appContext = {}; + +const getDynamicVolumeInfo = instances => { + const { extensionManager } = appContext; + + if (!extensionManager) { + throw new Error('extensionManager is not available'); + } + + const imageIds = instances.map(({ imageId }) => imageId); + const volumeLoaderUtility = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.volumeLoader' + ); + const { getDynamicVolumeInfo: csGetDynamicVolumeInfo } = volumeLoaderUtility.exports; + + return csGetDynamicVolumeInfo(imageIds); +}; const isMultiFrame = instance => { return instance.NumberOfFrames > 1; }; +function getDisplaySetInfo(instances) { + const dynamicVolumeInfo = getDynamicVolumeInfo(instances); + const { isDynamicVolume, timePoints } = dynamicVolumeInfo; + let displaySetInfo; + + const { appConfig } = appContext; + + if (isDynamicVolume) { + const timePoint = timePoints[0]; + const instancesMap = new Map(); + + // O(n) to convert it into a map and O(1) to find each instance + instances.forEach(instance => instancesMap.set(instance.imageId, instance)); + + const firstTimePointInstances = timePoint.map(imageId => instancesMap.get(imageId)); + + displaySetInfo = isDisplaySetReconstructable(firstTimePointInstances, appConfig); + } else { + displaySetInfo = isDisplaySetReconstructable(instances, appConfig); + } + + return { + isDynamicVolume, + ...displaySetInfo, + dynamicVolumeInfo, + }; +} + const makeDisplaySet = instances => { const instance = instances[0]; const imageSet = new ImageSet(instances); - const { value: isReconstructable, averageSpacingBetweenFrames } = - isDisplaySetReconstructable(instances); + const { + isDynamicVolume, + value: isReconstructable, + averageSpacingBetweenFrames, + dynamicVolumeInfo, + } = getDisplaySetInfo(instances); + + const volumeLoaderSchema = isDynamicVolume + ? DYNAMIC_VOLUME_LOADER_SCHEME + : DEFAULT_VOLUME_LOADER_SCHEME; + // set appropriate attributes to image set... - const messages = getDisplaySetMessages(instances, isReconstructable); + const messages = getDisplaySetMessages(instances, isReconstructable, isDynamicVolume); imageSet.setAttributes({ + volumeLoaderSchema, displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID SeriesDate: instance.SeriesDate, SeriesTime: instance.SeriesTime, @@ -39,6 +97,8 @@ const makeDisplaySet = instances => { isReconstructable, messages, averageSpacingBetweenFrames: averageSpacingBetweenFrames || null, + isDynamicVolume, + dynamicVolumeInfo, }); // Sort the images in this series if needed @@ -88,7 +148,6 @@ function getSopClassUids(instances) { * - For all Image types that are stackable, create * a displaySet with a stack of images * - * @param {Array} sopClassHandlerModules List of SOP Class Modules * @param {SeriesMetadata} series The series metadata object from which the display sets will be created * @returns {Array} The list of display sets created for the given series object */ @@ -203,7 +262,9 @@ const sopClassUids = [ sopClassDictionary.EnhancedUSVolumeStorage, ]; -function getSopClassHandlerModule() { +function getSopClassHandlerModule(appContextParam) { + appContext = appContextParam; + return [ { name: sopClassHandlerName, @@ -215,6 +276,11 @@ function getSopClassHandlerModule() { sopClassUids: [], getDisplaySetsFromSeries: getDisplaySetsFromUnsupportedSeries, }, + { + name: chartHandler.name, + sopClassUids: chartHandler.sopClassUids, + getDisplaySetsFromSeries: chartHandler.getDisplaySetsFromSeries, + }, ]; } diff --git a/extensions/default/src/getToolbarModule.tsx b/extensions/default/src/getToolbarModule.tsx index eacdffd7b..762aea9f0 100644 --- a/extensions/default/src/getToolbarModule.tsx +++ b/extensions/default/src/getToolbarModule.tsx @@ -1,39 +1,73 @@ import ToolbarDivider from './Toolbar/ToolbarDivider'; import ToolbarLayoutSelectorWithServices from './Toolbar/ToolbarLayoutSelector'; import ToolbarSplitButtonWithServices from './Toolbar/ToolbarSplitButtonWithServices'; -import ToolbarButtonWithServices from './Toolbar/ToolbarButtonWithServices'; +import ToolbarButtonGroupWithServices from './Toolbar/ToolbarButtonGroupWithServices'; +import { ToolbarButton } from '@ohif/ui'; +import ProgressDropdownWithService from './components/ProgressDropdownWithService'; + +const getClassName = isToggled => { + return { + className: isToggled + ? '!text-primary-active' + : '!text-common-bright hover:!bg-primary-dark hover:text-primary-light', + }; +}; export default function getToolbarModule({ commandsManager, servicesManager }) { + const { cineService } = servicesManager.services; return [ + { + name: 'ohif.radioGroup', + defaultComponent: ToolbarButton, + }, { name: 'ohif.divider', defaultComponent: ToolbarDivider, - clickHandler: () => {}, - }, - { - name: 'ohif.action', - defaultComponent: ToolbarButtonWithServices, - clickHandler: () => {}, - }, - { - name: 'ohif.radioGroup', - defaultComponent: ToolbarButtonWithServices, - clickHandler: () => {}, }, { name: 'ohif.splitButton', defaultComponent: ToolbarSplitButtonWithServices, - clickHandler: () => {}, }, { name: 'ohif.layoutSelector', - defaultComponent: ToolbarLayoutSelectorWithServices, - clickHandler: (evt, clickedBtn, btnSectionName) => {}, + defaultComponent: props => + ToolbarLayoutSelectorWithServices({ ...props, commandsManager, servicesManager }), }, { - name: 'ohif.toggle', - defaultComponent: ToolbarButtonWithServices, - clickHandler: () => {}, + name: 'ohif.buttonGroup', + defaultComponent: ToolbarButtonGroupWithServices, + }, + { + name: 'ohif.progressDropdown', + defaultComponent: ProgressDropdownWithService, + }, + { + name: 'evaluate.group.promoteToPrimary', + evaluate: ({ viewportId, button, itemId }) => { + const { items } = button.props; + + if (!itemId) { + return { + primary: button.props.primary, + items, + }; + } + + // other wise we can move the clicked tool to the primary button + const clickedItemProps = items.find(item => item.id === itemId || item.itemId === itemId); + + return { + primary: clickedItemProps, + items, + }; + }, + }, + { + name: 'evaluate.cine', + evaluate: () => { + const isToggled = cineService.getState().isCineEnabled; + return getClassName(isToggled); + }, }, ]; } diff --git a/extensions/default/src/getViewportModule.tsx b/extensions/default/src/getViewportModule.tsx new file mode 100644 index 000000000..7ac8adb64 --- /dev/null +++ b/extensions/default/src/getViewportModule.tsx @@ -0,0 +1,21 @@ +import { ServicesManager, CommandsManager, ExtensionManager } from '@ohif/core'; +import LineChartViewport from './Components/LineChartViewport/index'; + +const getViewportModule = ({ + servicesManager, + commandsManager, + extensionManager, +}: { + servicesManager: ServicesManager; + commandsManager: CommandsManager; + extensionManager: ExtensionManager; +}) => { + return [ + { + name: 'chartViewport', + component: LineChartViewport, + }, + ]; +}; + +export { getViewportModule as default }; diff --git a/extensions/default/src/index.ts b/extensions/default/src/index.ts index aa4662443..3d4c9ac48 100644 --- a/extensions/default/src/index.ts +++ b/extensions/default/src/index.ts @@ -9,12 +9,14 @@ import getCommandsModule from './commandsModule'; import getHangingProtocolModule from './getHangingProtocolModule'; import getStudiesForPatientByMRN from './Panels/getStudiesForPatientByMRN'; import getCustomizationModule from './getCustomizationModule'; +import getViewportModule from './getViewportModule'; import { id } from './id.js'; import preRegistration from './init'; import { ContextMenuController, CustomizableContextMenuTypes } from './CustomizableContextMenu'; import * as dicomWebUtils from './DicomWebDataSource/utils'; import { createReportDialogPrompt } from './Panels'; import createReportAsync from './Actions/createReportAsync'; +import StaticWadoClient from './DicomWebDataSource/utils/StaticWadoClient'; const defaultExtension: Types.Extensions.Extension = { /** @@ -23,6 +25,7 @@ const defaultExtension: Types.Extensions.Extension = { id, preRegistration, getDataSourcesModule, + getViewportModule, getLayoutTemplateModule, getPanelModule, getHangingProtocolModule, @@ -52,4 +55,5 @@ export { dicomWebUtils, createReportDialogPrompt, createReportAsync, + StaticWadoClient, }; diff --git a/extensions/default/src/init.ts b/extensions/default/src/init.ts index 973ae0722..4821e15ad 100644 --- a/extensions/default/src/init.ts +++ b/extensions/default/src/init.ts @@ -10,8 +10,13 @@ const metadataProvider = classes.MetadataProvider; * @param {Object} servicesManager * @param {Object} configuration */ -export default function init({ servicesManager, configuration = {} }): void { - const { stateSyncService } = servicesManager.services; +export default function init({ servicesManager, configuration = {}, commandsManager }): void { + const { stateSyncService, toolbarService, cineService, viewportGridService } = + servicesManager.services; + + toolbarService.registerEventForToolbarUpdate(cineService, [ + cineService.EVENTS.CINE_STATE_CHANGED, + ]); // Add DicomMetadataStore.subscribe(DicomMetadataStore.EVENTS.INSTANCES_ADDED, handlePETImageMetadata); @@ -24,6 +29,10 @@ export default function init({ servicesManager, configuration = {} }): void { // Used to recover manual changes to the layout of a stage. stateSyncService.register('viewportGridStore', { clearOnModeExit: true }); + // uiStateStore is a sync state which stores the relevant + // UI state for the viewer + stateSyncService.register('uiStateStore', { clearOnModeExit: true }); + // displaySetSelectorMap stores a map from // `::` to // a displaySetInstanceUID, used to display named display sets in @@ -38,7 +47,7 @@ export default function init({ servicesManager, configuration = {} }): void { }); // Stores a map from the to be applied hanging protocols `:` - // to the previously applied hanging protolStageIndexMap key, in order to toggle + // to the previously applied hanging protocolStageIndexMap key, in order to toggle // off the applied protocol and remember the old state. stateSyncService.register('toggleHangingProtocol', { clearOnModeExit: true }); @@ -46,41 +55,85 @@ export default function init({ servicesManager, configuration = {} }): void { // changes numRows and numCols, the viewports can be remembers and then replaced // afterwards. stateSyncService.register('viewportsByPosition', { clearOnModeExit: true }); + + // Function to process and subscribe to events for a given set of commands and listeners + const subscribeToEvents = listeners => { + Object.entries(listeners).forEach(([event, commands]) => { + const supportedEvents = [ + viewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED, + viewportGridService.EVENTS.VIEWPORTS_READY, + ]; + + if (supportedEvents.includes(event)) { + viewportGridService.subscribe(event, eventData => { + const viewportId = eventData?.viewportId ?? viewportGridService.getActiveViewportId(); + + commandsManager.run(commands, { viewportId }); + }); + } + }); + }; + + toolbarService.subscribe(toolbarService.EVENTS.TOOL_BAR_MODIFIED, state => { + const { buttons } = state; + for (const [id, button] of Object.entries(buttons)) { + const { groupId, items, listeners } = button.props || {}; + + // Handle group items' listeners + if (groupId && items) { + items.forEach(item => { + if (item.listeners) { + subscribeToEvents(item.listeners); + } + }); + } + + // Handle button listeners + if (listeners) { + subscribeToEvents(listeners); + } + } + }); } const handlePETImageMetadata = ({ SeriesInstanceUID, StudyInstanceUID }) => { const { instances } = DicomMetadataStore.getSeries(StudyInstanceUID, SeriesInstanceUID); - const modality = instances[0].Modality; - if (modality !== 'PT') { + if (!instances?.length) { return; } + + const modality = instances[0].Modality; + + if (!modality || modality !== 'PT') { + return; + } + const imageIds = instances.map(instance => instance.imageId); const instanceMetadataArray = []; - imageIds.forEach(imageId => { - const instanceMetadata = getPTImageIdInstanceMetadata(imageId); - if (instanceMetadata) { - instanceMetadataArray.push(instanceMetadata); - } - }); - - if (!instanceMetadataArray.length) { - return; - } // try except block to prevent errors when the metadata is not correct - let suvScalingFactors; try { - suvScalingFactors = calculateSUVScalingFactors(instanceMetadataArray); + imageIds.forEach(imageId => { + const instanceMetadata = getPTImageIdInstanceMetadata(imageId); + if (instanceMetadata) { + instanceMetadataArray.push(instanceMetadata); + } + }); + + if (!instanceMetadataArray.length) { + return; + } + + const suvScalingFactors = calculateSUVScalingFactors(instanceMetadataArray); + instanceMetadataArray.forEach((instanceMetadata, index) => { + metadataProvider.addCustomMetadata( + imageIds[index], + 'scalingModule', + suvScalingFactors[index] + ); + }); } catch (error) { console.log(error); } - - if (!suvScalingFactors) { - return; - } - - instanceMetadataArray.forEach((instanceMetadata, index) => { - metadataProvider.addCustomMetadata(imageIds[index], 'scalingModule', suvScalingFactors[index]); - }); }; diff --git a/extensions/default/src/utils/getDirectURL.js b/extensions/default/src/utils/getDirectURL.js index a43bdeb45..d9ea92499 100644 --- a/extensions/default/src/utils/getDirectURL.js +++ b/extensions/default/src/utils/getDirectURL.js @@ -10,6 +10,7 @@ import { utils } from '@ohif/core'; * @param {string} params.defaultType is the mime type of the response * @param {string} params.singlepart is the type of the part to retrieve * @param {string} params.fetchPart unknown? + * @param {string} params.url unknown? * @returns an absolute URL to the resource, if the absolute URL can be retrieved as singlepart, * or is already retrieved, or a promise to a URL for such use if a BulkDataURI */ @@ -21,7 +22,12 @@ const getDirectURL = (config, params) => { defaultPath = '/pixeldata', defaultType = 'video/mp4', singlepart: fetchPart = 'video', + url = null, } = params; + if (url) { + return url; + } + const value = instance[tag]; if (!value) { return undefined; @@ -37,7 +43,11 @@ const getDirectURL = (config, params) => { } if (!singlepart || (singlepart !== true && singlepart.indexOf(fetchPart) === -1)) { if (value.retrieveBulkData) { - return value.retrieveBulkData().then(arr => { + // Try the specified retrieve type. + const options = { + mediaType: defaultType, + }; + return value.retrieveBulkData(options).then(arr => { value.DirectRetrieveURL = URL.createObjectURL(new Blob([arr], { type: defaultType })); return value.DirectRetrieveURL; }); diff --git a/extensions/default/src/utils/reuseCachedLayouts.ts b/extensions/default/src/utils/reuseCachedLayouts.ts index f6c2ed1f1..d321082a2 100644 --- a/extensions/default/src/utils/reuseCachedLayouts.ts +++ b/extensions/default/src/utils/reuseCachedLayouts.ts @@ -21,6 +21,11 @@ const reuseCachedLayout = ( ): ReturnType => { const { activeViewportId } = state; const { protocol } = hangingProtocolService.getActiveProtocol(); + + if (!protocol) { + return; + } + const hpInfo = hangingProtocolService.getState(); const { protocolId, stageIndex, activeStudyUID } = hpInfo; diff --git a/extensions/dicom-microscopy/CHANGELOG.md b/extensions/dicom-microscopy/CHANGELOG.md index 3694beb96..d4655d780 100644 --- a/extensions/dicom-microscopy/CHANGELOG.md +++ b/extensions/dicom-microscopy/CHANGELOG.md @@ -3,7 +3,766 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -# [3.7.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.7.0) (2023-10-11) +# [3.8.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.92...v3.8.0-beta.93) (2024-04-29) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.91...v3.8.0-beta.92) (2024-04-28) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.90...v3.8.0-beta.91) (2024-04-25) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.89...v3.8.0-beta.90) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.88...v3.8.0-beta.89) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.87...v3.8.0-beta.88) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.86...v3.8.0-beta.87) (2024-04-19) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.85...v3.8.0-beta.86) (2024-04-19) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.84...v3.8.0-beta.85) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.83...v3.8.0-beta.84) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.82...v3.8.0-beta.83) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.81...v3.8.0-beta.82) (2024-04-17) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.80...v3.8.0-beta.81) (2024-04-16) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.79...v3.8.0-beta.80) (2024-04-16) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.78...v3.8.0-beta.79) (2024-04-10) + + +### Features + +* **SM:** remove SM measurements from measurement panel ([#4022](https://github.com/OHIF/Viewers/issues/4022)) ([df49a65](https://github.com/OHIF/Viewers/commit/df49a653be61a93f6e9fb3663aabe9775c31fd13)) + + + + + +# [3.8.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.77...v3.8.0-beta.78) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.76...v3.8.0-beta.77) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.75...v3.8.0-beta.76) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.74...v3.8.0-beta.75) (2024-04-10) + + +### Bug Fixes + +* Microscopy bulkdata and image retrieve ([#3894](https://github.com/OHIF/Viewers/issues/3894)) ([7fac49b](https://github.com/OHIF/Viewers/commit/7fac49b4492b4bd5e9ece8e2e2b0fa2faa840d7f)) + + + + + +# [3.8.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.73...v3.8.0-beta.74) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.72...v3.8.0-beta.73) (2024-04-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.71...v3.8.0-beta.72) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.70...v3.8.0-beta.71) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.69...v3.8.0-beta.70) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.68...v3.8.0-beta.69) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.67...v3.8.0-beta.68) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.66...v3.8.0-beta.67) (2024-04-02) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27) + + +### Features + +* **toolbar:** new Toolbar to enable reactive state synchronization ([#3983](https://github.com/OHIF/Viewers/issues/3983)) ([566b25a](https://github.com/OHIF/Viewers/commit/566b25a54425399096864bd263193646556011a5)) + + + + + +# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + + +### Bug Fixes + +* **SM:** drag and drop is now fixed for SM ([#3813](https://github.com/OHIF/Viewers/issues/3813)) ([f1a6764](https://github.com/OHIF/Viewers/commit/f1a67647aed635437b188cea7cf5d5a8fb974bbe)) + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) **Note:** Version bump only for package @ohif/extension-dicom-microscopy diff --git a/extensions/dicom-microscopy/package.json b/extensions/dicom-microscopy/package.json index 8c334f3a0..faa2d43eb 100644 --- a/extensions/dicom-microscopy/package.json +++ b/extensions/dicom-microscopy/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-dicom-microscopy", - "version": "3.7.0", + "version": "3.8.0-beta.93", "description": "OHIF extension for DICOM microscopy", "author": "Bill Wallace, md-prog", "license": "MIT", @@ -21,6 +21,8 @@ "yarn": ">=1.18.0" }, "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "dev:dicom-pdf": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", @@ -28,10 +30,10 @@ "start": "yarn run dev" }, "peerDependencies": { - "@ohif/core": "3.7.0", - "@ohif/extension-default": "3.7.0", - "@ohif/i18n": "3.7.0", - "@ohif/ui": "3.7.0", + "@ohif/core": "3.8.0-beta.93", + "@ohif/extension-default": "3.8.0-beta.93", + "@ohif/i18n": "3.8.0-beta.93", + "@ohif/ui": "3.8.0-beta.93", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx index 39b487358..fd44e21f7 100644 --- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx +++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx @@ -11,16 +11,6 @@ import dcmjs from 'dcmjs'; import cleanDenaturalizedDataset from './utils/cleanDenaturalizedDataset'; import MicroscopyService from './services/MicroscopyService'; -function transformImageTypeUnnaturalized(entry) { - if (entry.vr === 'CS') { - return { - vr: 'US', - Value: entry.Value[0].split('\\'), - }; - } - return entry; -} - class DicomMicroscopyViewport extends Component { state = { error: null as any, @@ -250,7 +240,6 @@ class DicomMicroscopyViewport extends Component { componentDidMount() { const { displaySets, viewportOptions } = this.props; - const { viewportId } = viewportOptions; // Todo-rename: this is always getting the 0 const displaySet = displaySets[0]; this.installOpenLayersRenderer(this.container.current, displaySet).then(() => { diff --git a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx index 79c0842e5..3da3b5d4b 100644 --- a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx +++ b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx @@ -1,11 +1,10 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { ServicesManager, ExtensionManager, CommandsManager, DicomMetadataStore } from '@ohif/core'; -import { MeasurementTable, Icon, ButtonGroup, Button } from '@ohif/ui'; +import { MeasurementTable } from '@ohif/ui'; import { withTranslation, WithTranslation } from 'react-i18next'; import { EVENTS as MicroscopyEvents } from '../../services/MicroscopyService'; import dcmjs from 'dcmjs'; -import styles from '../../utils/styles'; import callInputDialog from '../../utils/callInputDialog'; import constructSR from '../../utils/constructSR'; import { saveByteArray } from '../../utils/saveByteArray'; @@ -286,6 +285,11 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { props.commandsManager.runCommand('setLabel', { uid }, 'MICROSCOPY'); }; + const onMeasurementDeleteHandler = ({ uid, isActive }: { uid: string; isActive: boolean }) => { + const roiAnnotation = microscopyService.getAnnotation(uid); + microscopyService.removeAnnotation(roiAnnotation); + }; + // Convert ROI annotations managed by microscopyService into our // own format for display const data = roiAnnotations.map((roiAnnotation, index) => { @@ -322,8 +326,6 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { }; }); - const disabled = data.length === 0; - return ( <>
diff --git a/extensions/dicom-microscopy/src/getCommandsModule.ts b/extensions/dicom-microscopy/src/getCommandsModule.ts index 756c4fd5c..02d7ee8b8 100644 --- a/extensions/dicom-microscopy/src/getCommandsModule.ts +++ b/extensions/dicom-microscopy/src/getCommandsModule.ts @@ -124,7 +124,7 @@ export default function getCommandsModule({ } // overview - const { activeViewportId, viewports } = viewportGridService.getState(); + const { activeViewportId } = viewportGridService.getState(); microscopyService.toggleOverviewMap(activeViewportId); }, toggleAnnotations: () => { @@ -135,28 +135,18 @@ export default function getCommandsModule({ const definitions = { deleteMeasurement: { commandFn: actions.deleteMeasurement, - storeContexts: [] as any[], - options: {}, }, setLabel: { commandFn: actions.setLabel, - storeContexts: [] as any[], - options: {}, }, setToolActive: { commandFn: actions.setToolActive, - storeContexts: [] as any[], - options: {}, }, toggleOverlays: { commandFn: actions.toggleOverlays, - storeContexts: [] as any[], - options: {}, }, toggleAnnotations: { commandFn: actions.toggleAnnotations, - storeContexts: [] as any[], - options: {}, }, }; diff --git a/extensions/dicom-microscopy/src/index.tsx b/extensions/dicom-microscopy/src/index.tsx index a4589ed2d..bd26b5c3e 100644 --- a/extensions/dicom-microscopy/src/index.tsx +++ b/extensions/dicom-microscopy/src/index.tsx @@ -1,7 +1,8 @@ import { id } from './id'; -import React, { Suspense } from 'react'; +import React, { Suspense, useMemo } from 'react'; import getPanelModule from './getPanelModule'; import getCommandsModule from './getCommandsModule'; +import { Types } from '@ohif/core'; import { useViewportGrid } from '@ohif/ui'; import getDicomMicroscopySopClassHandler from './DicomMicroscopySopClassHandler'; @@ -23,14 +24,14 @@ const MicroscopyViewport = props => { /** * You can remove any of the following modules if you don't need them. */ -export default { +const extension: Types.Extensions.Extension = { /** * Only required property. Should be a unique value across all extensions. * You ID can be anything you want, but it should be unique. */ id, - async preRegistration({ servicesManager, commandsManager, configuration = {}, appConfig }) { + async preRegistration({ servicesManager }) { servicesManager.registerService(MicroscopyService.REGISTRATION(servicesManager)); }, @@ -58,8 +59,17 @@ export default { const [viewportGrid, viewportGridService] = useViewportGrid(); const { activeViewportId } = viewportGrid; + // a unique identifier based on the contents of displaySets. + // since we changed our rendering pipeline and if there is no + // element size change nor viewportId change we won't re-render + // we need a way to force re-rendering when displaySets change. + const displaySetsKey = useMemo(() => { + return props.displaySets.map(ds => ds.displaySetInstanceUID).join('-'); + }, [props.displaySets]); + return ( { + const { microscopyService } = servicesManager.services; + + const activeInteractions = microscopyService.getActiveInteractions(); + + const isPrimaryActive = activeInteractions.find(interactions => { + const sameMouseButton = interactions[1].bindings.mouseButtons.includes('left'); + + if (!sameMouseButton) { + return false; + } + + const notDraw = interactions[0] !== 'draw'; + + // there seems to be a custom logic for draw tool for some reason + return notDraw + ? interactions[0] === button.id + : interactions[1].geometryType === button.id; + }); + + return { + disabled: false, + className: isPrimaryActive + ? '!text-black bg-primary-light' + : '!text-common-bright hover:!bg-primary-dark hover:!text-primary-light', + // Todo: isActive right now is used for nested buttons where the primary + // button needs to be fully rounded (vs partial rounded) when active + // otherwise it does not have any other use + isActive: isPrimaryActive, + }; + }, + }, + ]; + }, + /** * SopClassHandlerModule should provide a list of sop class handlers that will be * available in OHIF for Modes to consume and use to create displaySets from Series. @@ -104,3 +153,5 @@ export default { getCommandsModule, }; + +export default extension; diff --git a/extensions/dicom-microscopy/src/services/MicroscopyService.ts b/extensions/dicom-microscopy/src/services/MicroscopyService.ts index 78ba545c2..0f6005f5d 100644 --- a/extensions/dicom-microscopy/src/services/MicroscopyService.ts +++ b/extensions/dicom-microscopy/src/services/MicroscopyService.ts @@ -573,6 +573,10 @@ export default class MicroscopyService extends PubSubService { this.activeInteractions = interactions; } + getActiveInteractions() { + return this.activeInteractions; + } + /** * Triggers the relabelling process for the given RoiAnnotation instance, by * publishing the RELABEL event to notify the subscribers diff --git a/extensions/dicom-microscopy/src/utils/dicomWebClient.ts b/extensions/dicom-microscopy/src/utils/dicomWebClient.ts index 8d6d78462..dc906b120 100644 --- a/extensions/dicom-microscopy/src/utils/dicomWebClient.ts +++ b/extensions/dicom-microscopy/src/utils/dicomWebClient.ts @@ -1,11 +1,5 @@ -import { api } from 'dicomweb-client'; import { errorHandler, DicomMetadataStore } from '@ohif/core'; - -const { DICOMwebClient } = api; - -DICOMwebClient._buildMultipartAcceptHeaderFieldValue = () => { - return '*/*'; -}; +import { StaticWadoClient } from '@ohif/extension-default'; /** * create a DICOMwebClient object to be used by Dicom Microscopy Viewer @@ -31,7 +25,7 @@ export default function getDicomWebClient({ extensionManager, servicesManager }) errorInterceptor: errorHandler.getHTTPErrorHandler(), }; - const client = new api.DICOMwebClient(wadoConfig); + const client = new StaticWadoClient(wadoConfig); client.wadoURL = wadoConfig.url; if (extensionManager.activeDataSource === 'dicomlocal') { diff --git a/extensions/dicom-pdf/CHANGELOG.md b/extensions/dicom-pdf/CHANGELOG.md index fcdfb1662..c013466a7 100644 --- a/extensions/dicom-pdf/CHANGELOG.md +++ b/extensions/dicom-pdf/CHANGELOG.md @@ -3,7 +3,760 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -# [3.7.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.7.0) (2023-10-11) +# [3.8.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.92...v3.8.0-beta.93) (2024-04-29) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.91...v3.8.0-beta.92) (2024-04-28) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.90...v3.8.0-beta.91) (2024-04-25) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.89...v3.8.0-beta.90) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.88...v3.8.0-beta.89) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.87...v3.8.0-beta.88) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.86...v3.8.0-beta.87) (2024-04-19) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.85...v3.8.0-beta.86) (2024-04-19) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.84...v3.8.0-beta.85) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.83...v3.8.0-beta.84) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.82...v3.8.0-beta.83) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.81...v3.8.0-beta.82) (2024-04-17) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.80...v3.8.0-beta.81) (2024-04-16) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.79...v3.8.0-beta.80) (2024-04-16) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.78...v3.8.0-beta.79) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.77...v3.8.0-beta.78) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.76...v3.8.0-beta.77) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.75...v3.8.0-beta.76) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.74...v3.8.0-beta.75) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.73...v3.8.0-beta.74) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.72...v3.8.0-beta.73) (2024-04-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.71...v3.8.0-beta.72) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.70...v3.8.0-beta.71) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.69...v3.8.0-beta.70) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.68...v3.8.0-beta.69) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.67...v3.8.0-beta.68) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.66...v3.8.0-beta.67) (2024-04-02) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27) + + +### Features + +* **toolbar:** new Toolbar to enable reactive state synchronization ([#3983](https://github.com/OHIF/Viewers/issues/3983)) ([566b25a](https://github.com/OHIF/Viewers/commit/566b25a54425399096864bd263193646556011a5)) + + + + + +# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + + +### Bug Fixes + +* **sr:** dcm4chee requires the patient name for an SR to match what is in the original study ([#3739](https://github.com/OHIF/Viewers/issues/3739)) ([d98439f](https://github.com/OHIF/Viewers/commit/d98439fe7f3825076dbc87b664a1d1480ff414d3)) + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) **Note:** Version bump only for package @ohif/extension-dicom-pdf diff --git a/extensions/dicom-pdf/package.json b/extensions/dicom-pdf/package.json index d7023df47..44c26b0bc 100644 --- a/extensions/dicom-pdf/package.json +++ b/extensions/dicom-pdf/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-dicom-pdf", - "version": "3.7.0", + "version": "3.8.0-beta.93", "description": "OHIF extension for PDF display", "author": "OHIF", "license": "MIT", @@ -20,6 +20,8 @@ "access": "public" }, "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", "build:package-1": "yarn run build", @@ -28,9 +30,9 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0", - "@ohif/ui": "3.7.0", - "dcmjs": "^0.29.5", + "@ohif/core": "3.8.0-beta.93", + "@ohif/ui": "3.8.0-beta.93", + "dcmjs": "^0.29.12", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/dicom-pdf/src/index.tsx b/extensions/dicom-pdf/src/index.tsx index ab3d33cf4..a655cbc50 100644 --- a/extensions/dicom-pdf/src/index.tsx +++ b/extensions/dicom-pdf/src/index.tsx @@ -41,18 +41,6 @@ const dicomPDFExtension = { return [{ name: 'dicom-pdf', component: ExtendedOHIFCornerstonePdfViewport }]; }, - // getCommandsModule({ servicesManager }) { - // return { - // definitions: { - // setToolActive: { - // commandFn: () => null, - // storeContexts: [], - // options: {}, - // }, - // }, - // defaultContext: 'ACTIVE_VIEWPORT::PDF', - // }; - // }, getSopClassHandlerModule, }; diff --git a/extensions/dicom-video/CHANGELOG.md b/extensions/dicom-video/CHANGELOG.md index d04edfa5d..d0455c837 100644 --- a/extensions/dicom-video/CHANGELOG.md +++ b/extensions/dicom-video/CHANGELOG.md @@ -3,7 +3,763 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -# [3.7.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.7.0) (2023-10-11) +# [3.8.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.92...v3.8.0-beta.93) (2024-04-29) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.91...v3.8.0-beta.92) (2024-04-28) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.90...v3.8.0-beta.91) (2024-04-25) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.89...v3.8.0-beta.90) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.88...v3.8.0-beta.89) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.87...v3.8.0-beta.88) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.86...v3.8.0-beta.87) (2024-04-19) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.85...v3.8.0-beta.86) (2024-04-19) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.84...v3.8.0-beta.85) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.83...v3.8.0-beta.84) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.82...v3.8.0-beta.83) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.81...v3.8.0-beta.82) (2024-04-17) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.80...v3.8.0-beta.81) (2024-04-16) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.79...v3.8.0-beta.80) (2024-04-16) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.78...v3.8.0-beta.79) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.77...v3.8.0-beta.78) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.76...v3.8.0-beta.77) (2024-04-10) + + +### Bug Fixes + +* **dicom-video:** Update get direct func for dicom json to use url if present and fix config argument ([#4017](https://github.com/OHIF/Viewers/issues/4017)) ([4f99244](https://github.com/OHIF/Viewers/commit/4f99244d864427d69be6f863cb7a6a78411adb12)) + + + + + +# [3.8.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.75...v3.8.0-beta.76) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.74...v3.8.0-beta.75) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.73...v3.8.0-beta.74) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.72...v3.8.0-beta.73) (2024-04-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.71...v3.8.0-beta.72) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.70...v3.8.0-beta.71) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.69...v3.8.0-beta.70) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.68...v3.8.0-beta.69) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.67...v3.8.0-beta.68) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.66...v3.8.0-beta.67) (2024-04-02) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27) + + +### Features + +* **toolbar:** new Toolbar to enable reactive state synchronization ([#3983](https://github.com/OHIF/Viewers/issues/3983)) ([566b25a](https://github.com/OHIF/Viewers/commit/566b25a54425399096864bd263193646556011a5)) + + + + + +# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + + +### Bug Fixes + +* **sr:** dcm4chee requires the patient name for an SR to match what is in the original study ([#3739](https://github.com/OHIF/Viewers/issues/3739)) ([d98439f](https://github.com/OHIF/Viewers/commit/d98439fe7f3825076dbc87b664a1d1480ff414d3)) + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) **Note:** Version bump only for package @ohif/extension-dicom-video diff --git a/extensions/dicom-video/package.json b/extensions/dicom-video/package.json index d6b0ea304..24fc9e1be 100644 --- a/extensions/dicom-video/package.json +++ b/extensions/dicom-video/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-dicom-video", - "version": "3.7.0", + "version": "3.8.0-beta.93", "description": "OHIF extension for video display", "author": "OHIF", "license": "MIT", @@ -20,6 +20,8 @@ "access": "public" }, "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", "build:package-1": "yarn run build", @@ -28,9 +30,9 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0", - "@ohif/ui": "3.7.0", - "dcmjs": "^0.29.5", + "@ohif/core": "3.8.0-beta.93", + "@ohif/ui": "3.8.0-beta.93", + "dcmjs": "^0.29.12", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/dicom-video/src/getSopClassHandlerModule.js b/extensions/dicom-video/src/getSopClassHandlerModule.js index 50181590f..5b0a2162e 100644 --- a/extensions/dicom-video/src/getSopClassHandlerModule.js +++ b/extensions/dicom-video/src/getSopClassHandlerModule.js @@ -51,7 +51,7 @@ const _getDisplaySetsFromSeries = (instances, servicesManager, extensionManager) }) .map(instance => { const { Modality, SOPInstanceUID, SeriesDescription = 'VIDEO' } = instance; - const { SeriesNumber, SeriesDate, SeriesInstanceUID, StudyInstanceUID, NumberOfFrames } = + const { SeriesNumber, SeriesDate, SeriesInstanceUID, StudyInstanceUID, NumberOfFrames, url } = instance; const displaySet = { //plugin: id, @@ -70,6 +70,7 @@ const _getDisplaySetsFromSeries = (instances, servicesManager, extensionManager) instance, singlepart: 'video', tag: 'PixelData', + url, }), instances: [instance], thumbnailSrc: dataSource.retrieve.directURL({ diff --git a/extensions/dicom-video/src/index.tsx b/extensions/dicom-video/src/index.tsx index a5fcd40d1..0fde8ca82 100644 --- a/extensions/dicom-video/src/index.tsx +++ b/extensions/dicom-video/src/index.tsx @@ -42,19 +42,6 @@ const dicomVideoExtension = { return [{ name: 'dicom-video', component: ExtendedOHIFCornerstoneVideoViewport }]; }, - // getCommandsModule({ servicesManager }) { - // return { - // definitions: { - // setToolActive: { - // commandFn: ({ toolName, element }) => { - // }, - // storeContexts: [], - // options: {}, - // }, - // }, - // defaultContext: 'ACTIVE_VIEWPORT::VIDEO', - // }; - // }, getSopClassHandlerModule, }; diff --git a/extensions/measurement-tracking/CHANGELOG.md b/extensions/measurement-tracking/CHANGELOG.md index 531283d00..b9551f77f 100644 --- a/extensions/measurement-tracking/CHANGELOG.md +++ b/extensions/measurement-tracking/CHANGELOG.md @@ -3,7 +3,835 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -# [3.7.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.7.0) (2023-10-11) +# [3.8.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.92...v3.8.0-beta.93) (2024-04-29) + + +### Bug Fixes + +* **toolbox:** Preserve user-specified tool state and streamline command execution ([#4063](https://github.com/OHIF/Viewers/issues/4063)) ([f1a736d](https://github.com/OHIF/Viewers/commit/f1a736d1934733a434cb87b2c284907a3122403f)) + + + + + +# [3.8.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.91...v3.8.0-beta.92) (2024-04-28) + + +### Bug Fixes + +* **bugs:** fix patient header for doc, track ball rotate resize observer and add segmentation button not being enabled on viewport data change ([#4068](https://github.com/OHIF/Viewers/issues/4068)) ([c09311d](https://github.com/OHIF/Viewers/commit/c09311d3b7df05fcd00a9f36a7233e9d7e5589d0)) + + + + + +# [3.8.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.90...v3.8.0-beta.91) (2024-04-25) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.89...v3.8.0-beta.90) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.88...v3.8.0-beta.89) (2024-04-22) + + +### Bug Fixes + +* **viewport-webworker-segmentation:** Resolve issues with viewport detection, webworker termination, and segmentation panel layout change ([#4059](https://github.com/OHIF/Viewers/issues/4059)) ([52a0c59](https://github.com/OHIF/Viewers/commit/52a0c59294a4161fcca0a6708855549034849951)) + + + + + +# [3.8.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.87...v3.8.0-beta.88) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.86...v3.8.0-beta.87) (2024-04-19) + + +### Features + +* **tmtv-mode:** Add Brush tools and move SUV peak calculation to web worker ([#4053](https://github.com/OHIF/Viewers/issues/4053)) ([8192e34](https://github.com/OHIF/Viewers/commit/8192e348eca993fec331d4963efe88f9a730eceb)) + + + + + +# [3.8.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.85...v3.8.0-beta.86) (2024-04-19) + + +### Bug Fixes + +* **layouts:** and fix thumbnail in touch and update migration guide for 3.8 release ([#4052](https://github.com/OHIF/Viewers/issues/4052)) ([d250d04](https://github.com/OHIF/Viewers/commit/d250d04580883446fcb8d748b2a97c5c198922af)) + + + + + +# [3.8.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.84...v3.8.0-beta.85) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.83...v3.8.0-beta.84) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.82...v3.8.0-beta.83) (2024-04-18) + + +### Bug Fixes + +* **bugs:** enhancements and bug fixes - final ([#4048](https://github.com/OHIF/Viewers/issues/4048)) ([170bb96](https://github.com/OHIF/Viewers/commit/170bb96983082c39b22b7352e0c54aacf3e73b02)) + + + + + +# [3.8.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.81...v3.8.0-beta.82) (2024-04-17) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.80...v3.8.0-beta.81) (2024-04-16) + + +### Bug Fixes + +* **viewport:** Reset viewport state and fix CINE looping, thumbnail resolution, and dynamic tool settings ([#4037](https://github.com/OHIF/Viewers/issues/4037)) ([f99a0bf](https://github.com/OHIF/Viewers/commit/f99a0bfb31434aa137bbb3ed1f9eef1dfcc09025)) + + + + + +# [3.8.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.79...v3.8.0-beta.80) (2024-04-16) + + +### Bug Fixes + +* **bugs:** enhancements and bug fixes ([#4036](https://github.com/OHIF/Viewers/issues/4036)) ([e80fc6f](https://github.com/OHIF/Viewers/commit/e80fc6f47708e1d6b1a1e1de438196a4b74ec637)) + + + + + +# [3.8.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.78...v3.8.0-beta.79) (2024-04-10) + + +### Features + +* **SM:** remove SM measurements from measurement panel ([#4022](https://github.com/OHIF/Viewers/issues/4022)) ([df49a65](https://github.com/OHIF/Viewers/commit/df49a653be61a93f6e9fb3663aabe9775c31fd13)) + + + + + +# [3.8.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.77...v3.8.0-beta.78) (2024-04-10) + + +### Bug Fixes + +* **general:** enhancements and bug fixes ([#4018](https://github.com/OHIF/Viewers/issues/4018)) ([2b83393](https://github.com/OHIF/Viewers/commit/2b83393f91cb16ea06821d79d14ff60f80c29c90)) + + + + + +# [3.8.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.76...v3.8.0-beta.77) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.75...v3.8.0-beta.76) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.74...v3.8.0-beta.75) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.73...v3.8.0-beta.74) (2024-04-10) + + +### Features + +* **4D:** Add 4D dynamic volume rendering and new pre-clinical 4d pt/ct mode ([#3664](https://github.com/OHIF/Viewers/issues/3664)) ([d57e8bc](https://github.com/OHIF/Viewers/commit/d57e8bc1571c6da4effaa492ee2d162c552365a2)) + + + + + +# [3.8.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.72...v3.8.0-beta.73) (2024-04-08) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.71...v3.8.0-beta.72) (2024-04-05) + + +### Bug Fixes + +* **cornerstone-dicom-sr:** Freehand SR hydration support ([#3996](https://github.com/OHIF/Viewers/issues/3996)) ([5645ac1](https://github.com/OHIF/Viewers/commit/5645ac1b271e1ed8c57f5d71100809362447267e)) + + + + + +# [3.8.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.70...v3.8.0-beta.71) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.69...v3.8.0-beta.70) (2024-04-05) + + +### Features + +* **measurement:** Add support measurement label autocompletion ([#3855](https://github.com/OHIF/Viewers/issues/3855)) ([56b1eae](https://github.com/OHIF/Viewers/commit/56b1eae6356a6534960df1196bdd1e95b0a9a470)) + + + + + +# [3.8.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.68...v3.8.0-beta.69) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.67...v3.8.0-beta.68) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.66...v3.8.0-beta.67) (2024-04-02) + + +### Features + +* **ViewportActionMenu:** window level per viewport / new patient info / colorbars/ 3D presets and 3D volume rendering ([#3963](https://github.com/OHIF/Viewers/issues/3963)) ([b7f90e3](https://github.com/OHIF/Viewers/commit/b7f90e3951845396f99b69f0a74fc56b2ffeada1)) + + + + + +# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28) + + +### Bug Fixes + +* **new layout:** address black screen bugs ([#4008](https://github.com/OHIF/Viewers/issues/4008)) ([158a181](https://github.com/OHIF/Viewers/commit/158a1816703e0ad66cae08cb9bd1ffb93bbd8d43)) + + + + + +# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27) + + +### Features + +* **toolbar:** new Toolbar to enable reactive state synchronization ([#3983](https://github.com/OHIF/Viewers/issues/3983)) ([566b25a](https://github.com/OHIF/Viewers/commit/566b25a54425399096864bd263193646556011a5)) + + + + + +# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18) + + +### Bug Fixes + +* **SR display:** and the token based navigation ([#3995](https://github.com/OHIF/Viewers/issues/3995)) ([feed230](https://github.com/OHIF/Viewers/commit/feed2304c124dc2facc7a7371ed9851548c223c5)) + + + + + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + + +### Bug Fixes + +* **demo:** Deploy issue ([#3951](https://github.com/OHIF/Viewers/issues/3951)) ([21e8a2b](https://github.com/OHIF/Viewers/commit/21e8a2bd0b7cc72f90a31e472d285d761be15d30)) + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + + +### Bug Fixes + +* Update CS3D to fix second render ([#3892](https://github.com/OHIF/Viewers/issues/3892)) ([d00a86b](https://github.com/OHIF/Viewers/commit/d00a86b022742ea089d246d06cfd691f43b64412)) + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + + +### Features + +* **hp:** enable OHIF to run with partial metadata for large studies at the cost of less effective hanging protocol ([#3804](https://github.com/OHIF/Viewers/issues/3804)) ([0049f4c](https://github.com/OHIF/Viewers/commit/0049f4c0303f0b6ea995972326fc8784259f5a47)) + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + + +### Bug Fixes + +* **segmentation:** upgrade cs3d to fix various segmentation bugs ([#3885](https://github.com/OHIF/Viewers/issues/3885)) ([b1efe40](https://github.com/OHIF/Viewers/commit/b1efe40aa146e4052cc47b3f774cabbb47a8d1a6)) + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + + +### Features + +* improve disableEditing flag ([#3875](https://github.com/OHIF/Viewers/issues/3875)) ([2049c09](https://github.com/OHIF/Viewers/commit/2049c0936c86f819604c243d3dc7b3fe971b5b2c)) + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + + +### Features + +* **i18n:** enhanced i18n support ([#3761](https://github.com/OHIF/Viewers/issues/3761)) ([d14a8f0](https://github.com/OHIF/Viewers/commit/d14a8f0199db95cd9e85866a011b64d6bf830d57)) + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + + +### Bug Fixes + +* **auth:** fix the issue with oauth at a non root path ([#3840](https://github.com/OHIF/Viewers/issues/3840)) ([6651008](https://github.com/OHIF/Viewers/commit/6651008fbb35dabd5991c7f61128e6ef324012df)) + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + + +### Bug Fixes + +* Update the CS3D packages to add the most recent HTJ2K TSUIDS ([#3806](https://github.com/OHIF/Viewers/issues/3806)) ([9d1884d](https://github.com/OHIF/Viewers/commit/9d1884d7d8b6b2a1cdc26965a96995838aa72682)) + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + + +### Bug Fixes + +* **thumbnail:** Avoid multiple promise creations for thumbnails ([#3756](https://github.com/OHIF/Viewers/issues/3756)) ([b23eeff](https://github.com/OHIF/Viewers/commit/b23eeff93745769e67e60c33d75293d6242c5ec9)) + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + + +### Bug Fixes + +* **sr:** dcm4chee requires the patient name for an SR to match what is in the original study ([#3739](https://github.com/OHIF/Viewers/issues/3739)) ([d98439f](https://github.com/OHIF/Viewers/commit/d98439fe7f3825076dbc87b664a1d1480ff414d3)) + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-measurement-tracking + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) **Note:** Version bump only for package @ohif/extension-measurement-tracking diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json index ce2d69c38..7a85e4366 100644 --- a/extensions/measurement-tracking/package.json +++ b/extensions/measurement-tracking/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-measurement-tracking", - "version": "3.7.0", + "version": "3.8.0-beta.93", "description": "Tracking features and functionality for basic image viewing", "author": "OHIF Core Team", "license": "MIT", @@ -23,6 +23,8 @@ "ohif-extension" ], "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "dev:dicom-pdf": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", @@ -30,13 +32,13 @@ "start": "yarn run dev" }, "peerDependencies": { - "@cornerstonejs/core": "^1.20.3", - "@cornerstonejs/tools": "^1.20.3", - "@ohif/core": "3.7.0", - "@ohif/extension-cornerstone-dicom-sr": "3.7.0", - "@ohif/ui": "3.7.0", + "@cornerstonejs/core": "^1.70.14", + "@cornerstonejs/tools": "^1.70.14", + "@ohif/core": "3.8.0-beta.93", + "@ohif/extension-cornerstone-dicom-sr": "3.8.0-beta.93", + "@ohif/ui": "3.8.0-beta.93", "classnames": "^2.3.2", - "dcmjs": "^0.29.5", + "dcmjs": "^0.29.12", "lodash.debounce": "^4.17.21", "prop-types": "^15.6.2", "react": "^17.0.2", @@ -46,7 +48,7 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@ohif/ui": "3.7.0", + "@ohif/ui": "3.8.0-beta.93", "@xstate/react": "^3.2.2", "xstate": "^4.10.0" } diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx index bc83efa7d..74657e85d 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { Machine } from 'xstate'; import { useMachine } from '@xstate/react'; import { useViewportGrid } from '@ohif/ui'; -import { machineConfiguration, defaultOptions } from './measurementTrackingMachine'; +import { machineConfiguration, defaultOptions, RESPONSE } from './measurementTrackingMachine'; import promptBeginTracking from './promptBeginTracking'; import promptTrackNewSeries from './promptTrackNewSeries'; import promptTrackNewStudy from './promptTrackNewStudy'; @@ -11,6 +11,7 @@ import promptSaveReport from './promptSaveReport'; import promptHydrateStructuredReport from './promptHydrateStructuredReport'; import hydrateStructuredReport from './hydrateStructuredReport'; import { useAppConfig } from '@state'; +import promptLabelAnnotation from './promptLabelAnnotation'; const TrackedMeasurementsContext = React.createContext(); TrackedMeasurementsContext.displayName = 'TrackedMeasurementsContext'; @@ -30,7 +31,7 @@ function TrackedMeasurementsContextProvider( const [viewportGrid, viewportGridService] = useViewportGrid(); const { activeViewportId, viewports } = viewportGrid; - const { measurementService, displaySetService } = servicesManager.services; + const { measurementService, displaySetService, customizationService } = servicesManager.services; const machineOptions = Object.assign({}, defaultOptions); machineOptions.actions = Object.assign({}, machineOptions.actions, { @@ -142,6 +143,20 @@ function TrackedMeasurementsContextProvider( extensionManager, appConfig, }), + promptLabelAnnotation: promptLabelAnnotation.bind(null, { + servicesManager, + extensionManager, + }), + }); + machineOptions.guards = Object.assign({}, machineOptions.guards, { + isLabelOnMeasure: (ctx, evt, condMeta) => { + const labelConfig = customizationService.get('measurementLabels'); + return labelConfig?.labelOnMeasure; + }, + isLabelOnMeasureAndShouldKillMachine: (ctx, evt, condMeta) => { + const labelConfig = customizationService.get('measurementLabels'); + return evt.data && evt.data.userResponse === RESPONSE.NO_NEVER && labelConfig?.labelOnMeasure; + }, }); // TODO: IMPROVE @@ -168,60 +183,69 @@ function TrackedMeasurementsContextProvider( // ~~ Listen for changes to ViewportGrid for potential SRs hung in panes when idle useEffect(() => { - if (viewports.size > 0) { - const activeViewport = viewports.get(activeViewportId); + const triggerPromptHydrateFlow = async () => { + if (viewports.size > 0) { + const activeViewport = viewports.get(activeViewportId); - if (!activeViewport || !activeViewport?.displaySetInstanceUIDs?.length) { - return; + if (!activeViewport || !activeViewport?.displaySetInstanceUIDs?.length) { + return; + } + + // Todo: Getting the first displaySetInstanceUID is wrong, but we don't have + // tracking fusion viewports yet. This should change when we do. + const { displaySetService } = servicesManager.services; + const displaySet = displaySetService.getDisplaySetByUID( + activeViewport.displaySetInstanceUIDs[0] + ); + + if (!displaySet) { + return; + } + + // If this is an SR produced by our SR SOPClassHandler, + // and it hasn't been loaded yet, do that now so we + // can check if it can be rehydrated or not. + // + // Note: This happens: + // - If the viewport is not currently an OHIFCornerstoneSRViewport + // - If the displaySet has never been hung + // + // Otherwise, the displaySet will be loaded by the useEffect handler + // listening to displaySet changes inside OHIFCornerstoneSRViewport. + // The issue here is that this handler in TrackedMeasurementsContext + // ends up occurring before the Viewport is created, so the displaySet + // is not loaded yet, and isRehydratable is undefined unless we call load(). + if ( + displaySet.SOPClassHandlerId === SR_SOPCLASSHANDLERID && + !displaySet.isLoaded && + displaySet.load + ) { + await displaySet.load(); + } + + // Magic string + // load function added by our sopClassHandler module + if ( + displaySet.SOPClassHandlerId === SR_SOPCLASSHANDLERID && + displaySet.isRehydratable === true + ) { + console.log('sending event...', trackedMeasurements); + sendTrackedMeasurementsEvent('PROMPT_HYDRATE_SR', { + displaySetInstanceUID: displaySet.displaySetInstanceUID, + SeriesInstanceUID: displaySet.SeriesInstanceUID, + viewportId: activeViewportId, + }); + } } - - // Todo: Getting the first displaySetInstanceUID is wrong, but we don't have - // tracking fusion viewports yet. This should change when we do. - const { displaySetService } = servicesManager.services; - const displaySet = displaySetService.getDisplaySetByUID( - activeViewport.displaySetInstanceUIDs[0] - ); - - if (!displaySet) { - return; - } - - // If this is an SR produced by our SR SOPClassHandler, - // and it hasn't been loaded yet, do that now so we - // can check if it can be rehydrated or not. - // - // Note: This happens: - // - If the viewport is not currently an OHIFCornerstoneSRViewport - // - If the displaySet has never been hung - // - // Otherwise, the displaySet will be loaded by the useEffect handler - // listening to displaySet changes inside OHIFCornerstoneSRViewport. - // The issue here is that this handler in TrackedMeasurementsContext - // ends up occurring before the Viewport is created, so the displaySet - // is not loaded yet, and isRehydratable is undefined unless we call load(). - if ( - displaySet.SOPClassHandlerId === SR_SOPCLASSHANDLERID && - !displaySet.isLoaded && - displaySet.load - ) { - displaySet.load(); - } - - // Magic string - // load function added by our sopClassHandler module - if ( - displaySet.SOPClassHandlerId === SR_SOPCLASSHANDLERID && - displaySet.isRehydratable === true - ) { - console.log('sending event...', trackedMeasurements); - sendTrackedMeasurementsEvent('PROMPT_HYDRATE_SR', { - displaySetInstanceUID: displaySet.displaySetInstanceUID, - SeriesInstanceUID: displaySet.SeriesInstanceUID, - viewportId: activeViewportId, - }); - } - } - }, [activeViewportId, sendTrackedMeasurementsEvent, servicesManager.services, viewports]); + }; + triggerPromptHydrateFlow(); + }, [ + trackedMeasurements, + activeViewportId, + sendTrackedMeasurementsEvent, + servicesManager.services, + viewports, + ]); return ( { const hydrationResult = baseHydrateStructuredReport( - { servicesManager, extensionManager }, + { servicesManager, extensionManager, appConfig }, displaySetInstanceUID ); diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js index 2b0b65ca1..fce9930d3 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js @@ -31,10 +31,33 @@ const machineConfiguration = { off: { type: 'final', }, + labellingOnly: { + on: { + TRACK_SERIES: [ + { + target: 'promptLabelAnnotation', + actions: ['setPreviousState'], + }, + { + target: 'off', + }, + ], + }, + }, idle: { entry: 'clearContext', on: { - TRACK_SERIES: 'promptBeginTracking', + TRACK_SERIES: [ + { + target: 'promptLabelAnnotation', + cond: 'isLabelOnMeasure', + actions: ['setPreviousState'], + }, + { + target: 'promptBeginTracking', + actions: ['setPreviousState'], + }, + ], // Unused? We may only do PROMPT_HYDRATE_SR now? SET_TRACKED_SERIES: [ { @@ -64,6 +87,10 @@ const machineConfiguration = { actions: ['setTrackedStudyAndSeries', 'setIsDirty'], cond: 'shouldSetStudyAndSeries', }, + { + target: 'labellingOnly', + cond: 'isLabelOnMeasureAndShouldKillMachine', + }, { target: 'off', cond: 'shouldKillMachine', @@ -80,6 +107,11 @@ const machineConfiguration = { tracking: { on: { TRACK_SERIES: [ + { + target: 'promptLabelAnnotation', + cond: 'isLabelOnMeasure', + actions: ['setPreviousState'], + }, { target: 'promptTrackNewStudy', cond: 'isNewStudy', @@ -252,6 +284,36 @@ const machineConfiguration = { }, }, }, + promptLabelAnnotation: { + invoke: { + src: 'promptLabelAnnotation', + onDone: [ + { + target: 'labellingOnly', + cond: 'wasLabellingOnly', + }, + { + target: 'promptBeginTracking', + cond: 'wasIdle', + }, + { + target: 'promptTrackNewStudy', + cond: 'wasTrackingAndIsNewStudy', + }, + { + target: 'promptTrackNewSeries', + cond: 'wasTrackingAndIsNewSeries', + }, + { + target: 'tracking', + cond: 'wasTracking', + }, + { + target: 'off', + }, + ], + }, + }, }, strict: true, }; @@ -337,6 +399,11 @@ const defaultOptions = { prevTrackedSeries: ctx.trackedSeries.slice().filter(ser => ser !== evt.SeriesInstanceUID), trackedSeries: ctx.trackedSeries.slice().filter(ser => ser !== evt.SeriesInstanceUID), })), + setPreviousState: assign((ctx, evt, meta) => { + return { + prevState: meta.state.value, + }; + }), }, guards: { // We set dirty any time we performan an action that: @@ -362,6 +429,30 @@ const defaultOptions = { evt.SeriesInstanceUID === undefined || ctx.trackedSeries.includes(evt.SeriesInstanceUID) ); }, + wasLabellingOnly: (ctx, evt, condMeta) => { + return ctx.prevState === 'labellingOnly'; + }, + wasIdle: (ctx, evt, condMeta) => { + return ctx.prevState === 'idle'; + }, + wasTracking: (ctx, evt, condMeta) => { + return ctx.prevState === 'tracking'; + }, + wasTrackingAndIsNewStudy: (ctx, evt, condMeta) => { + return ( + ctx.prevState === 'tracking' && + !ctx.ignoredSeries.includes(evt.data.SeriesInstanceUID) && + ctx.trackedStudy !== evt.data.StudyInstanceUID + ); + }, + wasTrackingAndIsNewSeries: (ctx, evt, condMeta) => { + return ( + ctx.prevState === 'tracking' && + !ctx.ignoredSeries.includes(evt.data.SeriesInstanceUID) && + !ctx.trackedSeries.includes(evt.data.SeriesInstanceUID) + ); + }, + shouldKillMachine: (ctx, evt) => evt.data && evt.data.userResponse === RESPONSE.NO_NEVER, shouldAddSeries: (ctx, evt) => evt.data && evt.data.userResponse === RESPONSE.ADD_SERIES, shouldSetStudyAndSeries: (ctx, evt) => @@ -397,4 +488,4 @@ const defaultOptions = { }, }; -export { defaultOptions, machineConfiguration }; +export { defaultOptions, machineConfiguration, RESPONSE }; diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js index 751823836..d8bb02b99 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js @@ -1,4 +1,5 @@ import { ButtonEnums } from '@ohif/ui'; +import i18n from 'i18next'; const RESPONSE = { NO_NEVER: -1, @@ -10,7 +11,9 @@ const RESPONSE = { function promptBeginTracking({ servicesManager, extensionManager }, ctx, evt) { const { uiViewportDialogService } = servicesManager.services; - const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt; + // When the state change happens after a promise, the state machine sends the retult in evt.data; + // In case of direct transition to the state, the state machine sends the data in evt; + const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt.data || evt; return new Promise(async function (resolve, reject) { let promptResult = await _askTrackMeasurements(uiViewportDialogService, viewportId); @@ -26,24 +29,24 @@ function promptBeginTracking({ servicesManager, extensionManager }, ctx, evt) { function _askTrackMeasurements(uiViewportDialogService, viewportId) { return new Promise(function (resolve, reject) { - const message = 'Track measurements for this series?'; + const message = i18n.t('MeasurementTable:Track measurements for this series?'); const actions = [ { id: 'prompt-begin-tracking-cancel', type: ButtonEnums.type.secondary, - text: 'No', + text: i18n.t('Common:No'), value: RESPONSE.CANCEL, }, { id: 'prompt-begin-tracking-no-do-not-ask-again', type: ButtonEnums.type.secondary, - text: 'No, do not ask again', + text: i18n.t('MeasurementTable:No, do not ask again'), value: RESPONSE.NO_NEVER, }, { id: 'prompt-begin-tracking-yes', type: ButtonEnums.type.primary, - text: 'Yes', + text: i18n.t('Common:Yes'), value: RESPONSE.SET_STUDY_AND_SERIES, }, ]; @@ -63,6 +66,12 @@ function _askTrackMeasurements(uiViewportDialogService, viewportId) { uiViewportDialogService.hide(); resolve(RESPONSE.CANCEL); }, + onKeyPress: event => { + if (event.key === 'Enter') { + const action = actions.find(action => action.id === 'prompt-begin-tracking-yes'); + onSubmit(action.value); + } + }, }); }); } diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js index 1881337ce..579ff9c7b 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js @@ -74,6 +74,12 @@ function _askTrackMeasurements(uiViewportDialogService, viewportId) { uiViewportDialogService.hide(); resolve(RESPONSE.CANCEL); }, + onKeyPress: event => { + if (event.key === 'Enter') { + const action = actions.find(action => action.value === RESPONSE.HYDRATE_REPORT); + onSubmit(action.value); + } + }, }); }); } diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptLabelAnnotation.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptLabelAnnotation.js new file mode 100644 index 000000000..b244ccead --- /dev/null +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptLabelAnnotation.js @@ -0,0 +1,33 @@ +function promptLabelAnnotation({ servicesManager, extensionManager }, ctx, evt) { + const { measurementService, customizationService } = servicesManager.services; + const { viewportId, StudyInstanceUID, SeriesInstanceUID, measurementId } = evt; + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.common' + ); + const { showLabelAnnotationPopup } = utilityModule.exports; + return new Promise(async function (resolve) { + const labelConfig = customizationService.get('measurementLabels'); + const measurement = measurementService.getMeasurement(measurementId); + const value = await showLabelAnnotationPopup( + measurement, + servicesManager.services.uiDialogService, + labelConfig + ); + + measurementService.update( + measurementId, + { + ...value, + }, + true + ); + + resolve({ + StudyInstanceUID, + SeriesInstanceUID, + viewportId, + }); + }); +} + +export default promptLabelAnnotation; diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewSeries.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewSeries.js index 8a54598ea..d5e5575c5 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewSeries.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewSeries.js @@ -11,7 +11,9 @@ const RESPONSE = { function promptTrackNewSeries({ servicesManager, extensionManager }, ctx, evt) { const { UIViewportDialogService } = servicesManager.services; - const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt; + // When the state change happens after a promise, the state machine sends the retult in evt.data; + // In case of direct transition to the state, the state machine sends the data in evt; + const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt.data || evt; return new Promise(async function (resolve, reject) { let promptResult = await _askShouldAddMeasurements(UIViewportDialogService, viewportId); diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js index 03080ce90..b590af3f6 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js @@ -1,3 +1,5 @@ +import i18n from 'i18next'; + const RESPONSE = { NO_NEVER: -1, CANCEL: 0, @@ -9,7 +11,9 @@ const RESPONSE = { function promptTrackNewStudy({ servicesManager, extensionManager }, ctx, evt) { const { UIViewportDialogService } = servicesManager.services; - const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt; + // When the state change happens after a promise, the state machine sends the retult in evt.data; + // In case of direct transition to the state, the state machine sends the data in evt; + const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt.data || evt; return new Promise(async function (resolve, reject) { let promptResult = await _askTrackMeasurements(UIViewportDialogService, viewportId); @@ -32,17 +36,17 @@ function promptTrackNewStudy({ servicesManager, extensionManager }, ctx, evt) { function _askTrackMeasurements(UIViewportDialogService, viewportId) { return new Promise(function (resolve, reject) { - const message = 'Track measurements for this series?'; + const message = i18n.t('MeasurementTable:Track measurements for this series?'); const actions = [ - { type: 'cancel', text: 'No', value: RESPONSE.CANCEL }, + { type: 'cancel', text: i18n.t('MeasurementTable:No'), value: RESPONSE.CANCEL }, { type: 'secondary', - text: 'No, do not ask again for this series', + text: i18n.t('MeasurementTable:No, do not ask again'), value: RESPONSE.NO_NOT_FOR_SERIES, }, { type: 'primary', - text: 'Yes', + text: i18n.t('MeasurementTable:Yes'), value: RESPONSE.SET_STUDY_AND_SERIES, }, ]; @@ -61,6 +65,12 @@ function _askTrackMeasurements(UIViewportDialogService, viewportId) { UIViewportDialogService.hide(); resolve(RESPONSE.CANCEL); }, + onKeyPress: event => { + if (event.key === 'Enter') { + const action = actions.find(action => action.value === RESPONSE.SET_STUDY_AND_SERIES); + onSubmit(action.value); + } + }, }); }); } diff --git a/extensions/measurement-tracking/src/getPanelModule.tsx b/extensions/measurement-tracking/src/getPanelModule.tsx index 6d7a90633..a49a44a5b 100644 --- a/extensions/measurement-tracking/src/getPanelModule.tsx +++ b/extensions/measurement-tracking/src/getPanelModule.tsx @@ -1,5 +1,6 @@ import { Types } from '@ohif/core'; import { PanelMeasurementTableTracking, PanelStudyBrowserTracking } from './panels'; +import i18n from 'i18next'; // TODO: // - No loading UI exists yet @@ -11,7 +12,7 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager }): name: 'seriesList', iconName: 'tab-studies', iconLabel: 'Studies', - label: 'Studies', + label: i18n.t('SidePanel:Studies'), component: PanelStudyBrowserTracking.bind(null, { commandsManager, extensionManager, @@ -23,7 +24,7 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager }): name: 'trackedMeasurements', iconName: 'tab-linear', iconLabel: 'Measure', - label: 'Measurements', + label: i18n.t('SidePanel:Measurements'), component: PanelMeasurementTableTracking.bind(null, { commandsManager, extensionManager, diff --git a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/ActionButtons.tsx b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/ActionButtons.tsx deleted file mode 100644 index 0c5f39dce..000000000 --- a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/ActionButtons.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { useTranslation } from 'react-i18next'; - -import { Button, ButtonEnums } from '@ohif/ui'; - -function ActionButtons({ onExportClick, onCreateReportClick, disabled }) { - const { t } = useTranslation('MeasurementTable'); - - return ( - - - - - ); -} - -ActionButtons.propTypes = { - onExportClick: PropTypes.func, - onCreateReportClick: PropTypes.func, - disabled: PropTypes.bool, -}; - -ActionButtons.defaultProps = { - onExportClick: () => alert('Export'), - onCreateReportClick: () => alert('Create Report'), - disabled: false, -}; - -export default ActionButtons; diff --git a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.tsx b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.tsx index 7b0ca7977..6d7fbf31d 100644 --- a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.tsx +++ b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.tsx @@ -1,18 +1,12 @@ import React, { useEffect, useState, useRef } from 'react'; import PropTypes from 'prop-types'; -import { - StudySummary, - MeasurementTable, - Dialog, - Input, - useViewportGrid, - ButtonEnums, -} from '@ohif/ui'; +import { StudySummary, MeasurementTable, useViewportGrid, ActionButtons } from '@ohif/ui'; import { DicomMetadataStore, utils } from '@ohif/core'; import { useDebounce } from '@hooks'; -import ActionButtons from './ActionButtons'; +import { useAppConfig } from '@state'; import { useTrackedMeasurements } from '../../getContextModule'; import debounce from 'lodash.debounce'; +import { useTranslation } from 'react-i18next'; const { downloadCSVReport } = utils; const { formatDate } = utils; @@ -26,9 +20,11 @@ const DISPLAY_STUDY_SUMMARY_INITIAL_VALUE = { function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { const [viewportGrid] = useViewportGrid(); + const { t } = useTranslation('MeasurementTable'); const [measurementChangeTimestamp, setMeasurementsUpdated] = useState(Date.now().toString()); const debouncedMeasurementChangeTimestamp = useDebounce(measurementChangeTimestamp, 200); - const { measurementService, uiDialogService, displaySetService } = servicesManager.services; + const { measurementService, uiDialogService, displaySetService, customizationService } = + servicesManager.services; const [trackedMeasurements, sendTrackedMeasurementsEvent] = useTrackedMeasurements(); const { trackedStudy, trackedSeries } = trackedMeasurements.context; const [displayStudySummary, setDisplayStudySummary] = useState( @@ -36,6 +32,7 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { ); const [displayMeasurements, setDisplayMeasurements] = useState([]); const measurementsPanelRef = useRef(null); + const [appConfig] = useAppConfig(); useEffect(() => { const measurements = measurementService.getMeasurements(); @@ -132,67 +129,24 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { }; const onMeasurementItemEditHandler = ({ uid, isActive }) => { - const measurement = measurementService.getMeasurement(uid); jumpToImage({ uid, isActive }); - - const onSubmitHandler = ({ action, value }) => { - switch (action.id) { - case 'save': { - measurementService.update( - uid, - { - ...measurement, - ...value, - }, - true - ); - } + const labelConfig = customizationService.get('measurementLabels'); + const measurement = measurementService.getMeasurement(uid); + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.common' + ); + const { showLabelAnnotationPopup } = utilityModule.exports; + showLabelAnnotationPopup(measurement, uiDialogService, labelConfig).then( + (val: Map) => { + measurementService.update( + uid, + { + ...val, + }, + true + ); } - uiDialogService.dismiss({ id: 'enter-annotation' }); - }; - - uiDialogService.create({ - id: 'enter-annotation', - centralize: true, - isDraggable: false, - showOverlay: true, - content: Dialog, - contentProps: { - title: 'Annotation', - noCloseButton: true, - value: { label: measurement.label || '' }, - body: ({ value, setValue }) => { - const onChangeHandler = event => { - event.persist(); - setValue(value => ({ ...value, label: event.target.value })); - }; - - const onKeyPressHandler = event => { - if (event.key === 'Enter') { - onSubmitHandler({ value, action: { id: 'save' } }); - } - }; - return ( - - ); - }, - actions: [ - { id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary }, - { id: 'save', text: 'Save', type: ButtonEnums.type.primary }, - ], - onSubmit: onSubmitHandler, - }, - }); + ); }; const onMeasurementItemClickHandler = ({ uid, isActive }) => { @@ -213,6 +167,9 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { dm => dm.measurementType === measurementService.VALUE_TYPES.POINT ); + const disabled = + additionalFindings.length === 0 && displayMeasurementsWithoutFindings.length === 0; + return ( <>
)}
-
- { - sendTrackedMeasurementsEvent('SAVE_REPORT', { - viewportId: viewportGrid.activeViewportId, - isBackupSave: true, - }); - }} - disabled={ - additionalFindings.length === 0 && displayMeasurementsWithoutFindings.length === 0 - } - /> -
+ {!appConfig?.disableEditing && ( +
+ { + sendTrackedMeasurementsEvent('SAVE_REPORT', { + viewportId: viewportGrid.activeViewportId, + isBackupSave: true, + }); + }, + }, + ]} + disabled={disabled} + /> +
+ )} ); } diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx index 98bc050c3..05cce40ec 100644 --- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx +++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx @@ -19,8 +19,13 @@ function PanelStudyBrowserTracking({ requestDisplaySetCreationForStudy, dataSource, }) { - const { displaySetService, uiDialogService, hangingProtocolService, uiNotificationService } = - servicesManager.services; + const { + displaySetService, + uiDialogService, + hangingProtocolService, + uiNotificationService, + measurementService, + } = servicesManager.services; const navigate = useNavigate(); const { t } = useTranslation('Common'); @@ -29,7 +34,8 @@ function PanelStudyBrowserTracking({ // doesn't have to have such an intense shape. This works well enough for now. // Tabs --> Studies --> DisplaySets --> Thumbnails const { StudyInstanceUIDs } = useImageViewer(); - const [{ activeViewportId, viewports }, viewportGridService] = useViewportGrid(); + const [{ activeViewportId, viewports, isHangingProtocolLayout }, viewportGridService] = + useViewportGrid(); const [trackedMeasurements, sendTrackedMeasurementsEvent] = useTrackedMeasurements(); const [activeTabName, setActiveTabName] = useState('primary'); const [expandedStudyInstanceUIDs, setExpandedStudyInstanceUIDs] = useState([ @@ -46,7 +52,8 @@ function PanelStudyBrowserTracking({ try { updatedViewports = hangingProtocolService.getViewportsRequireUpdate( viewportId, - displaySetInstanceUID + displaySetInstanceUID, + isHangingProtocolLayout ); } catch (error) { console.warn(error); @@ -129,7 +136,8 @@ function PanelStudyBrowserTracking({ const newImageSrcEntry = {}; const displaySet = displaySetService.getDisplaySetByUID(dSet.displaySetInstanceUID); const imageIds = dataSource.getImageIdsForDisplaySet(displaySet); - const imageId = imageIds[Math.floor(imageIds.length / 2)]; + + const imageId = getImageIdForThumbnail(displaySet, imageIds); // TODO: Is it okay that imageIds are not returned here for SR displaySets? if (!imageId || displaySet?.unsupported) { @@ -195,7 +203,7 @@ function PanelStudyBrowserTracking({ } const imageIds = dataSource.getImageIdsForDisplaySet(displaySet); - const imageId = imageIds[Math.floor(imageIds.length / 2)]; + const imageId = getImageIdForThumbnail(displaySet, imageIds); // TODO: Is it okay that imageIds are not returned here for SR displaysets? if (!imageId) { @@ -323,6 +331,65 @@ function PanelStudyBrowserTracking({ } }, [expandedStudyInstanceUIDs, jumpToDisplaySet, tabs]); + const onClickUntrack = displaySetInstanceUID => { + const onConfirm = () => { + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); + sendTrackedMeasurementsEvent('UNTRACK_SERIES', { + SeriesInstanceUID: displaySet.SeriesInstanceUID, + }); + const measurements = measurementService.getMeasurements(); + measurements.forEach(m => { + if (m.referenceSeriesUID === displaySet.SeriesInstanceUID) { + measurementService.remove(m.uid); + } + }); + }; + + uiDialogService.create({ + id: 'untrack-series', + centralize: true, + isDraggable: false, + showOverlay: true, + content: Dialog, + contentProps: { + title: 'Untrack Series', + body: () => ( +
+

Are you sure you want to untrack this series?

+

+ This action cannot be undone and will delete all your existing measurements. +

+
+ ), + actions: [ + { + id: 'cancel', + text: 'Cancel', + type: ButtonEnums.type.secondary, + }, + { + id: 'yes', + text: 'Yes', + type: ButtonEnums.type.primary, + classes: ['untrack-yes-button'], + }, + ], + onClose: () => uiDialogService.dismiss({ id: 'untrack-series' }), + onSubmit: async ({ action }) => { + switch (action.id) { + case 'yes': + onConfirm(); + uiDialogService.dismiss({ id: 'untrack-series' }); + break; + case 'cancel': + uiDialogService.dismiss({ id: 'untrack-series' }); + break; + } + }, + }, + }); + }; + return ( { - const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); - // TODO: shift this somewhere else where we're centralizing this logic? - // Potentially a helper from displaySetInstanceUID to this - sendTrackedMeasurementsEvent('UNTRACK_SERIES', { - SeriesInstanceUID: displaySet.SeriesInstanceUID, - }); + onClickUntrack(displaySetInstanceUID); }} onClickThumbnail={() => {}} onDoubleClickThumbnail={onDoubleClickThumbnailHandler} @@ -360,6 +422,19 @@ PanelStudyBrowserTracking.propTypes = { export default PanelStudyBrowserTracking; +function getImageIdForThumbnail(displaySet: any, imageIds: any) { + let imageId; + if (displaySet.isDynamicVolume) { + const timePoints = displaySet.dynamicVolumeInfo.timePoints; + const middleIndex = Math.floor(timePoints.length / 2); + const middleTimePointImageIds = timePoints[middleIndex]; + imageId = middleTimePointImageIds[Math.floor(middleTimePointImageIds.length / 2)]; + } else { + imageId = imageIds[Math.floor(imageIds.length / 2)]; + } + return imageId; +} + /** * Maps from the DataSource's format to a naturalized object * @@ -401,15 +476,6 @@ function _mapDisplaySets( const imageSrc = thumbnailImageSrcMap[ds.displaySetInstanceUID]; const componentType = _getComponentType(ds); const numPanes = viewportGridService.getNumViewportPanes(); - const viewportIdentificator = []; - - if (numPanes !== 1) { - viewports.forEach(viewportData => { - if (viewportData?.displaySetInstanceUIDs?.includes(ds.displaySetInstanceUID)) { - viewportIdentificator.push(viewportData.viewportLabel); - } - }); - } const array = componentType === 'thumbnailTracked' ? thumbnailDisplaySets : thumbnailNoImageDisplaySets; @@ -435,7 +501,6 @@ function _mapDisplaySets( }, isTracked: trackedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID), isHydratedForDerivedDisplaySet: ds.isHydrated, - viewportIdentificator, }; if (componentType === 'thumbnailNoImage') { diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/getImageSrcFromImageId.js b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/getImageSrcFromImageId.js index 60a31499b..8b5f74d84 100644 --- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/getImageSrcFromImageId.js +++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/getImageSrcFromImageId.js @@ -6,7 +6,7 @@ function getImageSrcFromImageId(cornerstone, imageId) { return new Promise((resolve, reject) => { const canvas = document.createElement('canvas'); cornerstone.utilities - .loadImageToCanvas({ canvas, imageId }) + .loadImageToCanvas({ canvas, imageId, thumbnail: true }) .then(imageId => { resolve(canvas.toDataURL()); }) diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/index.tsx b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/index.tsx index 3ce336cd4..cffd41bb0 100644 --- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/index.tsx +++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import PropTypes from 'prop-types'; // import PanelStudyBrowserTracking from './PanelStudyBrowserTracking'; @@ -26,7 +26,10 @@ function WrappedPanelStudyBrowserTracking({ commandsManager, extensionManager, s const getStudiesForPatientByMRN = _getStudyForPatientUtility(extensionManager); const _getStudiesForPatientByMRN = getStudiesForPatientByMRN.bind(null, dataSource); - const _getImageSrcFromImageId = _createGetImageSrcFromImageIdFn(extensionManager); + const _getImageSrcFromImageId = useCallback( + _createGetImageSrcFromImageIdFn(extensionManager), + [] + ); const _requestDisplaySetCreationForStudy = requestDisplaySetCreationForStudy.bind( null, dataSource diff --git a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx index aed9f6563..94ca843bb 100644 --- a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx +++ b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx @@ -1,27 +1,29 @@ import React, { useState, useEffect, useCallback } from 'react'; import PropTypes from 'prop-types'; -import OHIF, { utils } from '@ohif/core'; -import { ViewportActionBar, Tooltip, Icon } from '@ohif/ui'; - -import { useTranslation } from 'react-i18next'; +import { Tooltip, Icon, ViewportActionArrows, useViewportGrid } from '@ohif/ui'; import { annotation } from '@cornerstonejs/tools'; import { useTrackedMeasurements } from './../getContextModule'; import { BaseVolumeViewport, Enums } from '@cornerstonejs/core'; - -const { formatDate } = utils; +import { useTranslation } from 'react-i18next'; function TrackedCornerstoneViewport(props) { - const { displaySets, viewportId, viewportLabel, servicesManager, extensionManager } = props; + const { displaySets, viewportId, servicesManager, extensionManager } = props; - const { t } = useTranslation('Common'); - - const { measurementService, cornerstoneViewportService, viewportGridService } = - servicesManager.services; + const { + measurementService, + cornerstoneViewportService, + viewportGridService, + viewportActionCornersService, + } = servicesManager.services; // Todo: handling more than one displaySet on the same viewport const displaySet = displaySets[0]; + const { t } = useTranslation('Common'); + + const [viewportGrid] = useViewportGrid(); + const { activeViewportId } = viewportGrid; const [trackedMeasurements, sendTrackedMeasurementsEvent] = useTrackedMeasurements(); @@ -31,18 +33,7 @@ function TrackedCornerstoneViewport(props) { const { trackedSeries } = trackedMeasurements.context; - const { SeriesDate, SeriesDescription, SeriesInstanceUID, SeriesNumber } = displaySet; - - const { - PatientID, - PatientName, - PatientSex, - PatientAge, - SliceThickness, - SpacingBetweenSlices, - StudyDate, - ManufacturerModelName, - } = displaySet.images[0]; + const { SeriesInstanceUID } = displaySet; const updateIsTracked = useCallback(() => { const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); @@ -149,14 +140,18 @@ function TrackedCornerstoneViewport(props) { // Only send the tracked measurements event for the active viewport to avoid // sending it more than once. if (viewportId === activeViewportId) { - const { referenceStudyUID: StudyInstanceUID, referenceSeriesUID: SeriesInstanceUID } = - measurement; + const { + referenceStudyUID: StudyInstanceUID, + referenceSeriesUID: SeriesInstanceUID, + uid: measurementId, + } = measurement; sendTrackedMeasurementsEvent('SET_DIRTY', { SeriesInstanceUID }); sendTrackedMeasurementsEvent('TRACK_SERIES', { viewportId, StudyInstanceUID, SeriesInstanceUID, + measurementId, }); } }).unsubscribe @@ -170,22 +165,51 @@ function TrackedCornerstoneViewport(props) { }; }, [measurementService, sendTrackedMeasurementsEvent, viewportId, viewportGridService]); - function switchMeasurement(direction) { - const newTrackedMeasurementUID = _getNextMeasurementUID( - direction, - servicesManager, - trackedMeasurementUID, - trackedMeasurements + const switchMeasurement = useCallback( + direction => { + const newTrackedMeasurementUID = _getNextMeasurementUID( + direction, + servicesManager, + trackedMeasurementUID, + trackedMeasurements + ); + + if (!newTrackedMeasurementUID) { + return; + } + + setTrackedMeasurementUID(newTrackedMeasurementUID); + + measurementService.jumpToMeasurement(viewportId, newTrackedMeasurementUID); + }, + [measurementService, servicesManager, trackedMeasurementUID, trackedMeasurements, viewportId] + ); + + useEffect(() => { + const statusComponent = _getStatusComponent(isTracked, t); + const arrowsComponent = _getArrowsComponent( + isTracked, + switchMeasurement, + viewportId === activeViewportId ); - if (!newTrackedMeasurementUID) { - return; - } - - setTrackedMeasurementUID(newTrackedMeasurementUID); - - measurementService.jumpToMeasurement(viewportId, newTrackedMeasurementUID); - } + viewportActionCornersService.setComponents([ + { + viewportId, + id: 'viewportStatusComponent', + component: statusComponent, + indexPriority: -100, + location: viewportActionCornersService.LOCATIONS.topLeft, + }, + { + viewportId, + id: 'viewportActionArrowsComponent', + component: arrowsComponent, + indexPriority: 0, + location: viewportActionCornersService.LOCATIONS.topRight, + }, + ]); + }, [activeViewportId, isTracked, switchMeasurement, viewportActionCornersService, viewportId]); const getCornerstoneViewport = () => { const { component: Component } = extensionManager.getModuleEntry( @@ -195,7 +219,10 @@ function TrackedCornerstoneViewport(props) { return ( { + props.onElementEnabled?.(evt); + onElementEnabled(evt); + }} onElementDisabled={onElementDisabled} /> ); @@ -203,35 +230,6 @@ function TrackedCornerstoneViewport(props) { return ( <> - { - evt.stopPropagation(); - evt.preventDefault(); - }} - useAltStyling={isTracked} - onArrowsClick={direction => switchMeasurement(direction)} - getStatusComponent={() => _getStatusComponent(isTracked)} - studyData={{ - label: viewportLabel, - studyDate: formatDate(SeriesDate) || formatDate(StudyDate) || t('NoStudyDate'), - currentSeries: SeriesNumber, // TODO - switch entire currentSeries to be UID based or actual position based - seriesDescription: SeriesDescription, - patientInformation: { - patientName: PatientName ? OHIF.utils.formatPN(PatientName) : '', - patientSex: PatientSex || '', - patientAge: PatientAge || '', - MRN: PatientID || '', - thickness: SliceThickness ? `${parseFloat(SliceThickness).toFixed(2)}` : '', - thicknessUnits: 'mm', - spacing: - SpacingBetweenSlices !== undefined - ? `${parseFloat(SpacingBetweenSlices).toFixed(2)}mm` - : '', - scanner: ManufacturerModelName || '', - }, - }} - /> - {/* TODO: Viewport interface to accept stack or layers of content like this? */}
{getCornerstoneViewport()}
@@ -291,18 +289,11 @@ function _getNextMeasurementUID( // Not tracking a measurement, or previous measurement now deleted, revert to 0. measurementIndex = 0; } else { - if (direction === 'left') { - measurementIndex--; - - if (measurementIndex < 0) { - measurementIndex = measurementCount - 1; - } - } else if (direction === 'right') { - measurementIndex++; - - if (measurementIndex === measurementCount) { - measurementIndex = 0; - } + measurementIndex += direction; + if (measurementIndex < 0) { + measurementIndex = measurementCount - 1; + } else if (measurementIndex === measurementCount) { + measurementIndex = 0; } } @@ -311,8 +302,23 @@ function _getNextMeasurementUID( return newTrackedMeasurementId; } -function _getStatusComponent(isTracked) { - const trackedIcon = isTracked ? 'status-tracked' : 'status-untracked'; +const _getArrowsComponent = (isTracked, switchMeasurement, isActiveViewport) => { + if (!isTracked) { + return null; + } + + return ( + switchMeasurement(direction)} + className={isActiveViewport ? 'visible' : 'invisible group-hover:visible'} + > + ); +}; + +function _getStatusComponent(isTracked, t) { + if (!isTracked) { + return null; + } return (
@@ -329,16 +335,12 @@ function _getStatusComponent(isTracked) {
{isTracked ? ( - <> - Series is - tracked and can be viewed
{' '} - in the measurement panel - + <>{t('Series is tracked and can be viewed in the measurement panel')} ) : ( <> - Measurements for - untracked - series
will not be shown in the
measurements panel + {t( + 'Measurements for untracked series will not be shown in the measurements panel' + )} )}
@@ -347,7 +349,7 @@ function _getStatusComponent(isTracked) { } > diff --git a/extensions/test-extension/CHANGELOG.md b/extensions/test-extension/CHANGELOG.md index 90894ec57..13731c6cc 100644 --- a/extensions/test-extension/CHANGELOG.md +++ b/extensions/test-extension/CHANGELOG.md @@ -3,7 +3,760 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -# [3.7.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.7.0) (2023-10-11) +# [3.8.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.92...v3.8.0-beta.93) (2024-04-29) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.91...v3.8.0-beta.92) (2024-04-28) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.90...v3.8.0-beta.91) (2024-04-25) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.89...v3.8.0-beta.90) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.88...v3.8.0-beta.89) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.87...v3.8.0-beta.88) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.86...v3.8.0-beta.87) (2024-04-19) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.85...v3.8.0-beta.86) (2024-04-19) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.84...v3.8.0-beta.85) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.83...v3.8.0-beta.84) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.82...v3.8.0-beta.83) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.81...v3.8.0-beta.82) (2024-04-17) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.80...v3.8.0-beta.81) (2024-04-16) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.79...v3.8.0-beta.80) (2024-04-16) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.78...v3.8.0-beta.79) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.77...v3.8.0-beta.78) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.76...v3.8.0-beta.77) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.75...v3.8.0-beta.76) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.74...v3.8.0-beta.75) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.73...v3.8.0-beta.74) (2024-04-10) + + +### Features + +* **4D:** Add 4D dynamic volume rendering and new pre-clinical 4d pt/ct mode ([#3664](https://github.com/OHIF/Viewers/issues/3664)) ([d57e8bc](https://github.com/OHIF/Viewers/commit/d57e8bc1571c6da4effaa492ee2d162c552365a2)) + + + + + +# [3.8.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.72...v3.8.0-beta.73) (2024-04-08) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.71...v3.8.0-beta.72) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.70...v3.8.0-beta.71) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.69...v3.8.0-beta.70) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.68...v3.8.0-beta.69) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.67...v3.8.0-beta.68) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.66...v3.8.0-beta.67) (2024-04-02) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + + +### Bug Fixes + +* **sr:** dcm4chee requires the patient name for an SR to match what is in the original study ([#3739](https://github.com/OHIF/Viewers/issues/3739)) ([d98439f](https://github.com/OHIF/Viewers/commit/d98439fe7f3825076dbc87b664a1d1480ff414d3)) + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-test + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) **Note:** Version bump only for package @ohif/extension-test diff --git a/extensions/test-extension/package.json b/extensions/test-extension/package.json index 325ce4555..567447d29 100644 --- a/extensions/test-extension/package.json +++ b/extensions/test-extension/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-test", - "version": "3.7.0", + "version": "3.8.0-beta.93", "description": "OHIF extension used inside e2e testing", "author": "OHIF", "license": "MIT", @@ -20,6 +20,8 @@ "access": "public" }, "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", "build:package-1": "yarn run build", @@ -28,9 +30,9 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0", - "@ohif/ui": "3.7.0", - "dcmjs": "0.29.4", + "@ohif/core": "3.8.0-beta.93", + "@ohif/ui": "3.8.0-beta.93", + "dcmjs": "0.29.11", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/test-extension/src/hp/index.ts b/extensions/test-extension/src/hp/index.ts new file mode 100644 index 000000000..a1b0c22c2 --- /dev/null +++ b/extensions/test-extension/src/hp/index.ts @@ -0,0 +1,17 @@ +import hpMN from './hpMN'; + +const hangingProtocols = [ + { + name: '@ohif/hp-extension.mn', + protocol: hpMN, + }, +]; + +/** + * Registers a single study hanging protocol which can be referenced as + * `@ohif/hp-exgtension.mn`, that has initial layouts which show images + * only display sets, up to a 2x2 view. + */ +export default function getHangingProtocolModule() { + return hangingProtocols; +} diff --git a/extensions/tmtv/.webpack/webpack.prod.js b/extensions/tmtv/.webpack/webpack.prod.js index 8b1a2ab80..bc656140d 100644 --- a/extensions/tmtv/.webpack/webpack.prod.js +++ b/extensions/tmtv/.webpack/webpack.prod.js @@ -7,11 +7,14 @@ const pkg = require('./../package.json'); const ROOT_DIR = path.join(__dirname, './..'); const SRC_DIR = path.join(__dirname, '../src'); const DIST_DIR = path.join(__dirname, '../dist'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const ENTRY = { app: `${SRC_DIR}/index.tsx`, }; +const outputName = `ohif-${pkg.name.split('/').pop()}`; + module.exports = (env, argv) => { const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); @@ -42,6 +45,10 @@ module.exports = (env, argv) => { new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, }), + new MiniCssExtractPlugin({ + filename: `./dist/${outputName}.css`, + chunkFilename: `./dist/${outputName}.css`, + }), ], }); }; diff --git a/extensions/tmtv/CHANGELOG.md b/extensions/tmtv/CHANGELOG.md index 05c85dda8..86303fd34 100644 --- a/extensions/tmtv/CHANGELOG.md +++ b/extensions/tmtv/CHANGELOG.md @@ -3,7 +3,787 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -# [3.7.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.7.0) (2023-10-11) +# [3.8.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.92...v3.8.0-beta.93) (2024-04-29) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.91...v3.8.0-beta.92) (2024-04-28) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.90...v3.8.0-beta.91) (2024-04-25) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.89...v3.8.0-beta.90) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.88...v3.8.0-beta.89) (2024-04-22) + + +### Bug Fixes + +* **viewport-webworker-segmentation:** Resolve issues with viewport detection, webworker termination, and segmentation panel layout change ([#4059](https://github.com/OHIF/Viewers/issues/4059)) ([52a0c59](https://github.com/OHIF/Viewers/commit/52a0c59294a4161fcca0a6708855549034849951)) + + + + + +# [3.8.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.87...v3.8.0-beta.88) (2024-04-22) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.86...v3.8.0-beta.87) (2024-04-19) + + +### Features + +* **tmtv-mode:** Add Brush tools and move SUV peak calculation to web worker ([#4053](https://github.com/OHIF/Viewers/issues/4053)) ([8192e34](https://github.com/OHIF/Viewers/commit/8192e348eca993fec331d4963efe88f9a730eceb)) + + + + + +# [3.8.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.85...v3.8.0-beta.86) (2024-04-19) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.84...v3.8.0-beta.85) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.83...v3.8.0-beta.84) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.82...v3.8.0-beta.83) (2024-04-18) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.81...v3.8.0-beta.82) (2024-04-17) + + +### Bug Fixes + +* **bugs:** enhancements and bug fixes - more ([#4043](https://github.com/OHIF/Viewers/issues/4043)) ([3754c22](https://github.com/OHIF/Viewers/commit/3754c224b4dab28182adb0a41e37d890942144d8)) + + + + + +# [3.8.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.80...v3.8.0-beta.81) (2024-04-16) + + +### Bug Fixes + +* **viewport:** Reset viewport state and fix CINE looping, thumbnail resolution, and dynamic tool settings ([#4037](https://github.com/OHIF/Viewers/issues/4037)) ([f99a0bf](https://github.com/OHIF/Viewers/commit/f99a0bfb31434aa137bbb3ed1f9eef1dfcc09025)) + + + + + +# [3.8.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.79...v3.8.0-beta.80) (2024-04-16) + + +### Bug Fixes + +* **bugs:** enhancements and bug fixes ([#4036](https://github.com/OHIF/Viewers/issues/4036)) ([e80fc6f](https://github.com/OHIF/Viewers/commit/e80fc6f47708e1d6b1a1e1de438196a4b74ec637)) + + + + + +# [3.8.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.78...v3.8.0-beta.79) (2024-04-10) + + +### Features + +* **SM:** remove SM measurements from measurement panel ([#4022](https://github.com/OHIF/Viewers/issues/4022)) ([df49a65](https://github.com/OHIF/Viewers/commit/df49a653be61a93f6e9fb3663aabe9775c31fd13)) + + + + + +# [3.8.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.77...v3.8.0-beta.78) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.76...v3.8.0-beta.77) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.75...v3.8.0-beta.76) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.74...v3.8.0-beta.75) (2024-04-10) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.73...v3.8.0-beta.74) (2024-04-10) + + +### Features + +* **4D:** Add 4D dynamic volume rendering and new pre-clinical 4d pt/ct mode ([#3664](https://github.com/OHIF/Viewers/issues/3664)) ([d57e8bc](https://github.com/OHIF/Viewers/commit/d57e8bc1571c6da4effaa492ee2d162c552365a2)) + + + + + +# [3.8.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.72...v3.8.0-beta.73) (2024-04-08) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.71...v3.8.0-beta.72) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.70...v3.8.0-beta.71) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.69...v3.8.0-beta.70) (2024-04-05) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.68...v3.8.0-beta.69) (2024-04-03) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.67...v3.8.0-beta.68) (2024-04-03) + + +### Features + +* **segmentation:** Enhanced segmentation panel design for TMTV ([#3988](https://github.com/OHIF/Viewers/issues/3988)) ([9f3235f](https://github.com/OHIF/Viewers/commit/9f3235ff096636aafa88d8a42859e8dc85d9036d)) + + + + + +# [3.8.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.66...v3.8.0-beta.67) (2024-04-02) + + +### Features + +* **ViewportActionMenu:** window level per viewport / new patient info / colorbars/ 3D presets and 3D volume rendering ([#3963](https://github.com/OHIF/Viewers/issues/3963)) ([b7f90e3](https://github.com/OHIF/Viewers/commit/b7f90e3951845396f99b69f0a74fc56b2ffeada1)) + + + + + +# [3.8.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.65...v3.8.0-beta.66) (2024-03-28) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.64...v3.8.0-beta.65) (2024-03-28) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.63...v3.8.0-beta.64) (2024-03-27) + + +### Features + +* **toolbar:** new Toolbar to enable reactive state synchronization ([#3983](https://github.com/OHIF/Viewers/issues/3983)) ([566b25a](https://github.com/OHIF/Viewers/commit/566b25a54425399096864bd263193646556011a5)) + + + + + +# [3.8.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.62...v3.8.0-beta.63) (2024-03-25) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.61...v3.8.0-beta.62) (2024-03-19) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.60...v3.8.0-beta.61) (2024-03-18) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + + +### Bug Fixes + +* **sr:** dcm4chee requires the patient name for an SR to match what is in the original study ([#3739](https://github.com/OHIF/Viewers/issues/3739)) ([d98439f](https://github.com/OHIF/Viewers/commit/d98439fe7f3825076dbc87b664a1d1480ff414d3)) + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-tmtv + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) **Note:** Version bump only for package @ohif/extension-tmtv diff --git a/extensions/tmtv/package.json b/extensions/tmtv/package.json index 3d031b573..5ecd0c722 100644 --- a/extensions/tmtv/package.json +++ b/extensions/tmtv/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-tmtv", - "version": "3.7.0", + "version": "3.8.0-beta.93", "description": "OHIF extension for Total Metabolic Tumor Volume", "author": "OHIF", "license": "MIT", @@ -20,6 +20,8 @@ "access": "public" }, "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", "build:package": "yarn run build", @@ -28,9 +30,9 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0", - "@ohif/ui": "3.7.0", - "dcmjs": "^0.29.5", + "@ohif/core": "3.8.0-beta.93", + "@ohif/ui": "3.8.0-beta.93", + "dcmjs": "^0.29.12", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/tmtv/src/Panels/PanelPetSUV.tsx b/extensions/tmtv/src/Panels/PanelPetSUV.tsx index f0a467eef..eccd76afb 100644 --- a/extensions/tmtv/src/Panels/PanelPetSUV.tsx +++ b/extensions/tmtv/src/Panels/PanelPetSUV.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import { Input, Button } from '@ohif/ui'; +import { PanelSection, Input, Button } from '@ohif/ui'; import { DicomMetadataStore, ServicesManager } from '@ohif/core'; import { useTranslation } from 'react-i18next'; @@ -109,23 +109,6 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) { throw new Error('No ptDisplaySet found'); } - const toolGroupIds = toolGroupService.getToolGroupIds(); - - // Todo: we don't have a proper way to perform a toggle command and update the - // state for the toolbar, so here, we manually toggle the toolbar - - // Todo: Crosshairs have bugs for the camera reset currently, so we need to - // force turn it off before we update the metadata - toolGroupIds.forEach(toolGroupId => { - commandsManager.runCommand('toggleCrosshairs', { - toolGroupId, - toggledState: false, - }); - }); - - toolbarService.state.toggles['Crosshairs'] = false; - toolbarService._broadcastEvent(toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED); - // metadata should be dcmjs naturalized DicomMetadataStore.updateMetadataForSeries( ptDisplaySet.StudyInstanceUID, @@ -135,86 +118,110 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) { // update the displaySets displaySetService.setDisplaySetMetadataInvalidated(ptDisplaySet.displaySetInstanceUID); + + // Crosshair position depends on the metadata values such as the positioning interaction + // between series, so when the metadata is updated, the crosshairs need to be reset. + setTimeout(() => { + commandsManager.runCommand('resetCrosshairs'); + }, 0); } return ( -
- { -
-
- { - handleMetadataChange({ - PatientSex: e.target.value, - }); - }} - /> - { - handleMetadataChange({ - PatientWeight: e.target.value, - }); - }} - /> - { - handleMetadataChange({ - RadiopharmaceuticalInformationSequence: { - RadionuclideTotalDose: e.target.value, - }, - }); - }} - /> - { - handleMetadataChange({ - RadiopharmaceuticalInformationSequence: { - RadionuclideHalfLife: e.target.value, - }, - }); - }} - /> - { - handleMetadataChange({ - RadiopharmaceuticalInformationSequence: { - RadiopharmaceuticalStartTime: e.target.value, - }, - }); - }} - /> - {}} - /> - +
+
+ +
+
+ { + handleMetadataChange({ + PatientSex: e.target.value, + }); + }} + /> + kg} + labelClassName="text-[13px] font-inter text-white" + className="!m-0 !h-[26px] !w-[117px]" + value={metadata.PatientWeight || ''} + onChange={e => { + handleMetadataChange({ + PatientWeight: e.target.value, + }); + }} + /> + bq} + labelClassName="text-[13px] font-inter text-white" + className="!m-0 !h-[26px] !w-[117px]" + value={metadata.RadiopharmaceuticalInformationSequence.RadionuclideTotalDose || ''} + onChange={e => { + handleMetadataChange({ + RadiopharmaceuticalInformationSequence: { + RadionuclideTotalDose: e.target.value, + }, + }); + }} + /> + s} + labelClassName="text-[13px] font-inter text-white" + className="!m-0 !h-[26px] !w-[117px]" + value={metadata.RadiopharmaceuticalInformationSequence.RadionuclideHalfLife || ''} + onChange={e => { + handleMetadataChange({ + RadiopharmaceuticalInformationSequence: { + RadionuclideHalfLife: e.target.value, + }, + }); + }} + /> + s} + labelClassName="text-[13px] font-inter text-white" + className="!m-0 !h-[26px] !w-[117px]" + value={ + metadata.RadiopharmaceuticalInformationSequence.RadiopharmaceuticalStartTime || '' + } + onChange={e => { + handleMetadataChange({ + RadiopharmaceuticalInformationSequence: { + RadiopharmaceuticalStartTime: e.target.value, + }, + }); + }} + /> + s} + labelClassName="text-[13px] font-inter text-white" + className="!m-0 !h-[26px] !w-[117px]" + value={metadata.SeriesTime || ''} + onChange={() => {}} + /> + +
-
- } + +
); } diff --git a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ExportReports.tsx b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ExportReports.tsx deleted file mode 100644 index 628cb14ea..000000000 --- a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ExportReports.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import { LegacyButton, LegacyButtonGroup } from '@ohif/ui'; -import { useTranslation } from 'react-i18next'; - -function ExportReports({ segmentations, tmtvValue, config, commandsManager }) { - const { t } = useTranslation('PanelSUVExport'); - - return ( - <> - {segmentations?.length ? ( -
- {/* TODO Revisit design of LegacyButtonGroup later - for now use LegacyButton for its children.*/} - - { - commandsManager.runCommand('exportTMTVReportCSV', { - segmentations, - tmtv: tmtvValue, - config, - }); - }} - > - {t('Export CSV')} - - - - { - commandsManager.runCommand('createTMTVRTReport'); - }} - disabled={tmtvValue === null} - > - {t('Create RT Report')} - - -
- ) : null} - - ); -} - -export default ExportReports; diff --git a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdExport.tsx b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdExport.tsx new file mode 100644 index 000000000..a16eaa812 --- /dev/null +++ b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdExport.tsx @@ -0,0 +1,168 @@ +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { Icon, ActionButtons } from '@ohif/ui'; +import { useTranslation } from 'react-i18next'; +import { eventTarget } from '@cornerstonejs/core'; +import { Enums } from '@cornerstonejs/tools'; +import { handleROIThresholding } from '../../utils/handleROIThresholding'; + +export default function PanelRoiThresholdSegmentation({ servicesManager, commandsManager }) { + const { segmentationService, uiNotificationService } = servicesManager.services; + const { t } = useTranslation('PanelSUVExport'); + + const [segmentations, setSegmentations] = useState(() => segmentationService.getSegmentations()); + const [activeSegmentation, setActiveSegmentation] = useState(null); + + /** + * Update UI based on segmentation changes (added, removed, updated) + */ + useEffect(() => { + // ~~ Subscription + const added = segmentationService.EVENTS.SEGMENTATION_ADDED; + const updated = segmentationService.EVENTS.SEGMENTATION_UPDATED; + const removed = segmentationService.EVENTS.SEGMENTATION_REMOVED; + const subscriptions = []; + + [added, updated, removed].forEach(evt => { + const { unsubscribe } = segmentationService.subscribe(evt, () => { + const segmentations = segmentationService.getSegmentations(); + setSegmentations(segmentations); + + const activeSegmentation = segmentations.filter(seg => seg.isActive); + setActiveSegmentation(activeSegmentation[0]); + }); + + subscriptions.push(unsubscribe); + }); + + return () => { + subscriptions.forEach(unsub => { + unsub(); + }); + }; + }, []); + + useEffect(() => { + const callback = async evt => { + const { detail } = evt; + const { segmentationId } = detail; + + if (!segmentationId) { + return; + } + + await handleROIThresholding({ + segmentationId, + commandsManager, + segmentationService, + }); + + const segmentation = segmentationService.getSegmentation(segmentationId); + + const { cachedStats } = segmentation; + if (!cachedStats) { + return; + } + + // segment 1 + const suvPeak = cachedStats?.['1']?.suvPeak?.suvPeak; + + if (Number.isNaN(suvPeak)) { + uiNotificationService.show({ + title: 'SUV Peak', + message: 'Segmented volume does not allow SUV Peak calculation', + type: 'warning', + }); + } + }; + + eventTarget.addEventListenerDebounced(Enums.Events.SEGMENTATION_DATA_MODIFIED, callback, 250); + + return () => { + eventTarget.removeEventListenerDebounced(Enums.Events.SEGMENTATION_DATA_MODIFIED, callback); + }; + }, []); + + if (!activeSegmentation) { + return null; + } + + const tmtvValue = activeSegmentation.cachedStats?.tmtv?.value || null; + const config = activeSegmentation.cachedStats?.tmtv?.config || {}; + + const actions = [ + { + label: 'Export CSV', + onClick: () => { + commandsManager.runCommand('exportTMTVReportCSV', { + segmentations, + tmtv: tmtvValue, + config, + }); + }, + disabled: tmtvValue === null, + }, + { + label: 'Export RT Report', + onClick: () => { + commandsManager.runCommand('createTMTVRTReport'); + }, + disabled: tmtvValue === null, + }, + ]; + + return ( + <> +
+
+ {tmtvValue !== null ? ( +
+ + {'TMTV:'} + +
{`${tmtvValue} mL`}
+
+ ) : null} +
+ +
+
+
+
{ + // navigate to a url in a new tab + window.open('https://github.com/OHIF/Viewers/blob/master/modes/tmtv/README.md', '_blank'); + }} + > + + {'User Guide'} +
+ + ); +} + +PanelRoiThresholdSegmentation.propTypes = { + commandsManager: PropTypes.shape({ + runCommand: PropTypes.func.isRequired, + }), + servicesManager: PropTypes.shape({ + services: PropTypes.shape({ + segmentationService: PropTypes.shape({ + getSegmentation: PropTypes.func.isRequired, + getSegmentations: PropTypes.func.isRequired, + toggleSegmentationVisibility: PropTypes.func.isRequired, + subscribe: PropTypes.func.isRequired, + EVENTS: PropTypes.object.isRequired, + }).isRequired, + }).isRequired, + }).isRequired, +}; diff --git a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdSegmentation.tsx b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdSegmentation.tsx deleted file mode 100644 index 306c39c94..000000000 --- a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdSegmentation.tsx +++ /dev/null @@ -1,287 +0,0 @@ -import React, { useEffect, useState, useCallback, useReducer } from 'react'; -import PropTypes from 'prop-types'; -import { SegmentationTable, Button, Icon } from '@ohif/ui'; - -import { useTranslation } from 'react-i18next'; -import segmentationEditHandler from './segmentationEditHandler'; -import ExportReports from './ExportReports'; -import ROIThresholdConfiguration, { ROI_STAT } from './ROIThresholdConfiguration'; - -const LOWER_CT_THRESHOLD_DEFAULT = -1024; -const UPPER_CT_THRESHOLD_DEFAULT = 1024; -const LOWER_PT_THRESHOLD_DEFAULT = 2.5; -const UPPER_PT_THRESHOLD_DEFAULT = 100; -const WEIGHT_DEFAULT = 0.41; // a default weight for suv max often used in the literature -const DEFAULT_STRATEGY = ROI_STAT; - -function reducer(state, action) { - const { payload } = action; - const { strategy, ctLower, ctUpper, ptLower, ptUpper, weight } = payload; - - switch (action.type) { - case 'setStrategy': - return { - ...state, - strategy, - }; - case 'setThreshold': - return { - ...state, - ctLower: ctLower ? ctLower : state.ctLower, - ctUpper: ctUpper ? ctUpper : state.ctUpper, - ptLower: ptLower ? ptLower : state.ptLower, - ptUpper: ptUpper ? ptUpper : state.ptUpper, - }; - case 'setWeight': - return { - ...state, - weight, - }; - default: - return state; - } -} - -export default function PanelRoiThresholdSegmentation({ servicesManager, commandsManager }) { - const { segmentationService } = servicesManager.services; - - const { t } = useTranslation('PanelSUV'); - const [showConfig, setShowConfig] = useState(false); - const [labelmapLoading, setLabelmapLoading] = useState(false); - const [selectedSegmentationId, setSelectedSegmentationId] = useState(null); - const [segmentations, setSegmentations] = useState(() => segmentationService.getSegmentations()); - - const [config, dispatch] = useReducer(reducer, { - strategy: DEFAULT_STRATEGY, - ctLower: LOWER_CT_THRESHOLD_DEFAULT, - ctUpper: UPPER_CT_THRESHOLD_DEFAULT, - ptLower: LOWER_PT_THRESHOLD_DEFAULT, - ptUpper: UPPER_PT_THRESHOLD_DEFAULT, - weight: WEIGHT_DEFAULT, - }); - - const [tmtvValue, setTmtvValue] = useState(null); - - const runCommand = useCallback( - (commandName, commandOptions = {}) => { - return commandsManager.runCommand(commandName, commandOptions); - }, - [commandsManager] - ); - - const handleTMTVCalculation = useCallback(() => { - const tmtv = runCommand('calculateTMTV', { segmentations }); - - if (tmtv !== undefined) { - setTmtvValue(tmtv.toFixed(2)); - } - }, [segmentations, runCommand]); - - const handleROIThresholding = useCallback(() => { - const labelmap = runCommand('thresholdSegmentationByRectangleROITool', { - segmentationId: selectedSegmentationId, - config, - }); - - const lesionStats = runCommand('getLesionStats', { labelmap }); - const suvPeak = runCommand('calculateSuvPeak', { labelmap }); - const lesionGlyoclysisStats = lesionStats.volume * lesionStats.meanValue; - - // update segDetails with the suv peak for the active segmentation - const segmentation = segmentationService.getSegmentation(selectedSegmentationId); - - const cachedStats = { - lesionStats, - suvPeak, - lesionGlyoclysisStats, - }; - - const notYetUpdatedAtSource = true; - segmentationService.addOrUpdateSegmentation( - { - ...segmentation, - ...Object.assign(segmentation.cachedStats, cachedStats), - displayText: [`SUV Peak: ${suvPeak.suvPeak.toFixed(2)}`], - }, - notYetUpdatedAtSource - ); - - handleTMTVCalculation(); - }, [selectedSegmentationId, config]); - - /** - * Update UI based on segmentation changes (added, removed, updated) - */ - useEffect(() => { - // ~~ Subscription - const added = segmentationService.EVENTS.SEGMENTATION_ADDED; - const updated = segmentationService.EVENTS.SEGMENTATION_UPDATED; - const subscriptions = []; - - [added, updated].forEach(evt => { - const { unsubscribe } = segmentationService.subscribe(evt, () => { - const segmentations = segmentationService.getSegmentations(); - setSegmentations(segmentations); - }); - subscriptions.push(unsubscribe); - }); - - return () => { - subscriptions.forEach(unsub => { - unsub(); - }); - }; - }, []); - - useEffect(() => { - const { unsubscribe } = segmentationService.subscribe( - segmentationService.EVENTS.SEGMENTATION_REMOVED, - () => { - const segmentations = segmentationService.getSegmentations(); - setSegmentations(segmentations); - - if (segmentations.length > 0) { - setSelectedSegmentationId(segmentations[0].id); - handleTMTVCalculation(); - } else { - setSelectedSegmentationId(null); - setTmtvValue(null); - } - } - ); - - return () => { - unsubscribe(); - }; - }, []); - - /** - * Whenever the segmentations change, update the TMTV calculations - */ - useEffect(() => { - if (!selectedSegmentationId && segmentations.length > 0) { - setSelectedSegmentationId(segmentations[0].id); - } - - handleTMTVCalculation(); - }, [segmentations, selectedSegmentationId]); - - return ( - <> -
-
-
- - -
-
{ - setShowConfig(!showConfig); - }} - > -
{t('ROI Threshold Configuration')}
-
- {showConfig && ( - - )} - {/* show segmentation table */} -
- {segmentations?.length ? ( - { - runCommand('setSegmentationActiveForToolGroups', { - segmentationId: id, - }); - setSelectedSegmentationId(id); - }} - onToggleVisibility={id => { - segmentationService.toggleSegmentationVisibility(id); - }} - onToggleVisibilityAll={ids => { - ids.map(id => { - segmentationService.toggleSegmentationVisibility(id); - }); - }} - onDelete={id => { - segmentationService.remove(id); - }} - onEdit={id => { - segmentationEditHandler({ - id, - servicesManager, - }); - }} - /> - ) : null} -
- {tmtvValue !== null ? ( -
- - {'TMTV:'} - -
{`${tmtvValue} mL`}
-
- ) : null} - -
-
-
{ - // navigate to a url in a new tab - window.open('https://github.com/OHIF/Viewers/blob/master/modes/tmtv/README.md', '_blank'); - }} - > - - {'User Guide'} -
- - ); -} - -PanelRoiThresholdSegmentation.propTypes = { - commandsManager: PropTypes.shape({ - runCommand: PropTypes.func.isRequired, - }), - servicesManager: PropTypes.shape({ - services: PropTypes.shape({ - segmentationService: PropTypes.shape({ - getSegmentation: PropTypes.func.isRequired, - getSegmentations: PropTypes.func.isRequired, - toggleSegmentationVisibility: PropTypes.func.isRequired, - subscribe: PropTypes.func.isRequired, - EVENTS: PropTypes.object.isRequired, - }).isRequired, - }).isRequired, - }).isRequired, -}; diff --git a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ROIThresholdConfiguration.tsx b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ROIThresholdConfiguration.tsx index 274ae720d..1f650b1c2 100644 --- a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ROIThresholdConfiguration.tsx +++ b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ROIThresholdConfiguration.tsx @@ -14,7 +14,7 @@ function ROIThresholdConfiguration({ config, dispatch, runCommand }) { const { t } = useTranslation('ROIThresholdConfiguration'); return ( -
+
= ({ @@ -36,6 +37,7 @@ const InputDoubleRange: React.FC = ({ labelPosition, trackColor, allowNumberEdit, + allowOutOfRange = false, showAdjustmentArrows, }) => { // Set initial thumb positions as percentages @@ -55,7 +57,15 @@ const InputDoubleRange: React.FC = ({ updatedRangeValue[index] = newValues; } - const calculatePercentage = value => ((value - minValue) / (maxValue - minValue)) * 100; + const calculatePercentage = value => { + if (value < minValue) { + return 0; + } + if (value > maxValue) { + return 100; + } + return ((value - minValue) / (maxValue - minValue)) * 100; + }; const newPercentageStart = calculatePercentage(updatedRangeValue[0]); const newPercentageEnd = calculatePercentage(updatedRangeValue[1]); @@ -73,17 +83,22 @@ const InputDoubleRange: React.FC = ({ const LabelOrEditableNumber = (val, index) => { return allowNumberEdit ? ( - { - updateRangeValues(newValue, index); - }} - step={step} - labelClassName="text-white" - showAdjustmentArrows={showAdjustmentArrows} - /> + // the pl-[2px] class is used to align the thumb so that it doesn't + // go over the label when the value is full, not sure what is wrong + // with the implementation, we need to fix it properly +
+ { + updateRangeValues(newValue, index); + }} + step={step} + labelClassName={classNames(labelClassName ?? 'text-white')} + showAdjustmentArrows={showAdjustmentArrows} + /> +
) : ( {val} @@ -138,31 +153,52 @@ const InputDoubleRange: React.FC = ({ const newValue = Math.round(((x / rect.width) * (maxValue - minValue) + minValue) / step) * step; - // Make sure newValue is within [minValue, maxValue] - const clampedValue = Math.min(Math.max(newValue, minValue), maxValue); + if (!allowOutOfRange) { + const clampedValue = Math.min(Math.max(newValue, minValue), maxValue); - // Ensure that left and right thumbs don't switch positions - if (selectedThumbValue === 0 && clampedValue >= rangeValue[1]) { - return; - } - if (selectedThumbValue === 1 && clampedValue <= rangeValue[0]) { - return; + const updatedRangeValue = [...rangeValue]; + updatedRangeValue[selectedThumbValue] = clampedValue; + setRangeValue(updatedRangeValue); + + onChange(updatedRangeValue); + + const percentage = Math.round(((clampedValue - minValue) / (maxValue - minValue)) * 100); + if (selectedThumbValue === 0) { + setPercentageStart(percentage); + } else { + setPercentageEnd(percentage); + } + } else { + const updatedRangeValue = [...rangeValue]; + updatedRangeValue[selectedThumbValue] = newValue; + setRangeValue(updatedRangeValue); + + onChange(updatedRangeValue); + + // Update the thumb position + const percentage = Math.round(((newValue - minValue) / (maxValue - minValue)) * 100); + if (percentage < 0) { + if (selectedThumbValue === 0) { + setPercentageStart(0); + } else { + setPercentageEnd(0); + } + } else if (percentage > 100) { + if (selectedThumbValue === 0) { + setPercentageStart(100); + } else { + setPercentageEnd(100); + } + } else { + if (selectedThumbValue === 0) { + setPercentageStart(percentage); + } else { + setPercentageEnd(percentage); + } + } } // Update the correct values in the rangeValue array - const updatedRangeValue = [...rangeValue]; - updatedRangeValue[selectedThumbValue] = clampedValue; - setRangeValue(updatedRangeValue); - - onChange(updatedRangeValue); - - // Update the thumb position - const percentage = Math.round(((clampedValue - minValue) / (maxValue - minValue)) * 100); - if (selectedThumbValue === 0) { - setPercentageStart(percentage); - } else { - setPercentageEnd(percentage); - } }; // Calculate the range values percentages for gradient background diff --git a/platform/ui/src/components/InputLabelWrapper/InputLabelWrapper.tsx b/platform/ui/src/components/InputLabelWrapper/InputLabelWrapper.tsx index 73172da58..904b06904 100644 --- a/platform/ui/src/components/InputLabelWrapper/InputLabelWrapper.tsx +++ b/platform/ui/src/components/InputLabelWrapper/InputLabelWrapper.tsx @@ -1,7 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import { useTranslation } from 'react-i18next'; import Icon from '../Icon'; @@ -21,8 +20,6 @@ const InputLabelWrapper = ({ className, children, }) => { - const { t } = useTranslation('StudyList'); - const onClickHandler = e => { if (!isSortable) { return; @@ -40,7 +37,7 @@ const InputLabelWrapper = ({ onKeyDown={onClickHandler} tabIndex="0" > - {t(label)} + {label} {isSortable && ( = ({ value, onChange, @@ -38,19 +47,24 @@ const InputNumber: React.FC<{ size = 'sm', minValue = 0, maxValue = 100, - labelClassName, + labelClassName = 'text-aqua-pale text-[11px] mx-auto', label, showAdjustmentArrows = true, + arrowsDirection = 'vertical', + labelPosition = 'left', + inputClassName = 'text-white bg-primary-dark text-[14px]', + sizeClassName, + inputContainerClassName = 'bg-primary-dark border-secondary-light border rounded-[4px]', }) => { const [numberValue, setNumberValue] = useState(value); const [isFocused, setIsFocused] = useState(false); const maxDigits = getMaxDigits(maxValue, step); const inputWidth = Math.max(maxDigits * 10, showAdjustmentArrows ? 20 : 28); - const arrowWidth = showAdjustmentArrows ? 20 : 0; - const containerWidth = `${inputWidth + arrowWidth}px`; const decimalPlaces = Number.isInteger(step) ? 0 : step.toString().split('.')[1].length; + const sizeToUse = sizeClassName ? sizeClassName : sizesClasses[size]; + useEffect(() => { setNumberValue(value); }, [value]); @@ -95,21 +109,31 @@ const InputNumber: React.FC<{ const increment = () => updateValue(parseFloat(numberValue) + step); const decrement = () => updateValue(parseFloat(numberValue) - step); + const labelElement = label && ( +