feat(thumbnail highlight): Thumbnails of hydrated series are now highlighted (#3594)
This commit is contained in:
parent
3fe676638b
commit
0f9b36906f
@ -23,6 +23,7 @@ import CornerstoneOverlays from './Overlays/CornerstoneOverlays';
|
|||||||
import getSOPInstanceAttributes from '../utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
|
import getSOPInstanceAttributes from '../utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
|
||||||
import CornerstoneServices from '../types/CornerstoneServices';
|
import CornerstoneServices from '../types/CornerstoneServices';
|
||||||
import CinePlayer from '../components/CinePlayer';
|
import CinePlayer from '../components/CinePlayer';
|
||||||
|
import { Types } from '@ohif/core';
|
||||||
|
|
||||||
const STACK = 'stack';
|
const STACK = 'stack';
|
||||||
|
|
||||||
@ -264,7 +265,14 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { unsubscribe } = displaySetService.subscribe(
|
const { unsubscribe } = displaySetService.subscribe(
|
||||||
displaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED,
|
displaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED,
|
||||||
async invalidatedDisplaySetInstanceUID => {
|
async ({
|
||||||
|
displaySetInstanceUID: invalidatedDisplaySetInstanceUID,
|
||||||
|
invalidateData,
|
||||||
|
}: Types.DisplaySetSeriesMetadataInvalidatedEvent) => {
|
||||||
|
if (!invalidateData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(
|
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(
|
||||||
viewportIndex
|
viewportIndex
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1123,6 +1123,8 @@ class SegmentationService extends PubSubService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._setDisplaySetIsHydrated(segmentationId, true);
|
||||||
|
|
||||||
segmentation.hydrated = true;
|
segmentation.hydrated = true;
|
||||||
|
|
||||||
if (!suppressEvents) {
|
if (!suppressEvents) {
|
||||||
@ -1132,6 +1134,18 @@ class SegmentationService extends PubSubService {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private _setDisplaySetIsHydrated(
|
||||||
|
displaySetUID: string,
|
||||||
|
isHydrated: boolean
|
||||||
|
): void {
|
||||||
|
const {
|
||||||
|
DisplaySetService: displaySetService,
|
||||||
|
} = this.servicesManager.services;
|
||||||
|
const displaySet = displaySetService.getDisplaySetByUID(displaySetUID);
|
||||||
|
displaySet.isHydrated = isHydrated;
|
||||||
|
displaySetService.setDisplaySetMetadataInvalidated(displaySetUID, false);
|
||||||
|
}
|
||||||
|
|
||||||
private _highlightLabelmap(
|
private _highlightLabelmap(
|
||||||
segmentIndex: number,
|
segmentIndex: number,
|
||||||
alpha: number,
|
alpha: number,
|
||||||
@ -1303,6 +1317,8 @@ class SegmentationService extends PubSubService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._setDisplaySetIsHydrated(segmentationId, false);
|
||||||
|
|
||||||
this._broadcastEvent(this.EVENTS.SEGMENTATION_REMOVED, {
|
this._broadcastEvent(this.EVENTS.SEGMENTATION_REMOVED, {
|
||||||
segmentationId,
|
segmentationId,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -210,9 +210,22 @@ function PanelStudyBrowser({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const SubscriptionDisplaySetMetaDataInvalidated = displaySetService.subscribe(
|
||||||
|
displaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED,
|
||||||
|
() => {
|
||||||
|
const mappedDisplaySets = _mapDisplaySets(
|
||||||
|
displaySetService.getActiveDisplaySets(),
|
||||||
|
thumbnailImageSrcMap
|
||||||
|
);
|
||||||
|
|
||||||
|
setDisplaySets(mappedDisplaySets);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
SubscriptionDisplaySetsAdded.unsubscribe();
|
SubscriptionDisplaySetsAdded.unsubscribe();
|
||||||
SubscriptionDisplaySetsChanged.unsubscribe();
|
SubscriptionDisplaySetsChanged.unsubscribe();
|
||||||
|
SubscriptionDisplaySetMetaDataInvalidated.unsubscribe();
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
@ -335,6 +348,7 @@ function _mapDisplaySets(displaySets, thumbnailImageSrcMap) {
|
|||||||
displaySetInstanceUID: ds.displaySetInstanceUID,
|
displaySetInstanceUID: ds.displaySetInstanceUID,
|
||||||
// .. Any other data to pass
|
// .. Any other data to pass
|
||||||
},
|
},
|
||||||
|
isHydratedForDerivedDisplaySet: ds.isHydrated,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -256,9 +256,29 @@ function PanelStudyBrowserTracking({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const SubscriptionDisplaySetMetaDataInvalidated = displaySetService.subscribe(
|
||||||
|
displaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED,
|
||||||
|
() => {
|
||||||
|
const mappedDisplaySets = _mapDisplaySets(
|
||||||
|
displaySetService.getActiveDisplaySets(),
|
||||||
|
thumbnailImageSrcMap,
|
||||||
|
trackedSeries,
|
||||||
|
viewports,
|
||||||
|
viewportGridService,
|
||||||
|
dataSource,
|
||||||
|
displaySetService,
|
||||||
|
uiDialogService,
|
||||||
|
uiNotificationService
|
||||||
|
);
|
||||||
|
|
||||||
|
setDisplaySets(mappedDisplaySets);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
SubscriptionDisplaySetsAdded.unsubscribe();
|
SubscriptionDisplaySetsAdded.unsubscribe();
|
||||||
SubscriptionDisplaySetsChanged.unsubscribe();
|
SubscriptionDisplaySetsChanged.unsubscribe();
|
||||||
|
SubscriptionDisplaySetMetaDataInvalidated.unsubscribe();
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [
|
}, [
|
||||||
@ -467,6 +487,7 @@ function _mapDisplaySets(
|
|||||||
// .. Any other data to pass
|
// .. Any other data to pass
|
||||||
},
|
},
|
||||||
isTracked: trackedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID),
|
isTracked: trackedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID),
|
||||||
|
isHydratedForDerivedDisplaySet: ds.isHydrated,
|
||||||
viewportIdentificator,
|
viewportIdentificator,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,8 @@
|
|||||||
import { ExtensionManager } from '../../extensions';
|
import { ExtensionManager } from '../../extensions';
|
||||||
import { InstanceMetadata } from '../../types';
|
import { DisplaySet, InstanceMetadata } from '../../types';
|
||||||
import { PubSubService } from '../_shared/pubSubServiceInterface';
|
import { PubSubService } from '../_shared/pubSubServiceInterface';
|
||||||
import EVENTS from './EVENTS';
|
import EVENTS from './EVENTS';
|
||||||
|
|
||||||
export type DisplaySet = {
|
|
||||||
displaySetInstanceUID: string;
|
|
||||||
instances: InstanceMetadata[];
|
|
||||||
StudyInstanceUID: string;
|
|
||||||
SeriesInstanceUID?: string;
|
|
||||||
numImages?: number;
|
|
||||||
unsupported?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const displaySetCache = new Map<string, DisplaySet>();
|
const displaySetCache = new Map<string, DisplaySet>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,7 +145,10 @@ export default class DisplaySetService extends PubSubService {
|
|||||||
return displaySet;
|
return displaySet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setDisplaySetMetadataInvalidated(displaySetInstanceUID: string): void {
|
public setDisplaySetMetadataInvalidated(
|
||||||
|
displaySetInstanceUID: string,
|
||||||
|
invalidateData = true
|
||||||
|
): void {
|
||||||
const displaySet = this.getDisplaySetByUID(displaySetInstanceUID);
|
const displaySet = this.getDisplaySetByUID(displaySetInstanceUID);
|
||||||
|
|
||||||
if (!displaySet) {
|
if (!displaySet) {
|
||||||
@ -162,10 +156,10 @@ export default class DisplaySetService extends PubSubService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// broadcast event to update listeners with the new displaySets
|
// broadcast event to update listeners with the new displaySets
|
||||||
this._broadcastEvent(
|
this._broadcastEvent(EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, {
|
||||||
EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED,
|
displaySetInstanceUID,
|
||||||
displaySetInstanceUID
|
invalidateData,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteDisplaySet(displaySetInstanceUID) {
|
public deleteDisplaySet(displaySetInstanceUID) {
|
||||||
|
|||||||
15
platform/core/src/types/DisplaySet.ts
Normal file
15
platform/core/src/types/DisplaySet.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { InstanceMetadata } from './StudyMetadata';
|
||||||
|
|
||||||
|
export type DisplaySet = {
|
||||||
|
displaySetInstanceUID: string;
|
||||||
|
instances: InstanceMetadata[];
|
||||||
|
StudyInstanceUID: string;
|
||||||
|
SeriesInstanceUID?: string;
|
||||||
|
numImages?: number;
|
||||||
|
unsupported?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DisplaySetSeriesMetadataInvalidatedEvent = {
|
||||||
|
displaySetInstanceUID: string;
|
||||||
|
invalidateData: boolean;
|
||||||
|
};
|
||||||
@ -2,7 +2,6 @@ import * as Extensions from '../extensions/ExtensionManager';
|
|||||||
import * as HangingProtocol from './HangingProtocol';
|
import * as HangingProtocol from './HangingProtocol';
|
||||||
import Services from './Services';
|
import Services from './Services';
|
||||||
import Hotkey from '../classes/Hotkey';
|
import Hotkey from '../classes/Hotkey';
|
||||||
import { DisplaySet } from '../services/DisplaySetService/DisplaySetService';
|
|
||||||
import { DataSourceDefinition } from './DataSource';
|
import { DataSourceDefinition } from './DataSource';
|
||||||
|
|
||||||
export * from '../services/CustomizationService/types';
|
export * from '../services/CustomizationService/types';
|
||||||
@ -10,6 +9,7 @@ export * from '../services/CustomizationService/types';
|
|||||||
export * from './AppConfig';
|
export * from './AppConfig';
|
||||||
export * from './Consumer';
|
export * from './Consumer';
|
||||||
export * from './Command';
|
export * from './Command';
|
||||||
|
export * from './DisplaySet';
|
||||||
export * from './StudyMetadata';
|
export * from './StudyMetadata';
|
||||||
export * from './PanelModule';
|
export * from './PanelModule';
|
||||||
export * from './IPubSub';
|
export * from './IPubSub';
|
||||||
@ -19,11 +19,4 @@ export * from './Color';
|
|||||||
* Export the types used within the various services and managers, but
|
* Export the types used within the various services and managers, but
|
||||||
* not the services/managers themselves, which are exported at the top level.
|
* not the services/managers themselves, which are exported at the top level.
|
||||||
*/
|
*/
|
||||||
export {
|
export { Extensions, HangingProtocol, Services, Hotkey, DataSourceDefinition };
|
||||||
Extensions,
|
|
||||||
HangingProtocol,
|
|
||||||
Services,
|
|
||||||
Hotkey,
|
|
||||||
DisplaySet,
|
|
||||||
DataSourceDefinition,
|
|
||||||
};
|
|
||||||
|
|||||||
@ -160,6 +160,13 @@ The following properties can be added to the `configuration` property of each da
|
|||||||
##### `dicomUploadEnabled`
|
##### `dicomUploadEnabled`
|
||||||
A boolean indicating if the DICOM upload to the data source is permitted/accepted or not. A value of true provides a link on the OHIF work list page that allows for DICOM files from the local file system to be uploaded to the data source
|
A boolean indicating if the DICOM upload to the data source is permitted/accepted or not. A value of true provides a link on the OHIF work list page that allows for DICOM files from the local file system to be uploaded to the data source
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
The [OHIF plugin for Orthanc](https://book.orthanc-server.com/plugins/ohif.html) by default utilizes the DICOM JSON data
|
||||||
|
source and it has been discovered that only those studies uploaded to Orthanc AFTER the plugin has been installed are
|
||||||
|
available as DICOM JSON. As such, if the OHIF plugin for Orthanc is desired for studies uploaded prior to installing the plugin,
|
||||||
|
then consider switching to using [DICOMweb instead](https://book.orthanc-server.com/plugins/ohif.html#using-dicomweb).
|
||||||
|
:::
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### `singlepart`
|
#### `singlepart`
|
||||||
|
|||||||
@ -33,6 +33,7 @@ There are three events that get broadcasted in `DisplaySetService`:
|
|||||||
| DISPLAY_SETS_ADDED | Fires a displayset is added to the displaysets cache |
|
| DISPLAY_SETS_ADDED | Fires a displayset is added to the displaysets cache |
|
||||||
| DISPLAY_SETS_CHANGED | Fires when a displayset is changed |
|
| DISPLAY_SETS_CHANGED | Fires when a displayset is changed |
|
||||||
| DISPLAY_SETS_REMOVED | Fires when a displayset is removed |
|
| DISPLAY_SETS_REMOVED | Fires when a displayset is removed |
|
||||||
|
| DISPLAY_SET_SERIES_METADATA_INVALIDATED | Fires when a displayset's series metadata has been altered. An object payload for the event is sent with properties: `displaySetInstanceUID` - the UID of the display set affected; `invalidateData` - boolean indicating if data should be invalidated
|
||||||
|
|
||||||
|
|
||||||
## API
|
## API
|
||||||
@ -60,3 +61,5 @@ Let's find out about the public API for `DisplaySetService`.
|
|||||||
- `deleteDisplaySet`: Deletes the displaySets from the displaySets cache
|
- `deleteDisplaySet`: Deletes the displaySets from the displaySets cache
|
||||||
|
|
||||||
- `addActiveDisplaySets`: Adds a new display set independently of the make operation.
|
- `addActiveDisplaySets`: Adds a new display set independently of the make operation.
|
||||||
|
|
||||||
|
- `setDisplaySetMetadataInvalidated`: Fires the `DISPLAY_SET_SERIES_METADATA_INVALIDATED` event.
|
||||||
|
|||||||
@ -36,6 +36,7 @@ const ThumbnailList = ({
|
|||||||
imageSrc,
|
imageSrc,
|
||||||
messages,
|
messages,
|
||||||
imageAltText,
|
imageAltText,
|
||||||
|
isHydratedForDerivedDisplaySet,
|
||||||
}) => {
|
}) => {
|
||||||
const isActive = activeDisplaySetInstanceUIDs.includes(
|
const isActive = activeDisplaySetInstanceUIDs.includes(
|
||||||
displaySetInstanceUID
|
displaySetInstanceUID
|
||||||
@ -104,6 +105,9 @@ const ThumbnailList = ({
|
|||||||
onThumbnailDoubleClick(displaySetInstanceUID)
|
onThumbnailDoubleClick(displaySetInstanceUID)
|
||||||
}
|
}
|
||||||
viewportIdentificator={viewportIdentificator}
|
viewportIdentificator={viewportIdentificator}
|
||||||
|
isHydratedForDerivedDisplaySet={
|
||||||
|
isHydratedForDerivedDisplaySet
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -20,6 +20,7 @@ const ThumbnailNoImage = ({
|
|||||||
messages,
|
messages,
|
||||||
dragData,
|
dragData,
|
||||||
isActive,
|
isActive,
|
||||||
|
isHydratedForDerivedDisplaySet,
|
||||||
}) => {
|
}) => {
|
||||||
const [collectedProps, drag, dragPreview] = useDrag({
|
const [collectedProps, drag, dragPreview] = useDrag({
|
||||||
type: 'displayset',
|
type: 'displayset',
|
||||||
@ -32,8 +33,8 @@ const ThumbnailNoImage = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
'flex flex-row flex-1 cursor-pointer outline-none border-transparent hover:border-blue-300 focus:border-blue-300 rounded select-none',
|
'flex flex-row flex-1 cursor-pointer outline-none hover:border-blue-300 focus:border-blue-300 rounded select-none',
|
||||||
isActive ? 'border-2 border-primary-light' : 'border'
|
isActive ? 'border-2 border-primary-light' : 'border border-transparent'
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
padding: isActive ? '11px' : '12px',
|
padding: isActive ? '11px' : '12px',
|
||||||
@ -48,12 +49,27 @@ const ThumbnailNoImage = ({
|
|||||||
<div ref={drag}>
|
<div ref={drag}>
|
||||||
<div className="flex flex-col flex-1">
|
<div className="flex flex-col flex-1">
|
||||||
<div className="flex flex-row items-center flex-1 mb-2">
|
<div className="flex flex-row items-center flex-1 mb-2">
|
||||||
<Icon name="list-bullets" className="w-12 text-secondary-light" />
|
<Icon
|
||||||
|
name="list-bullets"
|
||||||
|
className={classnames(
|
||||||
|
'w-12',
|
||||||
|
isHydratedForDerivedDisplaySet
|
||||||
|
? 'text-primary-light'
|
||||||
|
: 'text-secondary-light'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
position="bottom"
|
position="bottom"
|
||||||
content={<Typography>{modalityTooltip}</Typography>}
|
content={<Typography>{modalityTooltip}</Typography>}
|
||||||
>
|
>
|
||||||
<div className="px-3 text-lg text-white rounded-sm bg-primary-main">
|
<div
|
||||||
|
className={classnames(
|
||||||
|
'px-3 text-lg rounded-sm',
|
||||||
|
isHydratedForDerivedDisplaySet
|
||||||
|
? 'text-black bg-primary-light'
|
||||||
|
: 'text-white bg-primary-main'
|
||||||
|
)}
|
||||||
|
>
|
||||||
{modality}
|
{modality}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -104,6 +120,7 @@ ThumbnailNoImage.propTypes = {
|
|||||||
onDoubleClick: PropTypes.func.isRequired,
|
onDoubleClick: PropTypes.func.isRequired,
|
||||||
messages: PropTypes.object,
|
messages: PropTypes.object,
|
||||||
isActive: PropTypes.bool.isRequired,
|
isActive: PropTypes.bool.isRequired,
|
||||||
|
isHydratedForDerivedDisplaySet: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ThumbnailNoImage;
|
export default ThumbnailNoImage;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user