diff --git a/extensions/default/src/Panels/PanelStudyBrowser.tsx b/extensions/default/src/Panels/PanelStudyBrowser.tsx index 20af14f4d4c..4fe6882efc9 100644 --- a/extensions/default/src/Panels/PanelStudyBrowser.tsx +++ b/extensions/default/src/Panels/PanelStudyBrowser.tsx @@ -131,7 +131,7 @@ function PanelStudyBrowser({ const imageId = imageIds[Math.floor(imageIds.length / 2)]; // TODO: Is it okay that imageIds are not returned here for SR displaySets? - if (imageId) { + if (imageId && !displaySet?.unsupported) { // When the image arrives, render it and store the result in the thumbnailImgSrcMap newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc( imageId @@ -175,20 +175,22 @@ function PanelStudyBrowser({ const displaySet = displaySetService.getDisplaySetByUID( dSet.displaySetInstanceUID ); - const imageIds = dataSource.getImageIdsForDisplaySet(displaySet); - const imageId = imageIds[Math.floor(imageIds.length / 2)]; - - // TODO: Is it okay that imageIds are not returned here for SR displaysets? - if (imageId) { - // When the image arrives, render it and store the result in the thumbnailImgSrcMap - newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc( - imageId, - dSet.initialViewport - ); - if (isMounted.current) { - setThumbnailImageSrcMap(prevState => { - return { ...prevState, ...newImageSrcEntry }; - }); + if (!displaySet?.unsupported) { + const imageIds = dataSource.getImageIdsForDisplaySet(displaySet); + const imageId = imageIds[Math.floor(imageIds.length / 2)]; + + // TODO: Is it okay that imageIds are not returned here for SR displaysets? + if (imageId) { + // When the image arrives, render it and store the result in the thumbnailImgSrcMap + newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc( + imageId, + dSet.initialViewport + ); + if (isMounted.current) { + setThumbnailImageSrcMap(prevState => { + return { ...prevState, ...newImageSrcEntry }; + }); + } } } }); @@ -308,7 +310,7 @@ function _mapDisplaySets(displaySets, thumbnailImageSrcMap) { .filter(ds => !ds.excludeFromThumbnailBrowser) .forEach(ds => { const imageSrc = thumbnailImageSrcMap[ds.displaySetInstanceUID]; - const componentType = _getComponentType(ds.Modality); + const componentType = _getComponentType(ds); const array = componentType === 'thumbnail' @@ -348,8 +350,8 @@ const thumbnailNoImageModalities = [ 'RTDOSE', ]; -function _getComponentType(Modality) { - if (thumbnailNoImageModalities.includes(Modality)) { +function _getComponentType(ds) { + if (thumbnailNoImageModalities.includes(ds.Modality) || ds?.unsupported) { // TODO probably others. return 'thumbnailNoImage'; } diff --git a/extensions/default/src/getDisplaySetsFromUnsupportedSeries.js b/extensions/default/src/getDisplaySetsFromUnsupportedSeries.js new file mode 100644 index 00000000000..d2da6f5bb7c --- /dev/null +++ b/extensions/default/src/getDisplaySetsFromUnsupportedSeries.js @@ -0,0 +1,30 @@ +import ImageSet from '@ohif/core/src/classes/ImageSet'; +import { DisplaySetMessage, DisplaySetMessageList } from '@ohif/core'; +/** + * Default handler for a instance list with an unsupported sopClassUID + */ +export default function getDisplaySetsFromUnsupportedSeries(instances) { + const imageSet = new ImageSet(instances); + const messages = new DisplaySetMessageList(); + messages.addMessage(DisplaySetMessage.CODES.UNSUPPORTED_DISPLAYSET); + const instance = instances[0]; + + imageSet.setAttributes({ + displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID + SeriesDate: instance.SeriesDate, + SeriesTime: instance.SeriesTime, + SeriesInstanceUID: instance.SeriesInstanceUID, + StudyInstanceUID: instance.StudyInstanceUID, + SeriesNumber: instance.SeriesNumber || 0, + FrameRate: instance.FrameTime, + SOPClassUID: instance.SOPClassUID, + SeriesDescription: instance.SeriesDescription || '', + Modality: instance.Modality, + numImageFrames: instances.length, + unsupported: true, + SOPClassHandlerId: 'unsupported', + isReconstructable: false, + messages, + }); + return [imageSet]; +} diff --git a/extensions/default/src/getSopClassHandlerModule.js b/extensions/default/src/getSopClassHandlerModule.js index 51aebb1ecf2..5d8113c1853 100644 --- a/extensions/default/src/getSopClassHandlerModule.js +++ b/extensions/default/src/getSopClassHandlerModule.js @@ -4,6 +4,7 @@ import ImageSet from '@ohif/core/src/classes/ImageSet'; import isDisplaySetReconstructable from '@ohif/core/src/utils/isDisplaySetReconstructable'; import { id } from './id'; import getDisplaySetMessages from './getDisplaySetMessages'; +import getDisplaySetsFromUnsupportedSeries from './getDisplaySetsFromUnsupportedSeries'; const sopClassHandlerName = 'stack'; @@ -213,6 +214,11 @@ function getSopClassHandlerModule() { sopClassUids, getDisplaySetsFromSeries, }, + { + name: 'not-supported-display-sets-handler', + sopClassUids: [], + getDisplaySetsFromSeries: getDisplaySetsFromUnsupportedSeries, + }, ]; } diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx index 91228d9c56e..f999f9e9bc2 100644 --- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx +++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx @@ -156,7 +156,7 @@ function PanelStudyBrowserTracking({ const imageId = imageIds[Math.floor(imageIds.length / 2)]; // TODO: Is it okay that imageIds are not returned here for SR displaysets? - if (imageId) { + if (imageId && !displaySet?.unsupported) { // When the image arrives, render it and store the result in the thumbnailImgSrcMap newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc( imageId @@ -212,23 +212,24 @@ function PanelStudyBrowserTracking({ const displaySet = displaySetService.getDisplaySetByUID( displaySetInstanceUID ); - - if (options.madeInClient) { - setJumpToDisplaySet(displaySetInstanceUID); - } - - const imageIds = dataSource.getImageIdsForDisplaySet(displaySet); - const imageId = imageIds[Math.floor(imageIds.length / 2)]; - - // TODO: Is it okay that imageIds are not returned here for SR displaysets? - if (imageId) { - // When the image arrives, render it and store the result in the thumbnailImgSrcMap - newImageSrcEntry[displaySetInstanceUID] = await getImageSrc( - imageId - ); - setThumbnailImageSrcMap(prevState => { - return { ...prevState, ...newImageSrcEntry }; - }); + if (!displaySet?.unsupported) { + if (options.madeInClient) { + setJumpToDisplaySet(displaySetInstanceUID); + } + + const imageIds = dataSource.getImageIdsForDisplaySet(displaySet); + const imageId = imageIds[Math.floor(imageIds.length / 2)]; + + // TODO: Is it okay that imageIds are not returned here for SR displaysets? + if (imageId) { + // When the image arrives, render it and store the result in the thumbnailImgSrcMap + newImageSrcEntry[displaySetInstanceUID] = await getImageSrc( + imageId + ); + setThumbnailImageSrcMap(prevState => { + return { ...prevState, ...newImageSrcEntry }; + }); + } } }); } @@ -424,7 +425,7 @@ function _mapDisplaySets( .filter(ds => !ds.excludeFromThumbnailBrowser) .forEach(ds => { const imageSrc = thumbnailImageSrcMap[ds.displaySetInstanceUID]; - const componentType = _getComponentType(ds.Modality); + const componentType = _getComponentType(ds); const numPanes = viewportGridService.getNumViewportPanes(); const viewportIdentificator = numPanes === 1 @@ -471,7 +472,7 @@ function _mapDisplaySets( if (componentType === 'thumbnailNoImage') { if (dataSource.reject && dataSource.reject.series) { - thumbnailProps.canReject = true; + thumbnailProps.canReject = !ds?.unsupported; thumbnailProps.onReject = () => { uiDialogService.create({ id: 'ds-reject-sr', @@ -564,8 +565,8 @@ const thumbnailNoImageModalities = [ 'OT', ]; -function _getComponentType(Modality) { - if (thumbnailNoImageModalities.includes(Modality)) { +function _getComponentType(ds) { + if (thumbnailNoImageModalities.includes(ds.Modality) || ds?.unsupported) { return 'thumbnailNoImage'; } diff --git a/platform/app/src/components/ViewportGrid.tsx b/platform/app/src/components/ViewportGrid.tsx index f3cead76a90..1d8bbacaaaa 100644 --- a/platform/app/src/components/ViewportGrid.tsx +++ b/platform/app/src/components/ViewportGrid.tsx @@ -280,6 +280,10 @@ function ViewerViewportGrid(props) { displaySetService.getDisplaySetByUID(displaySetInstanceUID) || {} ); } + ).filter( + (displaySet) => { + return !displaySet?.unsupported; + } ); const ViewportComponent = _getViewportComponent( diff --git a/platform/core/src/services/DisplaySetService/DisplaySetMessage.ts b/platform/core/src/services/DisplaySetService/DisplaySetMessage.ts index a752acee35d..6a139daf637 100644 --- a/platform/core/src/services/DisplaySetService/DisplaySetMessage.ts +++ b/platform/core/src/services/DisplaySetService/DisplaySetMessage.ts @@ -16,6 +16,7 @@ class DisplaySetMessage { INCONSISTENT_COMPONENTS: 10, INCONSISTENT_ORIENTATIONS: 11, INCONSISTENT_POSITION_INFORMATION: 12, + UNSUPPORTED_DISPLAYSET: 13, }; constructor(id: number) { diff --git a/platform/core/src/services/DisplaySetService/DisplaySetService.ts b/platform/core/src/services/DisplaySetService/DisplaySetService.ts index 5b0dd138cad..a3251dc6536 100644 --- a/platform/core/src/services/DisplaySetService/DisplaySetService.ts +++ b/platform/core/src/services/DisplaySetService/DisplaySetService.ts @@ -9,6 +9,7 @@ export type DisplaySet = { StudyInstanceUID: string; SeriesInstanceUID?: string; numImages?: number; + unsupported?: boolean; }; const displaySetCache = new Map(); @@ -48,6 +49,7 @@ export default class DisplaySetService extends PubSubService { }; public activeDisplaySets = []; + public unsuportedSOPClassHandler; extensionManager: ExtensionManager; protected activeDisplaySetsMap = new Map(); @@ -58,6 +60,8 @@ export default class DisplaySetService extends PubSubService { constructor() { super(EVENTS); + this.unsuportedSOPClassHandler = + '@ohif/extension-default.sopClassHandlerModule.not-supported-display-sets-handler'; } public init(extensionManager, SOPClassHandlerIds): void { @@ -85,6 +89,14 @@ export default class DisplaySetService extends PubSubService { }); } + /** + * Sets the handler for unsupported sop classes + * @param sopClassHandlerUID + */ + public setUnsuportedSOPClassHandler(sopClassHandler) { + this.unsuportedSOPClassHandler = sopClassHandler; + } + /** * Adds new display sets directly, as specified. * Use this function when the display sets are created externally directly @@ -256,6 +268,47 @@ export default class DisplaySetService extends PubSubService { this.activeDisplaySetsMap.clear(); } + /** + * This function hides the old makeDisplaySetForInstances function to first + * separate the instances by sopClassUID so each call have only instances + * with the same sopClassUID, to avoid a series composed by different + * sopClassUIDs be filtered inside one of the SOPClassHandler functions and + * didn't appear in the series list. + * @param instancesSrc + * @param settings + * @returns + */ + public makeDisplaySetForInstances( + instancesSrc: InstanceMetadata[], + settings + ): DisplaySet[] { + // creating a sopClassUID list and for each sopClass associate its respective + // instance list + const instancesForSetSOPClasses = instancesSrc.reduce( + (sopClassList, instance) => { + if (!(instance.SOPClassUID in sopClassList)) { + sopClassList[instance.SOPClassUID] = []; + } + sopClassList[instance.SOPClassUID].push(instance); + return sopClassList; + }, + {} + ); + // for each sopClassUID, call the old makeDisplaySetForInstances with a + // instance list composed only by instances with the same sopClassUID and + // accumulate the displaySets in the variable allDisplaySets + const sopClasses = Object.keys(instancesForSetSOPClasses); + let allDisplaySets = []; + sopClasses.forEach(sopClass => { + const displaySets = this._makeDisplaySetForInstances( + instancesForSetSOPClasses[sopClass], + settings + ); + allDisplaySets = [...allDisplaySets, ...displaySets]; + }); + return allDisplaySets; + } + /** * Creates new display sets for the instances contained in instancesSrc * according to the sop class handlers registered. @@ -272,7 +325,7 @@ export default class DisplaySetService extends PubSubService { * @param settings are settings to add * @returns Array of the display sets added. */ - public makeDisplaySetForInstances( + private _makeDisplaySetForInstances( instancesSrc: InstanceMetadata[], settings ): DisplaySet[] { @@ -355,6 +408,26 @@ export default class DisplaySetService extends PubSubService { allDisplaySets.push(...displaySets); } } + // applying the default sopClassUID handler + if (allDisplaySets.length === 0) { + // applying hp-defined viewport settings to the displaysets + const handler = this.extensionManager.getModuleEntry( + this.unsuportedSOPClassHandler + ); + const displaySets = handler.getDisplaySetsFromSeries(instances); + if (displaySets?.length) { + displaySets.forEach(ds => { + Object.keys(settings).forEach(key => { + ds[key] = settings[key]; + }); + }); + + this._addDisplaySetsToCache(displaySets); + this._addActiveDisplaySets(displaySets); + + allDisplaySets.push(...displaySets); + } + } return allDisplaySets; } diff --git a/platform/core/src/services/DisplaySetService/IDisplaySet.ts b/platform/core/src/services/DisplaySetService/IDisplaySet.ts index 9b81d6f891e..f193e32221e 100644 --- a/platform/core/src/services/DisplaySetService/IDisplaySet.ts +++ b/platform/core/src/services/DisplaySetService/IDisplaySet.ts @@ -3,6 +3,7 @@ interface IDisplaySet { StudyInstanceUID: string; SeriesInstanceUID?: string; SeriesNumber?: string; + unsupported?: boolean; } export default IDisplaySet; diff --git a/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts b/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts index d2015390967..ddf5ac80782 100644 --- a/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts +++ b/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts @@ -559,6 +559,13 @@ export default class HangingProtocolService extends PubSubService { } getViewportsRequireUpdate(viewportIndex, displaySetInstanceUID) { + const { displaySetService } = this._servicesManager.services; + const displaySet = displaySetService.getDisplaySetByUID( + displaySetInstanceUID + ); + if (displaySet?.unsupported) { + throw new Error('Unsupported displaySet'); + } const newDisplaySetInstanceUID = displaySetInstanceUID; const protocol = this.protocol; const protocolStage = protocol.stages[this.stageIndex]; @@ -1431,7 +1438,7 @@ export default class HangingProtocolService extends PubSubService { } const studyDisplaySets = this.displaySets.filter( - it => it.StudyInstanceUID === study.StudyInstanceUID + it => it.StudyInstanceUID === study.StudyInstanceUID && !it?.unsupported ); const studyMatchDetails = this.protocolEngine.findMatch( diff --git a/platform/i18n/src/locales/en-US/Messages.json b/platform/i18n/src/locales/en-US/Messages.json index 4004aa01017..13c4f7a7bdb 100644 --- a/platform/i18n/src/locales/en-US/Messages.json +++ b/platform/i18n/src/locales/en-US/Messages.json @@ -10,5 +10,6 @@ "9": "DisplaySet has inconsistent dimensions between frames.", "10": "DisplaySet has frames with inconsistent number of components.", "11": "DisplaySet has frames with inconsistent orientations.", - "12": "DisplaySet has inconsistent position information." + "12": "DisplaySet has inconsistent position information.", + "13": "Unsupported displaySet." } diff --git a/platform/i18n/src/locales/pt-BR/Messages.json b/platform/i18n/src/locales/pt-BR/Messages.json index f0d12dce1cd..649b86350b3 100644 --- a/platform/i18n/src/locales/pt-BR/Messages.json +++ b/platform/i18n/src/locales/pt-BR/Messages.json @@ -10,5 +10,6 @@ "9": "Série possui dimensões inconsistentes entre frames.", "10": "Série possui frames com componentes inconsistentes.", "11": "Série possui frames com orientações inconsistentes.", - "12": "Série possui informação de posição inconsistentes." + "12": "Série possui informação de posição inconsistentes.", + "13": "Série não suportada." } diff --git a/platform/ui/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx b/platform/ui/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx index 0f23ab49351..f6772ff716b 100644 --- a/platform/ui/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx +++ b/platform/ui/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx @@ -12,7 +12,7 @@ import { useTranslation } from 'react-i18next'; const DisplaySetMessageListTooltip = ({ messages, id }): React.ReactNode => { const { t } = useTranslation('Messages'); const [isOpen, setIsOpen] = useState(false); - if (messages.size()) { + if (messages?.size()) { return ( <> { arrow="center" parent={`#${id}`} > -
+
{
    {messages.messages.map((message, index) => ( diff --git a/platform/ui/src/components/Thumbnail/Thumbnail.tsx b/platform/ui/src/components/Thumbnail/Thumbnail.tsx index f334ce49aca..a8fbc146aab 100644 --- a/platform/ui/src/components/Thumbnail/Thumbnail.tsx +++ b/platform/ui/src/components/Thumbnail/Thumbnail.tsx @@ -82,7 +82,7 @@ const Thumbnail = ({
{description}
diff --git a/platform/ui/src/components/ThumbnailList/ThumbnailList.tsx b/platform/ui/src/components/ThumbnailList/ThumbnailList.tsx index 2de139ef17d..8b55c8a7085 100644 --- a/platform/ui/src/components/ThumbnailList/ThumbnailList.tsx +++ b/platform/ui/src/components/ThumbnailList/ThumbnailList.tsx @@ -159,7 +159,7 @@ function _getModalityTooltip(modality) { const _modalityTooltips = { SR: 'Structured Report', SEG: 'Segmentation', - RT: 'RT Structure Set', + RTSTRUCT: 'RT Structure Set', }; export default ThumbnailList; diff --git a/platform/ui/src/components/ThumbnailNoImage/ThumbnailNoImage.tsx b/platform/ui/src/components/ThumbnailNoImage/ThumbnailNoImage.tsx index 646a6836aee..52ccba65405 100644 --- a/platform/ui/src/components/ThumbnailNoImage/ThumbnailNoImage.tsx +++ b/platform/ui/src/components/ThumbnailNoImage/ThumbnailNoImage.tsx @@ -5,6 +5,7 @@ import { useDrag } from 'react-dnd'; import Icon from '../Icon'; import Tooltip from '../Tooltip'; import Typography from '../Typography'; +import DisplaySetMessageListTooltip from '../DisplaySetMessageListTooltip'; const ThumbnailNoImage = ({ displaySetInstanceUID, @@ -57,23 +58,10 @@ const ThumbnailNoImage = ({
{seriesDate} - {messages?.size() ? ( -
- - {messages.thumbnailContents()} -
- } - > - - - - ) : ( - <> - )} +
{canReject && ( @@ -107,7 +95,7 @@ ThumbnailNoImage.propTypes = { /** Must match the "type" a dropTarget expects */ type: PropTypes.string.isRequired, }), - description: PropTypes.string.isRequired, + description: PropTypes.string, modality: PropTypes.string.isRequired, /* Tooltip message to display when modality text is hovered */ modalityTooltip: PropTypes.string.isRequired, diff --git a/platform/ui/src/components/ViewportActionBar/ViewportActionBar.tsx b/platform/ui/src/components/ViewportActionBar/ViewportActionBar.tsx index 7a20dddde77..60d15f69ec5 100644 --- a/platform/ui/src/components/ViewportActionBar/ViewportActionBar.tsx +++ b/platform/ui/src/components/ViewportActionBar/ViewportActionBar.tsx @@ -198,7 +198,7 @@ ViewportActionBar.propTypes = { patientAge: PropTypes.string.isRequired, MRN: PropTypes.string.isRequired, thickness: PropTypes.string.isRequired, - thicknessUnits: PropTypes.string.isRequired, + thicknessUnits: PropTypes.string, spacing: PropTypes.string.isRequired, scanner: PropTypes.string.isRequired, }),