From 0e1a58da79cf0f14e71a3bcfcb726eed6ba2dfb4 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Mon, 6 Feb 2023 17:05:13 -0500 Subject: [PATCH] fix: Use @cornerstonejs/adapters (#3116) Also update the sort series to be consistent, using a single version of the series sort, and fix an issue with longitudinal mode export. --- .../src/commandsModule.js | 3 +- .../src/getSopClassHandlerModule.js | 4 +- .../utils/getFilteredCornerstoneToolState.ts | 2 +- .../src/utils/hydrateStructuredReport.js | 4 +- .../src/utils/isRehydratable.js | 4 +- .../src/utils/isToolSupported.js | 4 +- .../viewports/OHIFCornerstoneSRViewport.tsx | 13 ++++- extensions/cornerstone/package.json | 1 + .../default/src/DicomLocalDataSource/index.js | 38 +++++++++++---- .../default/src/DicomWebDataSource/qido.js | 3 ++ .../wado/retrieveMetadataLoaderAsync.js | 5 +- .../default/src/Panels/PanelStudyBrowser.tsx | 4 +- platform/core/src/utils/index.js | 15 +++++- platform/core/src/utils/index.test.js | 5 ++ .../core/src/utils/isLowPriorityModality.ts | 0 platform/core/src/utils/sortBySeriesDate.js | 17 ------- .../core/src/utils/sortStudy.ts | 47 +++++++++++++++---- yarn.lock | 25 ++++++++++ 18 files changed, 141 insertions(+), 53 deletions(-) rename extensions/default/src/DicomWebDataSource/utils/isLowPriorityModality.js => platform/core/src/utils/isLowPriorityModality.ts (100%) delete mode 100644 platform/core/src/utils/sortBySeriesDate.js rename extensions/default/src/DicomWebDataSource/utils/sortStudy.js => platform/core/src/utils/sortStudy.ts (70%) diff --git a/extensions/cornerstone-dicom-sr/src/commandsModule.js b/extensions/cornerstone-dicom-sr/src/commandsModule.js index d9c18cfc1..00431a3c8 100644 --- a/extensions/cornerstone-dicom-sr/src/commandsModule.js +++ b/extensions/cornerstone-dicom-sr/src/commandsModule.js @@ -2,10 +2,11 @@ import { metaData, utilities } from '@cornerstonejs/core'; import OHIF from '@ohif/core'; import dcmjs from 'dcmjs'; +import { adaptersSR } from '@cornerstonejs/adapters'; import getFilteredCornerstoneToolState from './utils/getFilteredCornerstoneToolState'; -const { MeasurementReport } = dcmjs.adapters.Cornerstone3D; +const { MeasurementReport } = adaptersSR.Cornerstone3D; const { log } = OHIF; /** diff --git a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js index f92f56460..7d40d11b6 100644 --- a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js +++ b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js @@ -2,9 +2,9 @@ import { SOPClassHandlerName, SOPClassHandlerId } from './id'; import { utils, classes } from '@ohif/core'; import addMeasurement from './utils/addMeasurement'; import isRehydratable from './utils/isRehydratable'; -import { adapters } from 'dcmjs'; +import { adaptersSR } from '@cornerstonejs/adapters'; -const { CodeScheme: Cornerstone3DCodeScheme } = adapters.Cornerstone3D; +const { CodeScheme: Cornerstone3DCodeScheme } = adaptersSR.Cornerstone3D; const { ImageSet, MetadataProvider: metadataProvider } = classes; // TODO -> diff --git a/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts b/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts index 6dcecdd88..1ef1842a5 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts @@ -39,7 +39,7 @@ function getFilteredCornerstoneToolState( const findingSites = []; // NOTE -> We use the CORNERSTONEJS coding schemeDesignator which we have - // defined in the dcmjs adapters + // defined in the @cornerstonejs/adapters if (measurementDataI.label) { if (additionalFindingTypes.includes(toolType)) { finding = { diff --git a/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js b/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js index 0ca978028..758e666a0 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js +++ b/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js @@ -1,10 +1,10 @@ import { utilities, metaData } from '@cornerstonejs/core'; import OHIF, { DicomMetadataStore } from '@ohif/core'; import getLabelFromDCMJSImportedToolData from './getLabelFromDCMJSImportedToolData'; -import { adapters } from 'dcmjs'; +import { adaptersSR } from '@cornerstonejs/adapters'; const { guid } = OHIF.utils; -const { MeasurementReport, CORNERSTONE_3D_TAG } = adapters.Cornerstone3D; +const { MeasurementReport, CORNERSTONE_3D_TAG } = adaptersSR.Cornerstone3D; const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools'; const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1'; diff --git a/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js b/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js index 44af7d276..b167e0a8e 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js +++ b/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js @@ -1,6 +1,6 @@ -import { adapters } from 'dcmjs'; +import { adaptersSR } from '@cornerstonejs/adapters'; -const cornerstoneAdapters = adapters.Cornerstone3D; +const cornerstoneAdapters = adaptersSR.Cornerstone3D; const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0']; const CORNERSTONE_3D_TAG = cornerstoneAdapters.CORNERSTONE_3D_TAG; diff --git a/extensions/cornerstone-dicom-sr/src/utils/isToolSupported.js b/extensions/cornerstone-dicom-sr/src/utils/isToolSupported.js index 4e455c0d6..cdbb2e725 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/isToolSupported.js +++ b/extensions/cornerstone-dicom-sr/src/utils/isToolSupported.js @@ -1,4 +1,4 @@ -import dcmjs from 'dcmjs'; +import { adaptersSR } from '@cornerstonejs/adapters'; /** * Checks if dcmjs has support to determined tool @@ -7,7 +7,7 @@ import dcmjs from 'dcmjs'; * @returns {boolean} */ const isToolSupported = toolName => { - const adapter = dcmjs.adapters.Cornerstone3D; + const adapter = adaptersSR.Cornerstone3D; return !!adapter[toolName]; }; diff --git a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx b/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx index bff4dba42..e17d7dee7 100644 --- a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx +++ b/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx @@ -81,14 +81,23 @@ function OHIFCornerstoneSRViewport(props) { sendTrackedMeasurementsEvent = tracked?.[1]; } if (!sendTrackedMeasurementsEvent) { - // if no panels from measurement-tracking extension is used, this code will trun + // if no panels from measurement-tracking extension is used, this code will run trackedMeasurements = null; sendTrackedMeasurementsEvent = (eventName, { displaySetInstanceUID }) => { MeasurementService.clearMeasurements(); - hydrateStructuredReport( + const { SeriesInstanceUIDs } = hydrateStructuredReport( { servicesManager, extensionManager }, displaySetInstanceUID ); + const displaySets = DisplaySetService.getDisplaySetsForSeries( + SeriesInstanceUIDs[0] + ); + if (displaySets.length) { + viewportGridService.setDisplaySetsForViewport({ + viewportIndex: activeViewportIndex, + displaySetInstanceUIDs: [displaySets[0].displaySetInstanceUID], + }); + } }; } diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index 414159a3d..651efa898 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -43,6 +43,7 @@ }, "dependencies": { "@babel/runtime": "7.17.9", + "@cornerstonejs/adapters": "^0.2.1", "@cornerstonejs/core": "^0.27.1", "@cornerstonejs/streaming-image-volume-loader": "^0.11.2", "@cornerstonejs/tools": "^0.36.3", diff --git a/extensions/default/src/DicomLocalDataSource/index.js b/extensions/default/src/DicomLocalDataSource/index.js index 1e29238bf..fc6f366bb 100644 --- a/extensions/default/src/DicomLocalDataSource/index.js +++ b/extensions/default/src/DicomLocalDataSource/index.js @@ -5,19 +5,37 @@ import dcmjs from 'dcmjs'; const metadataProvider = OHIF.classes.MetadataProvider; const { EVENTS } = DicomMetadataStore; -// Sorting SR modalities to be at the end of series list -function customSort(seriesA, seriesB) { - const modalityA = seriesA.instances[0].Modality; - const modalityB = seriesB.instances[0].Modality; +const END_MODALITIES = { + SR: true, + SEG: true, + DOC: true, +}; - if (modalityA === 'SR') { - return +1; +const compareValue = (v1, v2, def = 0) => { + if (v1 === v2) return def; + if (v1 < v2) return -1; + return 1; +}; + +// Sorting SR modalities to be at the end of series list +const customSort = (seriesA, seriesB) => { + const instanceA = seriesA.instances[0]; + const instanceB = seriesB.instances[0]; + const modalityA = instanceA.Modality; + const modalityB = instanceB.Modality; + + const isEndA = END_MODALITIES[modalityA]; + const isEndB = END_MODALITIES[modalityB]; + + if (isEndA && isEndB) { + // Compare by series date + return compareValue(instanceA.SeriesNumber, instanceB.SeriesNumber); } - if (modalityB === 'SR') { - return -1; + if (!isEndA && !isEndB) { + return compareValue(instanceB.SeriesNumber, instanceA.SeriesNumber); } - return 0; -} + return isEndA ? -1 : 1; +}; function createDicomLocalApi(dicomLocalConfig) { const { name } = dicomLocalConfig; diff --git a/extensions/default/src/DicomWebDataSource/qido.js b/extensions/default/src/DicomWebDataSource/qido.js index 2ac8b1fcf..ce372cd5f 100644 --- a/extensions/default/src/DicomWebDataSource/qido.js +++ b/extensions/default/src/DicomWebDataSource/qido.js @@ -23,6 +23,7 @@ * | offset | {number} | */ import { DICOMWeb, utils } from '@ohif/core'; +import { sortStudySeries } from '@ohif/core/src/utils/sortStudy'; const { getString, getName, getModalities } = DICOMWeb; @@ -90,6 +91,8 @@ export function processSeriesResults(qidoSeries) { ); } + sortStudySeries(series); + return series; } diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js index 3d7294edf..096c3c818 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js @@ -1,5 +1,8 @@ import dcmjs from 'dcmjs'; -import { sortStudySeries, sortingCriteria } from '../utils/sortStudy'; +import { + sortStudySeries, + sortingCriteria, +} from '@ohif/core/src/utils/sortStudy'; import RetrieveMetadataLoader from './retrieveMetadataLoader'; /** diff --git a/extensions/default/src/Panels/PanelStudyBrowser.tsx b/extensions/default/src/Panels/PanelStudyBrowser.tsx index 49f84f751..0da795044 100644 --- a/extensions/default/src/Panels/PanelStudyBrowser.tsx +++ b/extensions/default/src/Panels/PanelStudyBrowser.tsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { StudyBrowser, useImageViewer, useViewportGrid } from '@ohif/ui'; import { utils } from '@ohif/core'; -const { formatDate } = utils; +const { sortStudyInstances, formatDate } = utils; /** * @@ -139,6 +139,7 @@ function PanelStudyBrowser({ currentDisplaySets, thumbnailImageSrcMap ); + sortStudyInstances(mappedDisplaySets); setDisplaySets(mappedDisplaySets); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -300,6 +301,7 @@ function _mapDisplaySets(displaySets, thumbnailImageSrcMap) { seriesNumber: ds.SeriesNumber, modality: ds.Modality, seriesDate: ds.SeriesDate, + seriesTime: ds.SeriesTime, numInstances: ds.numImageFrames, StudyInstanceUID: ds.StudyInstanceUID, componentType, diff --git a/platform/core/src/utils/index.js b/platform/core/src/utils/index.js index cc8124158..d428a6648 100644 --- a/platform/core/src/utils/index.js +++ b/platform/core/src/utils/index.js @@ -2,7 +2,6 @@ import ObjectPath from './objectPath'; import absoluteUrl from './absoluteUrl'; import guid from './guid'; import sortBy from './sortBy.js'; -import sortBySeriesDate from './sortBySeriesDate.js'; import writeScript from './writeScript.js'; import b64toBlob from './b64toBlob.js'; //import loadAndCacheDerivedDisplaySets from './loadAndCacheDerivedDisplaySets.js'; @@ -26,6 +25,13 @@ import debounce from './debounce'; import roundNumber from './roundNumber'; import downloadCSVReport from './downloadCSVReport'; import isEqualWithin from './isEqualWithin'; +import { + sortStudy, + sortStudySeries, + sortStudyInstances, + sortingCriteria, + seriesSortCriteria, +} from './sortStudy'; // Commented out unused functionality. // Need to implement new mechanism for derived displaySets using the displaySetManager. @@ -35,7 +41,12 @@ const utils = { ObjectPath, absoluteUrl, sortBy, - sortBySeriesDate, + sortBySeriesDate: sortStudySeries, + sortStudy, + sortStudySeries, + sortStudyInstances, + sortingCriteria, + seriesSortCriteria, writeScript, formatDate, formatPN, diff --git a/platform/core/src/utils/index.test.js b/platform/core/src/utils/index.test.js index 5fdcab61f..9b83f8db5 100644 --- a/platform/core/src/utils/index.test.js +++ b/platform/core/src/utils/index.test.js @@ -6,8 +6,13 @@ describe('Top level exports', () => { 'guid', 'ObjectPath', 'absoluteUrl', + 'seriesSortCriteria', 'sortBy', + 'sortStudy', 'sortBySeriesDate', + 'sortStudyInstances', + 'sortStudySeries', + 'sortingCriteria', 'isLowPriorityModality', 'writeScript', 'debounce', diff --git a/extensions/default/src/DicomWebDataSource/utils/isLowPriorityModality.js b/platform/core/src/utils/isLowPriorityModality.ts similarity index 100% rename from extensions/default/src/DicomWebDataSource/utils/isLowPriorityModality.js rename to platform/core/src/utils/isLowPriorityModality.ts diff --git a/platform/core/src/utils/sortBySeriesDate.js b/platform/core/src/utils/sortBySeriesDate.js deleted file mode 100644 index 1a694934c..000000000 --- a/platform/core/src/utils/sortBySeriesDate.js +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Sorting function - * Sorts an array by seriesDate and seriesNumber if equal - */ -const sortBySeriesDate = array => - array.sort((a, b) => { - if (a.seriesNumber !== b.seriesNumber) { - return a.seriesNumber - b.seriesNumber; - } - - const seriesDateA = Date.parse(a.seriesDate); - const seriesDateB = Date.parse(b.seriesDate); - - return seriesDateA - seriesDateB; - }); - -export default sortBySeriesDate; diff --git a/extensions/default/src/DicomWebDataSource/utils/sortStudy.js b/platform/core/src/utils/sortStudy.ts similarity index 70% rename from extensions/default/src/DicomWebDataSource/utils/sortStudy.js rename to platform/core/src/utils/sortStudy.ts index 467d6fcc7..a39c8a16a 100644 --- a/extensions/default/src/DicomWebDataSource/utils/sortStudy.js +++ b/platform/core/src/utils/sortStudy.ts @@ -1,5 +1,22 @@ import isLowPriorityModality from './isLowPriorityModality'; +const compareSeriesDateTime = (a, b) => { + const seriesDateA = Date.parse( + `${a.seriesDate ?? a.SeriesDate} ${a.seriesTime ?? a.SeriesTime}` + ); + const seriesDateB = Date.parse( + `${a.seriesDate ?? a.SeriesDate} ${a.seriesTime ?? a.SeriesTime}` + ); + return seriesDateA - seriesDateB; +}; + +const defaultSeriesSort = (a, b) => { + const seriesNumberA = a.SeriesNumber ?? a.seriesNumber; + const seriesNumberB = b.SeriesNumber ?? b.seriesNumber; + if (seriesNumberA === seriesNumberB) return compareSeriesDateTime(a, b); + return seriesNumberA - seriesNumberB; +}; + /** * Series sorting criteria: series considered low priority are moved to the end * of the list and series number is used to break ties @@ -7,20 +24,24 @@ import isLowPriorityModality from './isLowPriorityModality'; * @param {Object} secondSeries */ function seriesInfoSortingCriteria(firstSeries, secondSeries) { - const aLowPriority = isLowPriorityModality(firstSeries.Modality); - const bLowPriority = isLowPriorityModality(secondSeries.Modality); - if (!aLowPriority && bLowPriority) { + const aLowPriority = isLowPriorityModality( + firstSeries.Modality ?? firstSeries.modality + ); + const bLowPriority = isLowPriorityModality( + secondSeries.Modality ?? secondSeries.modality + ); + + if (aLowPriority) { + return bLowPriority ? defaultSeriesSort(secondSeries, firstSeries) : 1; + } else if (bLowPriority) { return -1; } - if (aLowPriority && !bLowPriority) { - return 1; - } - return firstSeries.SeriesNumber - secondSeries.SeriesNumber; + return defaultSeriesSort(firstSeries, secondSeries); } const seriesSortCriteria = { - default: (a, b) => a.SeriesNumber - b.SeriesNumber, + default: seriesInfoSortingCriteria, seriesInfoSortingCriteria, }; @@ -44,7 +65,7 @@ const sortingCriteria = { const sortStudySeries = ( series, seriesSortingCriteria = seriesSortCriteria.default, - sortFunction + sortFunction = null ) => { if (typeof sortFunction === 'function') return sortFunction(series); else return series.sort(seriesSortingCriteria); @@ -96,4 +117,10 @@ export default function sortStudy( return study; } -export { sortStudy, sortStudySeries, sortStudyInstances, sortingCriteria }; +export { + sortStudy, + sortStudySeries, + sortStudyInstances, + sortingCriteria, + seriesSortCriteria, +}; diff --git a/yarn.lock b/yarn.lock index 7ed8045a8..f91f3a726 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2372,6 +2372,19 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== +"@cornerstonejs/adapters@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-0.2.1.tgz#c1de369d82e05a4ff58dce25dc441e870b0ef991" + integrity sha512-5oHHwpSR9sjRh3GBsHh3sBF8kiWbgAdNiVf1nWc7dc3PtemWl+BiPr7xLE0/9aRe0BCRkgyU/u/Yqgk7I/pZLg== + dependencies: + "@babel/runtime-corejs2" "^7.17.8" + dcmjs "^0.29.4" + gl-matrix "^3.4.3" + lodash.clonedeep "^4.5.0" + loglevelnext "^3.0.1" + ndarray "^1.0.19" + pako "^2.0.4" + "@cornerstonejs/calculate-suv@^1.0.3": version "1.0.3" resolved "https://registry.npmjs.org/@cornerstonejs/calculate-suv/-/calculate-suv-1.0.3.tgz#6d99a72032c0f90cebf44dc6f0b12a5f1102e884" @@ -10262,6 +10275,18 @@ dcmjs@^0.28.3: ndarray "^1.0.19" pako "^2.0.4" +dcmjs@^0.29.4: + version "0.29.4" + resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.29.4.tgz#3fb13945611979f756bc91b3f199fd87b4eabdb4" + integrity sha512-PzD6C4oB7v/AVTIPbkS6D6hjgVcSW7+T+DsxFpkD1xcEgU4qjGM16WGEHH3ZAPDBF0Xgm2d9FXUu/lXZIyfmow== + dependencies: + "@babel/runtime-corejs2" "^7.17.8" + gl-matrix "^3.1.0" + lodash.clonedeep "^4.5.0" + loglevelnext "^3.0.1" + ndarray "^1.0.19" + pako "^2.0.4" + debug-log@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f"