feat(command): Added command and hotkey binding for navigate to next and previous display set. (#3409)
* fix(command): Added command and hotkey binding for navigate to next and previous display set. * Centralized the sorting (function) of display sets since at least three areas sort display sets: hanging protocols, study/thumbnail browser and updateViewportDisplaySet command. * Renamed some methods for clarity. * Scrolling active thumbnail into view for an updateViewportDisplaySet * Added a null check. * Added check to minimize thumbnail scrolling.
This commit is contained in:
parent
4a17177d81
commit
685520acab
@ -23,6 +23,11 @@ export type HangingProtocolParams = {
|
|||||||
stageId?: string;
|
stageId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UpdateViewportDisplaySetParams = {
|
||||||
|
direction: number;
|
||||||
|
excludeNonImageModalities?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a command is a hanging protocol one.
|
* Determine if a command is a hanging protocol one.
|
||||||
* For now, just use the two hanging protocol commands that are in this
|
* For now, just use the two hanging protocol commands that are in this
|
||||||
@ -541,6 +546,130 @@ const commandsModule = ({
|
|||||||
overlays.item(i).classList.toggle('hidden');
|
overlays.item(i).classList.toggle('hidden');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
scrollActiveThumbnailIntoView: () => {
|
||||||
|
const { activeViewportIndex, viewports } = viewportGridService.getState();
|
||||||
|
|
||||||
|
if (
|
||||||
|
!viewports ||
|
||||||
|
activeViewportIndex < 0 ||
|
||||||
|
activeViewportIndex > viewports.length - 1
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeViewport = viewports[activeViewportIndex];
|
||||||
|
const activeDisplaySetInstanceUID =
|
||||||
|
activeViewport.displaySetInstanceUIDs[0];
|
||||||
|
|
||||||
|
const thumbnailList = document.querySelector('#ohif-thumbnail-list');
|
||||||
|
|
||||||
|
if (!thumbnailList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const thumbnailListBounds = thumbnailList.getBoundingClientRect();
|
||||||
|
|
||||||
|
const thumbnail = document.querySelector(
|
||||||
|
`#thumbnail-${activeDisplaySetInstanceUID}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!thumbnail) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const thumbnailBounds = thumbnail.getBoundingClientRect();
|
||||||
|
|
||||||
|
// This only handles a vertical thumbnail list.
|
||||||
|
if (
|
||||||
|
thumbnailBounds.top >= thumbnailListBounds.top &&
|
||||||
|
thumbnailBounds.top <= thumbnailListBounds.bottom
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbnail.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
},
|
||||||
|
|
||||||
|
updateViewportDisplaySet: ({
|
||||||
|
direction,
|
||||||
|
excludeNonImageModalities,
|
||||||
|
}: 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 { activeViewportIndex, viewports } = viewportGridService.getState();
|
||||||
|
|
||||||
|
const { displaySetInstanceUIDs } = viewports[activeViewportIndex];
|
||||||
|
|
||||||
|
const activeDisplaySetIndex = currentDisplaySets.findIndex(displaySet =>
|
||||||
|
displaySetInstanceUIDs.includes(displaySet.displaySetInstanceUID)
|
||||||
|
);
|
||||||
|
|
||||||
|
let displaySetIndexToShow: number;
|
||||||
|
|
||||||
|
for (
|
||||||
|
displaySetIndexToShow = activeDisplaySetIndex + direction;
|
||||||
|
displaySetIndexToShow > -1 &&
|
||||||
|
displaySetIndexToShow < currentDisplaySets.length;
|
||||||
|
displaySetIndexToShow += direction
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!excludeNonImageModalities ||
|
||||||
|
!nonImageModalities.includes(
|
||||||
|
currentDisplaySets[displaySetIndexToShow].Modality
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
displaySetIndexToShow < 0 ||
|
||||||
|
displaySetIndexToShow >= currentDisplaySets.length
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { displaySetInstanceUID } = currentDisplaySets[
|
||||||
|
displaySetIndexToShow
|
||||||
|
];
|
||||||
|
|
||||||
|
let updatedViewports = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
|
||||||
|
activeViewportIndex,
|
||||||
|
displaySetInstanceUID
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
uiNotificationService.show({
|
||||||
|
title: 'Navigate Viewport Display Set',
|
||||||
|
message:
|
||||||
|
'The requested display sets could not be added to the viewport due to a mismatch in the Hanging Protocol rules.',
|
||||||
|
type: 'info',
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
viewportGridService.setDisplaySetsForViewports(updatedViewports);
|
||||||
|
|
||||||
|
setTimeout(() => actions.scrollActiveThumbnailIntoView(), 0);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const definitions = {
|
const definitions = {
|
||||||
@ -598,6 +727,11 @@ const commandsModule = ({
|
|||||||
openDICOMTagViewer: {
|
openDICOMTagViewer: {
|
||||||
commandFn: actions.openDICOMTagViewer,
|
commandFn: actions.openDICOMTagViewer,
|
||||||
},
|
},
|
||||||
|
updateViewportDisplaySet: {
|
||||||
|
commandFn: actions.updateViewportDisplaySet,
|
||||||
|
storeContexts: [],
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { utils } from '@ohif/core';
|
import { HangingProtocolService, utils } from '@ohif/core';
|
||||||
import {
|
import {
|
||||||
StudyBrowser,
|
StudyBrowser,
|
||||||
useImageViewer,
|
useImageViewer,
|
||||||
@ -292,7 +292,8 @@ function PanelStudyBrowserTracking({
|
|||||||
const tabs = _createStudyBrowserTabs(
|
const tabs = _createStudyBrowserTabs(
|
||||||
StudyInstanceUIDs,
|
StudyInstanceUIDs,
|
||||||
studyDisplayList,
|
studyDisplayList,
|
||||||
displaySets
|
displaySets,
|
||||||
|
hangingProtocolService
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Should not fire this on "close"
|
// TODO: Should not fire this on "close"
|
||||||
@ -601,7 +602,8 @@ function _getComponentType(Modality) {
|
|||||||
function _createStudyBrowserTabs(
|
function _createStudyBrowserTabs(
|
||||||
primaryStudyInstanceUIDs,
|
primaryStudyInstanceUIDs,
|
||||||
studyDisplayList,
|
studyDisplayList,
|
||||||
displaySets
|
displaySets,
|
||||||
|
hangingProtocolService
|
||||||
) {
|
) {
|
||||||
const primaryStudies = [];
|
const primaryStudies = [];
|
||||||
const recentStudies = [];
|
const recentStudies = [];
|
||||||
@ -615,9 +617,8 @@ function _createStudyBrowserTabs(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Sort them
|
// Sort them
|
||||||
const sortedDisplaySetsForStudy = utils.sortBySeriesDate(
|
const dsSortFn = hangingProtocolService.getDisplaySetSortFunction();
|
||||||
displaySetsForStudy
|
displaySetsForStudy.sort(dsSortFn);
|
||||||
);
|
|
||||||
|
|
||||||
/* Sort by series number, then by series date
|
/* Sort by series number, then by series date
|
||||||
displaySetsForStudy.sort((a, b) => {
|
displaySetsForStudy.sort((a, b) => {
|
||||||
|
|||||||
@ -76,18 +76,24 @@ const bindings = [
|
|||||||
keys: ['left'],
|
keys: ['left'],
|
||||||
isEditable: true,
|
isEditable: true,
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// commandName: 'nextViewportDisplaySet',
|
commandName: 'updateViewportDisplaySet',
|
||||||
// label: 'Next Series',
|
commandOptions: {
|
||||||
// keys: ['pageup'],
|
direction: -1,
|
||||||
// isEditable: true,
|
},
|
||||||
// },
|
label: 'Previous Series',
|
||||||
// {
|
keys: ['pageup'],
|
||||||
// commandName: 'previousViewportDisplaySet',
|
isEditable: true,
|
||||||
// label: 'Previous Series',
|
},
|
||||||
// keys: ['pagedown'],
|
{
|
||||||
// isEditable: true,
|
commandName: 'updateViewportDisplaySet',
|
||||||
// },
|
commandOptions: {
|
||||||
|
direction: 1,
|
||||||
|
},
|
||||||
|
label: 'Next Series',
|
||||||
|
keys: ['pagedown'],
|
||||||
|
isEditable: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
commandName: 'nextStage',
|
commandName: 'nextStage',
|
||||||
context: 'DEFAULT',
|
context: 'DEFAULT',
|
||||||
|
|||||||
@ -1033,6 +1033,23 @@ export default class HangingProtocolService extends PubSubService {
|
|||||||
return this._matchViewport(useViewport, options);
|
return this._matchViewport(useViewport, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a sort function that is consistent with the display set sorting performed
|
||||||
|
* to match display sets to viewports.
|
||||||
|
* @returns a display set sort function
|
||||||
|
*/
|
||||||
|
public getDisplaySetSortFunction(): (
|
||||||
|
displaySetA: IDisplaySet,
|
||||||
|
displaySetB: IDisplaySet
|
||||||
|
) => number {
|
||||||
|
return (displaySetA, displaySetB) => {
|
||||||
|
const seriesA = this._getSeriesSortInfoForDisplaySetSort(displaySetA);
|
||||||
|
const seriesB = this._getSeriesSortInfoForDisplaySetSort(displaySetB);
|
||||||
|
|
||||||
|
return sortBy(this._getSeriesFieldForDisplaySetSort())(seriesA, seriesB);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the viewports with the selected protocol stage.
|
* Updates the viewports with the selected protocol stage.
|
||||||
*/
|
*/
|
||||||
@ -1461,7 +1478,7 @@ export default class HangingProtocolService extends PubSubService {
|
|||||||
sortingInfo: {
|
sortingInfo: {
|
||||||
score: totalMatchScore,
|
score: totalMatchScore,
|
||||||
study: study.StudyInstanceUID,
|
study: study.StudyInstanceUID,
|
||||||
series: parseInt(displaySet.SeriesNumber),
|
...this._getSeriesSortInfoForDisplaySetSort(displaySet),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1484,9 +1501,7 @@ export default class HangingProtocolService extends PubSubService {
|
|||||||
name: 'study',
|
name: 'study',
|
||||||
reverse: true,
|
reverse: true,
|
||||||
},
|
},
|
||||||
{
|
this._getSeriesFieldForDisplaySetSort()
|
||||||
name: 'series',
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
matchingScores.sort((a, b) =>
|
matchingScores.sort((a, b) =>
|
||||||
sortingFunction(a.sortingInfo, b.sortingInfo)
|
sortingFunction(a.sortingInfo, b.sortingInfo)
|
||||||
@ -1506,6 +1521,19 @@ export default class HangingProtocolService extends PubSubService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getSeriesSortInfoForDisplaySetSort(displaySet) {
|
||||||
|
return {
|
||||||
|
[this._getSeriesFieldForDisplaySetSort().name]:
|
||||||
|
displaySet.SeriesNumber != null
|
||||||
|
? parseInt(displaySet.SeriesNumber)
|
||||||
|
: parseInt(displaySet.seriesNumber),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getSeriesFieldForDisplaySetSort() {
|
||||||
|
return { name: 'series' };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the next stage is available
|
* Check if the next stage is available
|
||||||
* @return {Boolean} True if next stage is available or false otherwise
|
* @return {Boolean} True if next stage is available or false otherwise
|
||||||
|
|||||||
@ -12,7 +12,10 @@ const ThumbnailList = ({
|
|||||||
activeDisplaySetInstanceUIDs = [],
|
activeDisplaySetInstanceUIDs = [],
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="py-3 bg-black overflow-y-hidden ohif-scrollbar study-min-height">
|
<div
|
||||||
|
id="ohif-thumbnail-list"
|
||||||
|
className="py-3 bg-black overflow-y-hidden ohif-scrollbar study-min-height"
|
||||||
|
>
|
||||||
{thumbnails.map(
|
{thumbnails.map(
|
||||||
({
|
({
|
||||||
displaySetInstanceUID,
|
displaySetInstanceUID,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user