From a433d406e2cac13f644203996c682260b54e8865 Mon Sep 17 00:00:00 2001 From: Ibrahim <93064150+IbrahimCSAE@users.noreply.github.com> Date: Thu, 20 Jun 2024 16:58:59 -0400 Subject: [PATCH] feat(sort): custom series sort in study panel (#4214) --- extensions/default/src/commandsModule.ts | 5 -- .../default/src/getCustomizationModule.tsx | 23 ++++++ .../PanelStudyBrowserTracking.tsx | 6 +- modes/longitudinal/src/index.ts | 1 + platform/app/public/config/default.js | 5 +- platform/app/public/config/netlify.js | 1 + .../CustomizationService.ts | 3 +- .../DisplaySetService/DisplaySetService.ts | 20 +++++ platform/core/src/types/AppTypes.ts | 1 + .../docs/configuration/configurationFiles.md | 1 + platform/docs/docs/faq/study-sorting.png | Bin 0 -> 41503 bytes platform/docs/docs/faq/technical.md | 75 ++++++++++++++++++ .../components/StudyBrowser/StudyBrowser.tsx | 6 +- .../StudyBrowserSort/StudyBrowserSort.tsx | 73 +++++++++++++++++ .../src/components/StudyBrowserSort/index.js | 3 + platform/ui/src/components/index.js | 2 + platform/ui/src/index.js | 1 + 17 files changed, 211 insertions(+), 15 deletions(-) create mode 100644 platform/docs/docs/faq/study-sorting.png create mode 100644 platform/ui/src/components/StudyBrowserSort/StudyBrowserSort.tsx create mode 100644 platform/ui/src/components/StudyBrowserSort/index.js diff --git a/extensions/default/src/commandsModule.ts b/extensions/default/src/commandsModule.ts index 7ed6886e3f6..1b0941ad9b7 100644 --- a/extensions/default/src/commandsModule.ts +++ b/extensions/default/src/commandsModule.ts @@ -505,13 +505,8 @@ const commandsModule = ({ }: UpdateViewportDisplaySetParams) => { const nonImageModalities = ['SR', 'SEG', 'SM', 'RTSTRUCT', 'RTPLAN', 'RTDOSE']; - // Sort the display sets as per the hanging protocol service viewport/display set scoring system. - // The thumbnail list uses the same sorting. - const dsSortFn = hangingProtocolService.getDisplaySetSortFunction(); const currentDisplaySets = [...displaySetService.activeDisplaySets]; - currentDisplaySets.sort(dsSortFn); - const { activeViewportId, viewports, isHangingProtocolLayout } = viewportGridService.getState(); diff --git a/extensions/default/src/getCustomizationModule.tsx b/extensions/default/src/getCustomizationModule.tsx index d004d88f148..9f7153af212 100644 --- a/extensions/default/src/getCustomizationModule.tsx +++ b/extensions/default/src/getCustomizationModule.tsx @@ -4,6 +4,9 @@ import DataSourceSelector from './Panels/DataSourceSelector'; import { ProgressDropdownWithService } from './Components/ProgressDropdownWithService'; import DataSourceConfigurationComponent from './Components/DataSourceConfigurationComponent'; import { GoogleCloudDataSourceConfigurationAPI } from './DataSourceConfigurationAPI/GoogleCloudDataSourceConfigurationAPI'; +import { utils } from '@ohif/core'; + +const formatDate = utils.formatDate; /** * @@ -162,6 +165,26 @@ export default function getCustomizationModule({ servicesManager, extensionManag id: 'progressDropdownWithServiceComponent', component: ProgressDropdownWithService, }, + { + id: 'studyBrowser.sortFunctions', + merge: 'Append', + values: [ + { + label: 'Series Number', + sortFunction: (a, b) => { + return a?.SeriesNumber - b?.SeriesNumber; + }, + }, + { + label: 'Series Date', + sortFunction: (a, b) => { + const dateA = new Date(formatDate(a?.SeriesDate)); + const dateB = new Date(formatDate(b?.SeriesDate)); + return dateB.getTime() - dateA.getTime(); + }, + }, + ], + }, ], }, ]; diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx index a59a40f4c1d..c1909c8cd4b 100644 --- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx +++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx @@ -272,11 +272,7 @@ function PanelStudyBrowserTracking({ }; }, [thumbnailImageSrcMap, trackedSeries, viewports, dataSource, displaySetService]); - const tabs = createStudyBrowserTabs( - StudyInstanceUIDs, - studyDisplayList, - displaySets, - ); + const tabs = createStudyBrowserTabs(StudyInstanceUIDs, studyDisplayList, displaySets); // TODO: Should not fire this on "close" function _handleStudyClick(StudyInstanceUID) { diff --git a/modes/longitudinal/src/index.ts b/modes/longitudinal/src/index.ts index ac05c4e84a7..0da0a69cc3f 100644 --- a/modes/longitudinal/src/index.ts +++ b/modes/longitudinal/src/index.ts @@ -90,6 +90,7 @@ function modeFactory({ modeConfiguration }) { // }, // ]); + // Init Default and SR ToolGroups initToolGroups(extensionManager, toolGroupService, commandsManager, this.labelConfig); diff --git a/platform/app/public/config/default.js b/platform/app/public/config/default.js index 89154c606b6..9e1aa717e7f 100644 --- a/platform/app/public/config/default.js +++ b/platform/app/public/config/default.js @@ -1,6 +1,6 @@ /** @type {AppTypes.Config} */ -const config = { +window.config = { routerBasename: '/', // whiteLabeling: {}, extensions: [], @@ -13,6 +13,7 @@ const config = { showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, + experimentalStudyBrowserSort: true, strictZSpacingForVolumeViewport: true, groupEnabledModesFirst: true, maxNumRequests: { @@ -289,5 +290,3 @@ const config = { }, ], }; - -window.config = config; diff --git a/platform/app/public/config/netlify.js b/platform/app/public/config/netlify.js index 1369fa3bd4d..ce5ec9e3a85 100644 --- a/platform/app/public/config/netlify.js +++ b/platform/app/public/config/netlify.js @@ -9,6 +9,7 @@ window.config = { showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, + experimentalStudyBrowserSort: false, strictZSpacingForVolumeViewport: true, groupEnabledModesFirst: true, // filterQueryParam: false, diff --git a/platform/core/src/services/CustomizationService/CustomizationService.ts b/platform/core/src/services/CustomizationService/CustomizationService.ts index df4d0b94df0..4323bf88e52 100644 --- a/platform/core/src/services/CustomizationService/CustomizationService.ts +++ b/platform/core/src/services/CustomizationService/CustomizationService.ts @@ -177,9 +177,10 @@ export default class CustomizationService extends PubSubService { defaultCustomization || {}; + // use the source merge type if not provided then fallback to merge this.modeCustomizations.set( customizationId, - this.mergeValue(sourceCustomization, customization, merge) + this.mergeValue(sourceCustomization, customization, sourceCustomization.merge ?? merge) ); this.transformedCustomizations.clear(); this._broadcastEvent(this.EVENTS.CUSTOMIZATION_MODIFIED, { diff --git a/platform/core/src/services/DisplaySetService/DisplaySetService.ts b/platform/core/src/services/DisplaySetService/DisplaySetService.ts index 3db737c0cbe..ac992248420 100644 --- a/platform/core/src/services/DisplaySetService/DisplaySetService.ts +++ b/platform/core/src/services/DisplaySetService/DisplaySetService.ts @@ -424,4 +424,24 @@ export default class DisplaySetService extends PubSubService { return result; } + + /** + * + * @param sortFn function to sort the display sets + * @param direction direction to sort the display sets + * @returns void + */ + public sortDisplaySets( + sortFn: (a: DisplaySet, b: DisplaySet) => number, + direction: string, + suppressEvent = false + ): void { + this.activeDisplaySets.sort(sortFn); + if (direction === 'descending') { + this.activeDisplaySets.reverse(); + } + if (!suppressEvent) { + this._broadcastEvent(EVENTS.DISPLAY_SETS_CHANGED, this.activeDisplaySets); + } + } } diff --git a/platform/core/src/types/AppTypes.ts b/platform/core/src/types/AppTypes.ts index f5c914e2026..c7f953e8a02 100644 --- a/platform/core/src/types/AppTypes.ts +++ b/platform/core/src/types/AppTypes.ts @@ -70,6 +70,7 @@ declare global { customizationService?: any; extensions?: string[]; modes?: string[]; + experimentalStudyBrowserSort?: boolean; defaultDataSourceName?: string; hotkeys?: Record | Hotkey[]; useSharedArrayBuffer?: 'AUTO' | 'FALSE' | 'TRUE'; diff --git a/platform/docs/docs/configuration/configurationFiles.md b/platform/docs/docs/configuration/configurationFiles.md index cbcdf82345f..fefd36af0f4 100644 --- a/platform/docs/docs/configuration/configurationFiles.md +++ b/platform/docs/docs/configuration/configurationFiles.md @@ -124,6 +124,7 @@ Here are a list of some options available: - `acceptHeader` : accept header to request specific dicom transfer syntax ex : [ 'multipart/related; type=image/jls; q=1', 'multipart/related; type=application/octet-stream; q=0.1' ] - `investigationalUseDialog`: This should contain an object with `option` value, it can be either `always` which always shows the dialog once per session, `never` which never shows the dialog, or `configure` which shows the dialog once and won't show it again until a set number of days defined by the user, if it's set to configure, you are required to add an additional property `days` which is the number of days to wait before showing the dialog again. - `groupEnabledModesFirst`: boolean, if set to true, all valid modes for the study get grouped together first, then the rest of the modes. If false, all modes are shown in the order they are defined in the configuration. +- `experimentalStudyBrowserSort`: boolean, if set to true, you will get the experimental StudyBrowserSort component in the UI, which displays a list of sort functions that the displaySets can be sorted by, the sort reflects in all part of the app including the thumbnail/study panel. These sort functions are defined in the customizationModule and can be expanded by users. - `disableConfirmationPrompts`: boolean, if set to true, it skips confirmation prompts for measurement tracking and hydration. - `showPatientInfo`: string, if set to 'visible', the patient info header will be shown and its initial state is expanded. If set to 'visibleCollapsed', the patient info header will be shown but it's initial state is collapsed. If set to 'disabled', the patient info header will never be shown, and if set to 'visibleReadOnly', the patient info header will be shown and always expanded. - `requestTransferSyntaxUID` : Request a specific Transfer syntax from dicom web server ex: 1.2.840.10008.1.2.4.80 (applied only if acceptHeader is not set) diff --git a/platform/docs/docs/faq/study-sorting.png b/platform/docs/docs/faq/study-sorting.png new file mode 100644 index 0000000000000000000000000000000000000000..46d2be1ec46c5a8f02b63b07706bfad8a269430b GIT binary patch literal 41503 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 UjY#79K)^>{M&)I>lu6+K0D`|RPyhe` literal 0 HcmV?d00001 diff --git a/platform/docs/docs/faq/technical.md b/platform/docs/docs/faq/technical.md index 224dd1908ba..adc33d95a54 100644 --- a/platform/docs/docs/faq/technical.md +++ b/platform/docs/docs/faq/technical.md @@ -272,3 +272,78 @@ to which then it will look like ![alt text](faq-measure-5.png) + + + +## How do I sort the series in the study panel by a specific value + +You need to enable the experimental StudyBrowserSort component by setting the `experimentalStudyBrowserSort` to true in your config file. This will add a dropdown in the study panel to sort the series by a specific value. This component is experimental +since we are re-deigning the study panel and it might change in the future, but the functionality will remain the same. + +```js +{ + experimentalStudyBrowserSort: true, +} +``` +The component will appear in the study panel and will allow you to sort the series by a specific value. It comes with 3 default sorting functions, Series Number, Series Image Count, and Series Date. + +You can sort the series in the study panel by a specific value by adding a custom sorting function in the customizationModule, you can use the existing customizationModule in `extensions/default/src/getCustomizationModule.tsx` or create your own in your extension. + +The value to be used for the entry is `studyBrowser.sortFunctions` and should be under the `default` key. + +### Example + +```js +export default function getCustomizationModule({ servicesManager, extensionManager }) { + return [ + { + name: 'default', + value: [ + + { + id: 'studyBrowser.sortFunctions', + values: [ + { + label: 'Series Number', + sortFunction: (a, b) => { + return a?.SeriesNumber - b?.SeriesNumber; + }, + }, + // Add more sort functions as needed + ], + }, + ], + }, + ]; +} +``` + +### Explanation +This function will be retrieved by the StudyBrowserSort component and will be used to sort all displaySets, it will reflect in all parts of the app since it works at the displaySetService level, which means the thumbnails in the study panel will also be sorted by the desired value. +You can define multiple functions and pick which sort to use via the dropdown in the StudyBrowserSort component that appears in the study panel. + + +## How can i change the sorting of the thumbnail / study panel / study browser +We are currently redesigning the study panel and the study browser. During this process, you can enable our undesigned component via the `experimentalStudyBrowserSort` flag. This will look like: + +![alt text](study-sorting.png) + +You can also add your own sorting functions by utilizing the `customizationService` and adding the `studyBrowser.sortFunctions` key, as shown below: + +``` +customizationService.addModeCustomizations([ + { + id: 'studyBrowser.sortFunctions', + values: [{ + label: 'Series Images', + sortFunction: (a, b) => { + return a?.numImageFrames - b?.numImageFrames; + }, + }], + }, +]); +``` + +:::note +Notice the arrays and objects, the values are arrays +::: diff --git a/platform/ui/src/components/StudyBrowser/StudyBrowser.tsx b/platform/ui/src/components/StudyBrowser/StudyBrowser.tsx index ddf4478ea39..91fd6b0e4a8 100644 --- a/platform/ui/src/components/StudyBrowser/StudyBrowser.tsx +++ b/platform/ui/src/components/StudyBrowser/StudyBrowser.tsx @@ -7,6 +7,7 @@ import LegacyButtonGroup from '../LegacyButtonGroup'; import LegacyButton from '../LegacyButton'; import ThumbnailList from '../ThumbnailList'; import { StringNumber } from '../../types'; +import StudyBrowserSort from '../StudyBrowserSort'; const getTrackedSeries = displaySets => { let trackedSeries = 0; @@ -73,7 +74,7 @@ const StudyBrowser = ({ return (
{/* TODO Revisit design of LegacyButtonGroup later - for now use LegacyButton for its children.*/} @@ -111,6 +112,9 @@ const StudyBrowser = ({ ); })} + {window.config.experimentalStudyBrowserSort && ( + + )}
{getTabContent()} diff --git a/platform/ui/src/components/StudyBrowserSort/StudyBrowserSort.tsx b/platform/ui/src/components/StudyBrowserSort/StudyBrowserSort.tsx new file mode 100644 index 00000000000..a9bfdd81ddb --- /dev/null +++ b/platform/ui/src/components/StudyBrowserSort/StudyBrowserSort.tsx @@ -0,0 +1,73 @@ +import React, { useEffect, useState } from 'react'; +import Icon from '../Icon'; + +export default function StudyBrowserSort({ servicesManager }: withAppTypes) { + const { customizationService, displaySetService } = servicesManager.services; + const { values: sortFunctions } = customizationService.get('studyBrowser.sortFunctions'); + + const [selectedSort, setSelectedSort] = useState(sortFunctions[0]); + const [sortDirection, setSortDirection] = useState('ascending'); + + const handleSortChange = event => { + const selectedSortFunction = sortFunctions.find(sort => sort.label === event.target.value); + setSelectedSort(selectedSortFunction); + }; + + const toggleSortDirection = e => { + e.stopPropagation(); + setSortDirection(prevDirection => (prevDirection === 'ascending' ? 'descending' : 'ascending')); + }; + + useEffect(() => { + displaySetService.sortDisplaySets(selectedSort.sortFunction, sortDirection); + }, [displaySetService, selectedSort, sortDirection]); + + useEffect(() => { + const SubscriptionDisplaySetsChanged = displaySetService.subscribe( + displaySetService.EVENTS.DISPLAY_SETS_CHANGED, + () => { + displaySetService.sortDisplaySets(selectedSort.sortFunction, sortDirection, true); + } + ); + const SubscriptionDisplaySetMetaDataInvalidated = displaySetService.subscribe( + displaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, + () => { + displaySetService.sortDisplaySets(selectedSort.sortFunction, sortDirection, true); + } + ); + + return () => { + SubscriptionDisplaySetsChanged.unsubscribe(); + SubscriptionDisplaySetMetaDataInvalidated.unsubscribe(); + }; + }, [displaySetService, selectedSort, sortDirection]); + + return ( +
+ + +
+ ); +} diff --git a/platform/ui/src/components/StudyBrowserSort/index.js b/platform/ui/src/components/StudyBrowserSort/index.js new file mode 100644 index 00000000000..1f9a4601ebc --- /dev/null +++ b/platform/ui/src/components/StudyBrowserSort/index.js @@ -0,0 +1,3 @@ +import StudyBrowserSort from './StudyBrowserSort'; + +export default StudyBrowserSort; diff --git a/platform/ui/src/components/index.js b/platform/ui/src/components/index.js index f3e62c84726..52020efd55a 100644 --- a/platform/ui/src/components/index.js +++ b/platform/ui/src/components/index.js @@ -95,6 +95,7 @@ import InvestigationalUseDialog from './InvestigationalUseDialog'; import MeasurementItem from './MeasurementTable/MeasurementItem'; import LayoutPreset from './LayoutPreset'; import ActionButtons from './ActionButtons'; +import StudyBrowserSort from './StudyBrowserSort'; export { ActionButtons, @@ -198,4 +199,5 @@ export { ToolSettings, Toolbox, InvestigationalUseDialog, + StudyBrowserSort, }; diff --git a/platform/ui/src/index.js b/platform/ui/src/index.js index 214b4584a8e..b240a6e4b5a 100644 --- a/platform/ui/src/index.js +++ b/platform/ui/src/index.js @@ -132,6 +132,7 @@ export { Toolbox, InvestigationalUseDialog, LayoutPreset, + StudyBrowserSort, } from './components'; export { useSessionStorage } from './hooks';