From cedeb756c0f6b231a7a329ba06605c612a332771 Mon Sep 17 00:00:00 2001 From: Davide Punzo Date: Wed, 6 Apr 2022 17:10:00 +0200 Subject: [PATCH] Re IDC #2762: fix thumbnail series type for unsupported SOPClassUID (#2770) --- .../src/components/DicomTagBrowser.js | 2 +- .../src/classes/metadata/StudyMetadata.js | 17 +- .../core/src/utils/naturalizeSOPClassUID.js | 200 ++++++++++++++++++ .../components/studyBrowser/Thumbnail.styl | 8 +- platform/viewer/public/config/default.js | 1 + .../ConnectedStudyBrowser.js | 2 +- .../viewer/src/connectedComponents/Viewer.js | 6 +- .../src/connectedComponents/ViewerMain.js | 2 +- 8 files changed, 227 insertions(+), 11 deletions(-) create mode 100644 platform/core/src/utils/naturalizeSOPClassUID.js diff --git a/extensions/dicom-tag-browser/src/components/DicomTagBrowser.js b/extensions/dicom-tag-browser/src/components/DicomTagBrowser.js index 9be272c90..1664799e1 100644 --- a/extensions/dicom-tag-browser/src/components/DicomTagBrowser.js +++ b/extensions/dicom-tag-browser/src/components/DicomTagBrowser.js @@ -59,7 +59,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => { let metadata; const isImageStack = activeDisplaySet instanceof ImageSet && - activeDisplaySet.isModalitySupported === true; + activeDisplaySet.isSOPClassUIDSupported === true; let instanceList; diff --git a/platform/core/src/classes/metadata/StudyMetadata.js b/platform/core/src/classes/metadata/StudyMetadata.js index 2789bcdf2..811e7fcd8 100644 --- a/platform/core/src/classes/metadata/StudyMetadata.js +++ b/platform/core/src/classes/metadata/StudyMetadata.js @@ -3,12 +3,12 @@ import DICOMWeb from './../../DICOMWeb'; import ImageSet from './../ImageSet'; import { InstanceMetadata } from './InstanceMetadata'; import { Metadata } from './Metadata'; -import OHIFError from '../OHIFError'; import { SeriesMetadata } from './SeriesMetadata'; // - createStacks import { api } from 'dicomweb-client'; // - createStacks import { isImage } from '../../utils/isImage'; +import { naturalizeSOPClassUID } from '../../utils/naturalizeSOPClassUID'; import { isDisplaySetReconstructable, isSpacingUniform, @@ -169,8 +169,12 @@ class StudyMetadata extends Metadata { !isImage(instance.getTagValue('SOPClassUID')) && !instance.getTagValue('Rows') ) { - // we set an empty display and we add a isModalitySupported variable to - // print a warning that the modality is not supported in the thumbnail. + // we set an empty display and we add a isSOPClassUIDSupported variable to + // print a warning that the series is not supported in the thumbnail. + // SOPClassUIDNaturalized is human readable name, since for non image series, + // we could have a mismatch between the SOPClassUID and the Modality. + // For example, in the Parametric map IOD Modality is expected to match + // the value for the series used to generate Parametric map, and there is no "PM" modality. const displaySet = new ImageSet([]); const seriesData = series.getData(); displaySet.setAttributes({ @@ -188,7 +192,10 @@ class StudyMetadata extends Metadata { InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime isReconstructable: false, - isModalitySupported: false, + isSOPClassUIDSupported: false, + SOPClassUIDNaturalized: naturalizeSOPClassUID( + instance.getTagValue('SOPClassUID') + ), metadata: instance.getData().metadata, }); @@ -923,7 +930,7 @@ const makeDisplaySet = (series, instances) => { : displayReconstructableInfo.reconstructionIssues; } - imageSet.isModalitySupported = true; + imageSet.isSOPClassUIDSupported = true; return imageSet; }; diff --git a/platform/core/src/utils/naturalizeSOPClassUID.js b/platform/core/src/utils/naturalizeSOPClassUID.js new file mode 100644 index 000000000..0c3b6977b --- /dev/null +++ b/platform/core/src/utils/naturalizeSOPClassUID.js @@ -0,0 +1,200 @@ +import { sopClassDictionary } from './sopClassDictionary'; +import { isImage } from './isImage'; + +/** + * Naturalize SOP Class UID which do not have image data + * @param {string} SOPClassUID - SOP Class UID to be converted + * @returns {string} - human readable name + */ +export const naturalizeSOPClassUID = SOPClassUID => { + let naturalizedName = ''; + if (!SOPClassUID) return naturalizedName; + if (!isImage) return naturalizedName; + + if (sopClassDictionary.MRSpectroscopyStorage === SOPClassUID) { + naturalizedName = 'MRSpectroscopy'; + } else if (sopClassDictionary.EnhancedUSVolumeStorage === SOPClassUID) { + naturalizedName = 'EnhancedUSVolume'; + } else if (sopClassDictionary.Sop12LeadECGWaveformStorage === SOPClassUID) { + naturalizedName = 'Sop12LeadECGWaveform'; + } else if (sopClassDictionary.GeneralECGWaveformStorage === SOPClassUID) { + naturalizedName = 'GeneralECGWaveform'; + } else if (sopClassDictionary.AmbulatoryECGWaveformStorage === SOPClassUID) { + naturalizedName = 'AECAmbulatoryECGWaveformGW'; + } else if (sopClassDictionary.HemodynamicWaveformStorage === SOPClassUID) { + naturalizedName = 'HemodynamicWaveform'; + } else if ( + sopClassDictionary.CardiacElectrophysiologyWaveformStorage === SOPClassUID + ) { + naturalizedName = 'CardiacElectrophysiologyWaveform'; + } else if ( + sopClassDictionary.BasicVoiceAudioWaveformStorage === SOPClassUID + ) { + naturalizedName = 'BasicVoiceAudioWaveform'; + } else if (sopClassDictionary.GeneralAudioWaveformStorage === SOPClassUID) { + naturalizedName = 'GGeneralAudioWaveformAW'; + } else if (sopClassDictionary.ArterialPulseWaveformStorage === SOPClassUID) { + naturalizedName = 'APArterialPulseWaveformW'; + } else if (sopClassDictionary.RespiratoryWaveformStorage === SOPClassUID) { + naturalizedName = 'RespiratoryWaveform'; + } else if ( + sopClassDictionary.GrayscaleSoftcopyPresentationStateStorage === SOPClassUID + ) { + naturalizedName = 'GrayscaleSoftcopyPresentationState'; + } else if ( + sopClassDictionary.ColorSoftcopyPresentationStateStorage === SOPClassUID + ) { + naturalizedName = 'ColorSoftcopyPresentationState'; + } else if ( + sopClassDictionary.PseudoColorSoftcopyPresentationStateStorage === + SOPClassUID + ) { + naturalizedName = 'PseudoColorSoftcopyPresentationState'; + } else if ( + sopClassDictionary.BlendingSoftcopyPresentationStateStorage === SOPClassUID + ) { + naturalizedName = 'BlendingSoftcopyPresentationState'; + } else if ( + sopClassDictionary.XAXRFGrayscaleSoftcopyPresentationStateStorage === + SOPClassUID + ) { + naturalizedName = 'XAXRFGrayscaleSoftcopyPresentationState'; + } else if (sopClassDictionary.RawDataStorage === SOPClassUID) { + naturalizedName = 'RawData'; + } else if (sopClassDictionary.SpatialRegistrationStorage === SOPClassUID) { + naturalizedName = 'SpatialRegistration'; + } else if (sopClassDictionary.SpatialFiducialsStorage === SOPClassUID) { + naturalizedName = 'SpatialFiducials'; + } else if ( + sopClassDictionary.DeformableSpatialRegistrationStorage === SOPClassUID + ) { + naturalizedName = 'DeformableSpatialRegistration'; + } else if (sopClassDictionary.SegmentationStorage === SOPClassUID) { + naturalizedName = 'SEG'; + } else if (sopClassDictionary.SurfaceSegmentationStorage === SOPClassUID) { + naturalizedName = 'SurfaceSEG'; + } else if (sopClassDictionary.RealWorldValueMappingStorage === SOPClassUID) { + naturalizedName = 'RealWorldValueMapping'; + } else if (sopClassDictionary.SurfaceScanMeshStorage === SOPClassUID) { + naturalizedName = 'SurfaceScanMesh'; + } else if (sopClassDictionary.SurfaceScanPointCloudStorage === SOPClassUID) { + naturalizedName = 'SurfaceScanPointCloud'; + } else if ( + sopClassDictionary.StereometricRelationshipStorage === SOPClassUID + ) { + naturalizedName = 'StereometricRelationship'; + } else if (sopClassDictionary.LensometryMeasurementsStorage === SOPClassUID) { + naturalizedName = 'LensometryMeasurements'; + } else if ( + sopClassDictionary.AutorefractionMeasurementsStorage === SOPClassUID + ) { + naturalizedName = 'AutorefractionMeasurements'; + } else if ( + sopClassDictionary.KeratometryMeasurementsStorage === SOPClassUID + ) { + naturalizedName = 'KeratometryMeasurements'; + } else if ( + sopClassDictionary.SubjectiveRefractionMeasurementsStorage === SOPClassUID + ) { + naturalizedName = 'SubjectiveRefractionMeasurements'; + } else if ( + sopClassDictionary.VisualAcuityMeasurementsStorage === SOPClassUID + ) { + naturalizedName = 'VisualAcuityMeasurements'; + } else if ( + sopClassDictionary.SpectaclePrescriptionReportStorage === SOPClassUID + ) { + naturalizedName = 'SpectaclePrescriptionReport'; + } else if ( + sopClassDictionary.OphthalmicAxialMeasurementsStorage === SOPClassUID + ) { + naturalizedName = 'OphthalmicAxialMeasurements'; + } else if ( + sopClassDictionary.IntraocularLensCalculationsStorage === SOPClassUID + ) { + naturalizedName = 'IntraocularLensCalculations'; + } else if ( + sopClassDictionary.MacularGridThicknessandVolumeReport === SOPClassUID + ) { + naturalizedName = 'MacularGridThicknessandVolume'; + } else if ( + sopClassDictionary.OphthalmicVisualFieldStaticPerimetryMeasurementsStorage === + SOPClassUID + ) { + naturalizedName = 'OphthalmicVisualFieldStaticPerimetryMeasurements'; + } else if (sopClassDictionary.OphthalmicThicknessMapStorage === SOPClassUID) { + naturalizedName = 'OphthalmicThicknessMap'; + } else if (sopClassDictionary.CornealTopographyMapStorage === SOPClassUID) { + naturalizedName = 'CornealTopographyMap'; + } else if (sopClassDictionary.BasicTextSR === SOPClassUID) { + naturalizedName = 'BasicTextSR'; + } else if (sopClassDictionary.EnhancedSR === SOPClassUID) { + naturalizedName = 'EnhancedSR'; + } else if (sopClassDictionary.ComprehensiveSR === SOPClassUID) { + naturalizedName = 'ComprehensiveSR'; + } else if (sopClassDictionary.Comprehensive3DSR === SOPClassUID) { + naturalizedName = 'Comprehensive3DSR'; + } else if (sopClassDictionary.ProcedureLog === SOPClassUID) { + naturalizedName = 'ProcedureLog'; + } else if (sopClassDictionary.MammographyCADSR === SOPClassUID) { + naturalizedName = 'MammographyCADSR'; + } else if (sopClassDictionary.KeyObjectSelection === SOPClassUID) { + naturalizedName = 'KeyObject'; + } else if (sopClassDictionary.ChestCADSR === SOPClassUID) { + naturalizedName = 'ChestCADSR'; + } else if (sopClassDictionary.XRayRadiationDoseSR === SOPClassUID) { + naturalizedName = 'XRayRadiationDoseSR'; + } else if ( + sopClassDictionary.RadiopharmaceuticalRadiationDoseSR === SOPClassUID + ) { + naturalizedName = 'RadiopharmaceuticalRadiationDoseSR'; + } else if (sopClassDictionary.ColonCADSR === SOPClassUID) { + naturalizedName = 'ColonCADSR'; + } else if ( + sopClassDictionary.ImplantationPlanSRDocumentStorage === SOPClassUID + ) { + naturalizedName = 'ImplantationPlanSRDocument'; + } else if (sopClassDictionary.EncapsulatedPDFStorage === SOPClassUID) { + naturalizedName = 'EncapsulatedPDF'; + } else if (sopClassDictionary.EncapsulatedCDAStorage === SOPClassUID) { + naturalizedName = 'EncapsulatedCDA'; + } else if (sopClassDictionary.BasicStructuredDisplayStorage === SOPClassUID) { + naturalizedName = 'BasicStructuredDisplay'; + } else if (sopClassDictionary.RTDoseStorage === SOPClassUID) { + naturalizedName = 'RTDose'; + } else if (sopClassDictionary.RTStructureSetStorage === SOPClassUID) { + naturalizedName = 'RTStructureSet'; + } else if (sopClassDictionary.RTBeamsTreatmentRecordStorage === SOPClassUID) { + naturalizedName = 'RTBeamsTreatmentRecord'; + } else if (sopClassDictionary.RTPlanStorage === SOPClassUID) { + naturalizedName = 'RTPlan'; + } else if ( + sopClassDictionary.RTBrachyTreatmentRecordStorage === SOPClassUID + ) { + naturalizedName = 'RTBrachyTreatmentRecord'; + } else if ( + sopClassDictionary.RTTreatmentSummaryRecordStorage === SOPClassUID + ) { + naturalizedName = 'RTTreatmentSummaryRecord'; + } else if (sopClassDictionary.RTIonPlanStorage === SOPClassUID) { + naturalizedName = 'RTIonPlan'; + } else if ( + sopClassDictionary.RTIonBeamsTreatmentRecordStorage === SOPClassUID + ) { + naturalizedName = 'RTIonBeamsTreatmentRecord'; + } else if ( + sopClassDictionary.RTBeamsDeliveryInstructionStorage === SOPClassUID + ) { + naturalizedName = 'RTBeamsDeliveryInstruction'; + } else if (sopClassDictionary.GenericImplantTemplateStorage === SOPClassUID) { + naturalizedName = 'GenericImplantTemplate'; + } else if ( + sopClassDictionary.ImplantAssemblyTemplateStorage === SOPClassUID + ) { + naturalizedName = 'ImplantAssemblyTemplate'; + } else if (sopClassDictionary.ImplantTemplateGroupStorage === SOPClassUID) { + naturalizedName = 'ImplantTemplateGroup'; + } + + return naturalizedName; +}; diff --git a/platform/ui/src/components/studyBrowser/Thumbnail.styl b/platform/ui/src/components/studyBrowser/Thumbnail.styl index 00a5609a2..3bb1b8fe9 100644 --- a/platform/ui/src/components/studyBrowser/Thumbnail.styl +++ b/platform/ui/src/components/studyBrowser/Thumbnail.styl @@ -25,7 +25,8 @@ box-shadow: inset 0 0 0 1px var(--ui-border-color-dark); border: 2px solid transparent; border-radius: 12px; - height: 135px; + min-height: 135px; + max-height: 250px; margin: 0 auto; padding: 5px; position: relative; @@ -36,6 +37,11 @@ h1 text-align: center color: var(--text-primary-color); + max-width: 200px; + max-height: 250px; + overflow-wrap: break-word; + word-wrap: break-word; + hyphens: auto; .series-details display: flex; diff --git a/platform/viewer/public/config/default.js b/platform/viewer/public/config/default.js index 6c2b85dfa..bb80b6381 100644 --- a/platform/viewer/public/config/default.js +++ b/platform/viewer/public/config/default.js @@ -30,6 +30,7 @@ window.config = { }, ], }, + // Extensions should be able to suggest default values for these? // Or we can require that these be explicitly set hotkeys: [ diff --git a/platform/viewer/src/connectedComponents/ConnectedStudyBrowser.js b/platform/viewer/src/connectedComponents/ConnectedStudyBrowser.js index 43992632b..06d2cc04f 100644 --- a/platform/viewer/src/connectedComponents/ConnectedStudyBrowser.js +++ b/platform/viewer/src/connectedComponents/ConnectedStudyBrowser.js @@ -79,7 +79,7 @@ const mapDispatchToProps = (dispatch, ownProps) => { }); } - if (displaySet.isModalitySupported === false) { + if (displaySet.isSOPClassUIDSupported === false) { const error = new Error('Modality not supported'); const message = 'Modality not supported'; LoggerService.error({ error, message }); diff --git a/platform/viewer/src/connectedComponents/Viewer.js b/platform/viewer/src/connectedComponents/Viewer.js index 54b6f48bf..bdd0e6238 100644 --- a/platform/viewer/src/connectedComponents/Viewer.js +++ b/platform/viewer/src/connectedComponents/Viewer.js @@ -563,8 +563,8 @@ const _checkForSeriesInconsistencesWarnings = async function(displaySet) { ); } - if (displaySet.isModalitySupported === false) { - inconsistencyWarnings.push('The datasets modality is not supported.'); + if (displaySet.isSOPClassUIDSupported === false) { + inconsistencyWarnings.push('The datasets is not supported.'); } displaySet.inconsistencyWarnings = inconsistencyWarnings; } else { @@ -668,6 +668,8 @@ const _mapStudiesToThumbnails = function(studies, activeDisplaySetInstanceUID) { } else if (displaySet.images && displaySet.images.length) { const imageIndex = Math.floor(displaySet.images.length / 2); imageId = displaySet.images[imageIndex].getImageId(); + } else if (displaySet.isSOPClassUIDSupported === false) { + altImageText = displaySet.SOPClassUIDNaturalized; } else { altImageText = displaySet.Modality ? displaySet.Modality : 'UN'; } diff --git a/platform/viewer/src/connectedComponents/ViewerMain.js b/platform/viewer/src/connectedComponents/ViewerMain.js index c71617177..6b0a7b8b9 100644 --- a/platform/viewer/src/connectedComponents/ViewerMain.js +++ b/platform/viewer/src/connectedComponents/ViewerMain.js @@ -190,7 +190,7 @@ class ViewerMain extends Component { } } - if (displaySet.isModalitySupported === false) { + if (displaySet.isSOPClassUIDSupported === false) { const error = new Error('Modality not supported'); const message = 'Modality not supported'; LoggerService.error({ error, message });