feat(thumbnail highlight): Thumbnails of hydrated series are now highlighted (#3594)

This commit is contained in:
Joe Boccanfuso 2023-08-16 12:51:26 -04:00 committed by GitHub
parent 3fe676638b
commit 0f9b36906f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 121 additions and 29 deletions

View File

@ -23,6 +23,7 @@ import CornerstoneOverlays from './Overlays/CornerstoneOverlays';
import getSOPInstanceAttributes from '../utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
import CornerstoneServices from '../types/CornerstoneServices';
import CinePlayer from '../components/CinePlayer';
import { Types } from '@ohif/core';
const STACK = 'stack';
@ -264,7 +265,14 @@ const OHIFCornerstoneViewport = React.memo(props => {
useEffect(() => {
const { unsubscribe } = displaySetService.subscribe(
displaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED,
async invalidatedDisplaySetInstanceUID => {
async ({
displaySetInstanceUID: invalidatedDisplaySetInstanceUID,
invalidateData,
}: Types.DisplaySetSeriesMetadataInvalidatedEvent) => {
if (!invalidateData) {
return;
}
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(
viewportIndex
);

View File

@ -1123,6 +1123,8 @@ class SegmentationService extends PubSubService {
);
}
this._setDisplaySetIsHydrated(segmentationId, true);
segmentation.hydrated = true;
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(
segmentIndex: number,
alpha: number,
@ -1303,6 +1317,8 @@ class SegmentationService extends PubSubService {
}
}
this._setDisplaySetIsHydrated(segmentationId, false);
this._broadcastEvent(this.EVENTS.SEGMENTATION_REMOVED, {
segmentationId,
});

View File

@ -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 () => {
SubscriptionDisplaySetsAdded.unsubscribe();
SubscriptionDisplaySetsChanged.unsubscribe();
SubscriptionDisplaySetMetaDataInvalidated.unsubscribe();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@ -335,6 +348,7 @@ function _mapDisplaySets(displaySets, thumbnailImageSrcMap) {
displaySetInstanceUID: ds.displaySetInstanceUID,
// .. Any other data to pass
},
isHydratedForDerivedDisplaySet: ds.isHydrated,
});
});

View File

@ -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 () => {
SubscriptionDisplaySetsAdded.unsubscribe();
SubscriptionDisplaySetsChanged.unsubscribe();
SubscriptionDisplaySetMetaDataInvalidated.unsubscribe();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
@ -467,6 +487,7 @@ function _mapDisplaySets(
// .. Any other data to pass
},
isTracked: trackedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID),
isHydratedForDerivedDisplaySet: ds.isHydrated,
viewportIdentificator,
};

View File

@ -1,17 +1,8 @@
import { ExtensionManager } from '../../extensions';
import { InstanceMetadata } from '../../types';
import { DisplaySet, InstanceMetadata } from '../../types';
import { PubSubService } from '../_shared/pubSubServiceInterface';
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>();
/**
@ -154,7 +145,10 @@ export default class DisplaySetService extends PubSubService {
return displaySet;
}
public setDisplaySetMetadataInvalidated(displaySetInstanceUID: string): void {
public setDisplaySetMetadataInvalidated(
displaySetInstanceUID: string,
invalidateData = true
): void {
const displaySet = this.getDisplaySetByUID(displaySetInstanceUID);
if (!displaySet) {
@ -162,10 +156,10 @@ export default class DisplaySetService extends PubSubService {
}
// broadcast event to update listeners with the new displaySets
this._broadcastEvent(
EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED,
displaySetInstanceUID
);
this._broadcastEvent(EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, {
displaySetInstanceUID,
invalidateData,
});
}
public deleteDisplaySet(displaySetInstanceUID) {

View 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;
};

View File

@ -2,7 +2,6 @@ import * as Extensions from '../extensions/ExtensionManager';
import * as HangingProtocol from './HangingProtocol';
import Services from './Services';
import Hotkey from '../classes/Hotkey';
import { DisplaySet } from '../services/DisplaySetService/DisplaySetService';
import { DataSourceDefinition } from './DataSource';
export * from '../services/CustomizationService/types';
@ -10,6 +9,7 @@ export * from '../services/CustomizationService/types';
export * from './AppConfig';
export * from './Consumer';
export * from './Command';
export * from './DisplaySet';
export * from './StudyMetadata';
export * from './PanelModule';
export * from './IPubSub';
@ -19,11 +19,4 @@ export * from './Color';
* Export the types used within the various services and managers, but
* not the services/managers themselves, which are exported at the top level.
*/
export {
Extensions,
HangingProtocol,
Services,
Hotkey,
DisplaySet,
DataSourceDefinition,
};
export { Extensions, HangingProtocol, Services, Hotkey, DataSourceDefinition };

View File

@ -160,6 +160,13 @@ The following properties can be added to the `configuration` property of each da
##### `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
:::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).
:::
![toolbarModule-layout](../../assets/img/uploader.gif)
#### `singlepart`

View File

@ -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_CHANGED | Fires when a displayset is changed |
| 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
@ -60,3 +61,5 @@ Let's find out about the public API for `DisplaySetService`.
- `deleteDisplaySet`: Deletes the displaySets from the displaySets cache
- `addActiveDisplaySets`: Adds a new display set independently of the make operation.
- `setDisplaySetMetadataInvalidated`: Fires the `DISPLAY_SET_SERIES_METADATA_INVALIDATED` event.

View File

@ -36,6 +36,7 @@ const ThumbnailList = ({
imageSrc,
messages,
imageAltText,
isHydratedForDerivedDisplaySet,
}) => {
const isActive = activeDisplaySetInstanceUIDs.includes(
displaySetInstanceUID
@ -104,6 +105,9 @@ const ThumbnailList = ({
onThumbnailDoubleClick(displaySetInstanceUID)
}
viewportIdentificator={viewportIdentificator}
isHydratedForDerivedDisplaySet={
isHydratedForDerivedDisplaySet
}
/>
);
default:

View File

@ -20,6 +20,7 @@ const ThumbnailNoImage = ({
messages,
dragData,
isActive,
isHydratedForDerivedDisplaySet,
}) => {
const [collectedProps, drag, dragPreview] = useDrag({
type: 'displayset',
@ -32,8 +33,8 @@ const ThumbnailNoImage = ({
return (
<div
className={classnames(
'flex flex-row flex-1 cursor-pointer outline-none border-transparent hover:border-blue-300 focus:border-blue-300 rounded select-none',
isActive ? 'border-2 border-primary-light' : 'border'
'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 border-transparent'
)}
style={{
padding: isActive ? '11px' : '12px',
@ -48,12 +49,27 @@ const ThumbnailNoImage = ({
<div ref={drag}>
<div className="flex flex-col flex-1">
<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
position="bottom"
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}
</div>
</Tooltip>
@ -104,6 +120,7 @@ ThumbnailNoImage.propTypes = {
onDoubleClick: PropTypes.func.isRequired,
messages: PropTypes.object,
isActive: PropTypes.bool.isRequired,
isHydratedForDerivedDisplaySet: PropTypes.bool,
};
export default ThumbnailNoImage;