feat(multimonitor): Add simple multi-monitor support to open another study(#4178)
This commit is contained in:
1 parent
a4a6de07f4
commit
07c628e689
49 files changed
+1476
-367
No files matched your search
@@ -1,15 +1,10 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { utils } from '@ohif/core';
|
||||
import { useViewportGrid } from '@ohif/ui-next';
|
||||
import { MeasurementTable } from '@ohif/ui-next';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { useMeasurements } from '../hooks/useMeasurements';
|
||||
|
||||
const {
|
||||
filterAdditionalFindings: filterAdditionalFinding,
|
||||
filterOr,
|
||||
filterAny,
|
||||
} = utils.MeasurementFilters;
|
||||
const { filterAdditionalFindings: filterAdditionalFinding, filterAny } = utils.MeasurementFilters;
|
||||
|
||||
export type withAppAndFilters = withAppTypes & {
|
||||
measurementFilter: (item) => boolean;
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
Icons,
|
||||
Button,
|
||||
} from '@ohif/ui-next';
|
||||
|
||||
/**
|
||||
* The default sub-menu appearance and setup is defined here, but this can be
|
||||
* replaced by
|
||||
*/
|
||||
const getMenuItemsDefault = ({ commandsManager, items, servicesManager, ...props }) => {
|
||||
const { customizationService } = servicesManager.services;
|
||||
|
||||
// This allows replacing the default child item for menus, whereas the entire
|
||||
// getMenuItems can also be replaced by providing it to the MoreDropdownMenu
|
||||
const menuContent = customizationService.getCustomization('ohif.menuContent');
|
||||
|
||||
return (
|
||||
<DropdownMenuContent
|
||||
hideWhenDetached
|
||||
align="start"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
{items?.map(item =>
|
||||
menuContent.content({
|
||||
key: item.id,
|
||||
item,
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
...props,
|
||||
})
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* The component provides a ... sub-menu for various components which appears
|
||||
* on hover over the main component.
|
||||
*
|
||||
* @param bindProps - properties to define the sub-menu
|
||||
* @returns Component bound to the bindProps
|
||||
*/
|
||||
export default function MoreDropdownMenu(bindProps) {
|
||||
const {
|
||||
menuItemsKey,
|
||||
getMenuItems = getMenuItemsDefault,
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
} = bindProps;
|
||||
const { customizationService } = servicesManager.services;
|
||||
|
||||
const items = customizationService.getCustomization(menuItemsKey)?.value;
|
||||
|
||||
if (!items) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function BoundMoreDropdownMenu(props) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="hidden group-hover:inline-flex data-[state=open]:inline-flex"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Icons.More />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
{getMenuItems({
|
||||
...props,
|
||||
commandsManager: commandsManager,
|
||||
servicesManager: servicesManager,
|
||||
items,
|
||||
})}
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
return BoundMoreDropdownMenu;
|
||||
}
|
||||
@@ -47,15 +47,22 @@ export default class ContextMenuController {
|
||||
}
|
||||
|
||||
const { event, subMenu, menuId, menus, selectorProps } = contextMenuProps;
|
||||
if (!menus) {
|
||||
console.warn('No menus found for', menuId);
|
||||
return;
|
||||
}
|
||||
|
||||
const { locking, visibility } = CsAnnotation;
|
||||
const targetAnnotationId = selectorProps?.nearbyToolData?.annotationUID as string;
|
||||
const isLocked = locking.isAnnotationLocked(targetAnnotationId);
|
||||
const isVisible = visibility.isAnnotationVisible(targetAnnotationId);
|
||||
|
||||
if (isLocked || !isVisible) {
|
||||
console.warn(`Annotation is ${isLocked ? 'locked' : 'not visible'}.`);
|
||||
return;
|
||||
if (targetAnnotationId) {
|
||||
const isLocked = locking.isAnnotationLocked(targetAnnotationId);
|
||||
const isVisible = visibility.isAnnotationVisible(targetAnnotationId);
|
||||
|
||||
if (isLocked || !isVisible) {
|
||||
console.warn(`Annotation is ${isLocked ? 'locked' : 'not visible'}.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const items = ContextMenuItemsBuilder.getMenuItems(
|
||||
@@ -73,7 +80,7 @@ export default class ContextMenuController {
|
||||
preventCutOf: true,
|
||||
defaultPosition: ContextMenuController._getDefaultPosition(
|
||||
defaultPointsPosition,
|
||||
event?.detail,
|
||||
event?.detail || event,
|
||||
viewportElement
|
||||
),
|
||||
event,
|
||||
@@ -89,7 +96,7 @@ export default class ContextMenuController {
|
||||
menus,
|
||||
event,
|
||||
subMenu,
|
||||
eventData: event?.detail,
|
||||
eventData: event?.detail || event,
|
||||
|
||||
onClose: () => {
|
||||
this.services.uiDialogService.dismiss({ id: 'context-menu' });
|
||||
@@ -136,8 +143,8 @@ export default class ContextMenuController {
|
||||
};
|
||||
|
||||
static _getEventDefaultPosition = eventDetail => ({
|
||||
x: eventDetail && eventDetail.currentPoints.client[0],
|
||||
y: eventDetail && eventDetail.currentPoints.client[1],
|
||||
x: eventDetail?.currentPoints?.client[0] ?? eventDetail?.pageX,
|
||||
y: eventDetail?.currentPoints?.client[1] ?? eventDetail?.pageY,
|
||||
});
|
||||
|
||||
static _getElementDefaultPosition = element => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { Separator } from '@ohif/ui-next';
|
||||
import { PanelStudyBrowserHeader } from './PanelStudyBrowserHeader';
|
||||
import { defaultActionIcons, defaultViewPresets } from './constants';
|
||||
import MoreDropdownMenu from '../../Components/MoreDropdownMenu';
|
||||
|
||||
const { sortStudyInstances, formatDate, createStudyBrowserTabs } = utils;
|
||||
|
||||
@@ -280,10 +281,6 @@ function PanelStudyBrowser({
|
||||
|
||||
const activeDisplaySetInstanceUIDs = viewports.get(activeViewportId)?.displaySetInstanceUIDs;
|
||||
|
||||
const onThumbnailContextMenu = (commandName, options) => {
|
||||
commandsManager.runCommand(commandName, options);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<>
|
||||
@@ -304,16 +301,26 @@ function PanelStudyBrowser({
|
||||
tabs={tabs}
|
||||
servicesManager={servicesManager}
|
||||
activeTabName={activeTabName}
|
||||
onDoubleClickThumbnail={onDoubleClickThumbnailHandler}
|
||||
activeDisplaySetInstanceUIDs={activeDisplaySetInstanceUIDs}
|
||||
expandedStudyInstanceUIDs={expandedStudyInstanceUIDs}
|
||||
onClickStudy={_handleStudyClick}
|
||||
onClickTab={clickedTabName => {
|
||||
setActiveTabName(clickedTabName);
|
||||
}}
|
||||
onClickThumbnail={() => {}}
|
||||
onDoubleClickThumbnail={onDoubleClickThumbnailHandler}
|
||||
activeDisplaySetInstanceUIDs={activeDisplaySetInstanceUIDs}
|
||||
showSettings={actionIcons.find(icon => icon.id === 'settings').value}
|
||||
viewPresets={viewPresets}
|
||||
onThumbnailContextMenu={onThumbnailContextMenu}
|
||||
ThumbnailMenuItems={MoreDropdownMenu({
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
menuItemsKey: 'studyBrowser.thumbnailMenuItems',
|
||||
})}
|
||||
StudyMenuItems={MoreDropdownMenu({
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
menuItemsKey: 'studyBrowser.studyMenuItems',
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -13,10 +13,10 @@ import requestDisplaySetCreationForStudy from './requestDisplaySetCreationForStu
|
||||
* @param {object} commandsManager
|
||||
* @param {object} extensionManager
|
||||
*/
|
||||
function WrappedPanelStudyBrowser({ commandsManager, extensionManager, servicesManager }) {
|
||||
function WrappedPanelStudyBrowser({ extensionManager, servicesManager }) {
|
||||
// TODO: This should be made available a different way; route should have
|
||||
// already determined our datasource
|
||||
const dataSource = extensionManager.getDataSources()[0];
|
||||
const [dataSource] = extensionManager.getActiveDataSource();
|
||||
const _getStudiesForPatientByMRN = getStudiesForPatientByMRN.bind(null, dataSource);
|
||||
const _getImageSrcFromImageId = useCallback(
|
||||
_createGetImageSrcFromImageIdFn(extensionManager),
|
||||
|
||||
@@ -13,7 +13,7 @@ function requestDisplaySetCreationForStudy(
|
||||
return;
|
||||
}
|
||||
|
||||
dataSource.retrieve.series.metadata({ StudyInstanceUID, madeInClient });
|
||||
return dataSource.retrieve.series.metadata({ StudyInstanceUID, madeInClient });
|
||||
}
|
||||
|
||||
export default requestDisplaySetCreationForStudy;
|
||||
@@ -25,8 +25,6 @@ function ViewerHeader({
|
||||
const onClickReturnButton = () => {
|
||||
const { pathname } = location;
|
||||
const dataSourceIdx = pathname.indexOf('/', 1);
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
const configUrl = query.get('configUrl');
|
||||
|
||||
const dataSourceName = pathname.substring(dataSourceIdx + 1);
|
||||
const existingDataSource = extensionManager.getDataSources(dataSourceName);
|
||||
@@ -35,10 +33,7 @@ function ViewerHeader({
|
||||
if (dataSourceIdx !== -1 && existingDataSource) {
|
||||
searchQuery.append('datasources', pathname.substring(dataSourceIdx + 1));
|
||||
}
|
||||
|
||||
if (configUrl) {
|
||||
searchQuery.append('configUrl', configUrl);
|
||||
}
|
||||
preserveQueryParameters(searchQuery);
|
||||
|
||||
navigate({
|
||||
pathname: publicUrl,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Types } from '@ohif/core';
|
||||
import { Types, DicomMetadataStore } from '@ohif/core';
|
||||
|
||||
import { ContextMenuController, defaultContextMenu } from './CustomizableContextMenu';
|
||||
import DicomTagBrowser from './DicomTagBrowser/DicomTagBrowser';
|
||||
@@ -16,6 +16,7 @@ import { useHangingProtocolStageIndexStore } from './stores/useHangingProtocolSt
|
||||
import { useToggleHangingProtocolStore } from './stores/useToggleHangingProtocolStore';
|
||||
import { useViewportsByPositionStore } from './stores/useViewportsByPositionStore';
|
||||
import { useToggleOneUpViewportGridStore } from './stores/useToggleOneUpViewportGridStore';
|
||||
import requestDisplaySetCreationForStudy from './Panels/requestDisplaySetCreationForStudy';
|
||||
|
||||
export type HangingProtocolParams = {
|
||||
protocolId?: string;
|
||||
@@ -33,6 +34,7 @@ export type UpdateViewportDisplaySetParams = {
|
||||
const commandsModule = ({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
}: Types.Extensions.ExtensionParams): Types.Extensions.CommandsModule => {
|
||||
const {
|
||||
customizationService,
|
||||
@@ -41,12 +43,56 @@ const commandsModule = ({
|
||||
uiNotificationService,
|
||||
viewportGridService,
|
||||
displaySetService,
|
||||
multiMonitorService,
|
||||
} = servicesManager.services;
|
||||
|
||||
// Define a context menu controller for use with any context menus
|
||||
const contextMenuController = new ContextMenuController(servicesManager, commandsManager);
|
||||
|
||||
const actions = {
|
||||
/**
|
||||
* Runs a command in multi-monitor mode. No-op if not multi-monitor.
|
||||
*/
|
||||
multimonitor: async options => {
|
||||
const { screenDelta, StudyInstanceUID, commands, hashParams } = options;
|
||||
if (multiMonitorService.numberOfScreens < 2) {
|
||||
return options.fallback?.(options);
|
||||
}
|
||||
|
||||
const newWindow = await multiMonitorService.launchWindow(
|
||||
StudyInstanceUID,
|
||||
screenDelta,
|
||||
hashParams
|
||||
);
|
||||
|
||||
// Only run commands if we successfully got a window with a commands manager
|
||||
if (newWindow && commands) {
|
||||
// Todo: fix this properly, but it takes time for the new window to load
|
||||
// and then the commandsManager is available for it
|
||||
setTimeout(() => {
|
||||
multiMonitorService.run(screenDelta, commands, options);
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensures that the specified study is available for display
|
||||
* Then, if commands is specified, runs the given commands list/instance
|
||||
*/
|
||||
loadStudy: async options => {
|
||||
const { StudyInstanceUID } = options;
|
||||
const displaySets = displaySetService.getActiveDisplaySets();
|
||||
const isActive = displaySets.find(ds => ds.StudyInstanceUID === StudyInstanceUID);
|
||||
if (isActive) {
|
||||
return;
|
||||
}
|
||||
const [dataSource] = extensionManager.getActiveDataSource();
|
||||
await requestDisplaySetCreationForStudy(dataSource, displaySetService, StudyInstanceUID);
|
||||
|
||||
const study = DicomMetadataStore.getStudy(StudyInstanceUID);
|
||||
hangingProtocolService.addStudy(study);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the context menu.
|
||||
* @param options.menuId defines the menu name to lookup, from customizationService
|
||||
@@ -135,11 +181,13 @@ const commandsModule = ({
|
||||
*/
|
||||
setHangingProtocol: ({
|
||||
activeStudyUID = '',
|
||||
StudyInstanceUID = '',
|
||||
protocolId,
|
||||
stageId,
|
||||
stageIndex,
|
||||
reset = false,
|
||||
}: HangingProtocolParams): boolean => {
|
||||
const toUseStudyInstanceUID = activeStudyUID || StudyInstanceUID;
|
||||
try {
|
||||
// Stores in the state the display set selector id to displaySetUID mapping
|
||||
// Pass in viewportId for the active viewport. This item will get set as
|
||||
@@ -158,7 +206,7 @@ const commandsModule = ({
|
||||
}
|
||||
} else if (stageIndex === undefined && stageId === undefined) {
|
||||
// Re-set the same stage as was previously used
|
||||
const hangingId = `${activeStudyUID || hpInfo.activeStudyUID}:${protocolId}`;
|
||||
const hangingId = `${toUseStudyInstanceUID || hpInfo.activeStudyUID}:${protocolId}`;
|
||||
stageIndex = hangingProtocolStageIndexMap[hangingId]?.stageIndex;
|
||||
}
|
||||
|
||||
@@ -169,11 +217,9 @@ const commandsModule = ({
|
||||
stageIndex,
|
||||
});
|
||||
|
||||
if (activeStudyUID) {
|
||||
hangingProtocolService.setActiveStudyUID(activeStudyUID);
|
||||
}
|
||||
const activeStudyChanged = hangingProtocolService.setActiveStudyUID(toUseStudyInstanceUID);
|
||||
|
||||
const storedHanging = `${hangingProtocolService.getState().activeStudyUID}:${protocolId}:${
|
||||
const storedHanging = `${toUseStudyInstanceUID || hangingProtocolService.getState().activeStudyUID}:${protocolId}:${
|
||||
useStageIdx || 0
|
||||
}`;
|
||||
|
||||
@@ -181,9 +227,25 @@ const commandsModule = ({
|
||||
const restoreProtocol = !reset && viewportGridState[storedHanging];
|
||||
|
||||
if (
|
||||
reset ||
|
||||
(activeStudyChanged &&
|
||||
!viewportGridState[storedHanging] &&
|
||||
stageIndex === undefined &&
|
||||
stageId === undefined)
|
||||
) {
|
||||
// Run the hanging protocol fresh, re-using the existing study data
|
||||
// This is done on reset or when the study changes and we haven't yet
|
||||
// applied it, and don't specify exact stage to use.
|
||||
const displaySets = displaySetService.getActiveDisplaySets();
|
||||
const activeStudy = {
|
||||
StudyInstanceUID: toUseStudyInstanceUID,
|
||||
displaySets,
|
||||
};
|
||||
hangingProtocolService.run(activeStudy, protocolId);
|
||||
} else if (
|
||||
protocolId === hpInfo.protocolId &&
|
||||
useStageIdx === hpInfo.stageIndex &&
|
||||
!activeStudyUID
|
||||
!toUseStudyInstanceUID
|
||||
) {
|
||||
// Clear the HP setting to reset them
|
||||
hangingProtocolService.setProtocol(protocolId, {
|
||||
@@ -204,7 +266,7 @@ const commandsModule = ({
|
||||
// Do this after successfully applying the update
|
||||
const { setDisplaySetSelector } = useDisplaySetSelectorStore.getState();
|
||||
setDisplaySetSelector(
|
||||
`${activeStudyUID || hpInfo.activeStudyUID}:activeDisplaySet:0`,
|
||||
`${toUseStudyInstanceUID || hpInfo.activeStudyUID}:activeDisplaySet:0`,
|
||||
null
|
||||
);
|
||||
return true;
|
||||
@@ -562,6 +624,8 @@ const commandsModule = ({
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
multimonitor: actions.multimonitor,
|
||||
loadStudy: actions.loadStudy,
|
||||
showContextMenu: actions.showContextMenu,
|
||||
closeContextMenu: actions.closeContextMenu,
|
||||
clearMeasurements: actions.clearMeasurements,
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
export const studyBrowserContextMenu = {
|
||||
id: 'StudyBrowser.studyContextMenu',
|
||||
customizationType: 'ohif.contextMenu',
|
||||
menus: [
|
||||
{
|
||||
id: 'studyBrowserContextMenu',
|
||||
// selector restricts context menu to when there is nearbyToolData
|
||||
items: [
|
||||
{
|
||||
label: 'Show in Grid',
|
||||
commands: {
|
||||
commandName: 'loadStudy',
|
||||
commandOptions: {
|
||||
commands: {
|
||||
commandName: 'setHangingProtocol',
|
||||
commandOptions: {
|
||||
protocolId: '@ohif/mnGrid8',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Show in other monitor',
|
||||
selector: ({ isMultimonitor }) => isMultimonitor,
|
||||
commands: {
|
||||
commandName: 'multimonitor',
|
||||
commandOptions: {
|
||||
commands: {
|
||||
commandName: 'loadStudy',
|
||||
commandOptions: {
|
||||
commands: {
|
||||
commandName: 'setHangingProtocol',
|
||||
commandOptions: {
|
||||
protocolId: '@ohif/mnGrid8',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Compare All',
|
||||
selector: ({ isMultimonitor }) => isMultimonitor,
|
||||
commands: [
|
||||
{
|
||||
commandName: 'loadStudy',
|
||||
commandOptions: {
|
||||
commands: {
|
||||
commandName: 'setHangingProtocol',
|
||||
commandOptions: {
|
||||
protocolId: '@ohif/mnGrid',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
commandName: 'multimonitor',
|
||||
commandOptions: {
|
||||
commands: {
|
||||
commandName: 'loadStudy',
|
||||
commandOptions: {
|
||||
commands: {
|
||||
commandName: 'setHangingProtocol',
|
||||
commandOptions: {
|
||||
protocolId: '@ohif/mnGridMonitor2',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default studyBrowserContextMenu;
|
||||
@@ -5,6 +5,15 @@ import { ProgressDropdownWithService } from './Components/ProgressDropdownWithSe
|
||||
import DataSourceConfigurationComponent from './Components/DataSourceConfigurationComponent';
|
||||
import { GoogleCloudDataSourceConfigurationAPI } from './DataSourceConfigurationAPI/GoogleCloudDataSourceConfigurationAPI';
|
||||
import { utils } from '@ohif/core';
|
||||
import studyBrowserContextMenu from './customizations/studyBrowserContextMenu';
|
||||
import {
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuItem,
|
||||
Icons,
|
||||
} from '@ohif/ui-next';
|
||||
|
||||
const formatDate = utils.formatDate;
|
||||
|
||||
@@ -47,7 +56,74 @@ export default function getCustomizationModule({ servicesManager, extensionManag
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: 'multimonitor',
|
||||
merge: 'Append',
|
||||
value: {
|
||||
id: 'studyBrowser.studyMenuItems',
|
||||
customizationType: 'ohif.menuContent',
|
||||
value: [
|
||||
{
|
||||
id: 'applyHangingProtocol',
|
||||
label: 'Apply Hanging Protocol',
|
||||
iconName: 'ViewportViews',
|
||||
items: [
|
||||
{
|
||||
id: 'applyDefaultProtocol',
|
||||
label: 'Default',
|
||||
commands: [
|
||||
'loadStudy',
|
||||
{
|
||||
commandName: 'setHangingProtocol',
|
||||
commandOptions: {
|
||||
protocolId: 'default',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'applyMPRProtocol',
|
||||
label: '2x2 Grid',
|
||||
commands: [
|
||||
'loadStudy',
|
||||
{
|
||||
commandName: 'setHangingProtocol',
|
||||
commandOptions: {
|
||||
protocolId: '@ohif/mnGrid',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'showInOtherMonitor',
|
||||
label: 'Launch On Second Monitor',
|
||||
iconName: 'DicomTagBrowser',
|
||||
// we should use evaluator for this, as these are basically toolbar buttons
|
||||
selector: ({ servicesManager }) => {
|
||||
const { multiMonitorService } = servicesManager.services;
|
||||
return multiMonitorService.isMultimonitor;
|
||||
},
|
||||
commands: {
|
||||
commandName: 'multimonitor',
|
||||
commandOptions: {
|
||||
hashParams: '&hangingProtocolId=@ohif/mnGrid8',
|
||||
commands: [
|
||||
'loadStudy',
|
||||
{
|
||||
commandName: 'setHangingProtocol',
|
||||
commandOptions: {
|
||||
protocolId: '@ohif/mnGrid8',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'default',
|
||||
value: [
|
||||
@@ -117,7 +193,6 @@ export default function getCustomizationModule({ servicesManager, extensionManag
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'ohif.contextMenu',
|
||||
|
||||
@@ -140,6 +215,7 @@ export default function getCustomizationModule({ servicesManager, extensionManag
|
||||
return clonedObject;
|
||||
},
|
||||
},
|
||||
studyBrowserContextMenu,
|
||||
|
||||
{
|
||||
// the generic GUI component to configure a data source using an instance of a BaseDataSourceConfigurationAPI
|
||||
@@ -200,6 +276,69 @@ export default function getCustomizationModule({ servicesManager, extensionManag
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'studyBrowser.thumbnailMenuItems',
|
||||
value: [
|
||||
{
|
||||
id: 'tagBrowser',
|
||||
label: 'Tag Browser',
|
||||
iconName: 'DicomTagBrowser',
|
||||
commands: 'openDICOMTagViewer',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'ohif.menuContent',
|
||||
content: function (props) {
|
||||
const { item, commandsManager, servicesManager, ...rest } = props;
|
||||
|
||||
// If item has sub-items, render a submenu
|
||||
if (item.items) {
|
||||
return (
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger className="gap-[6px]">
|
||||
{item.iconName && (
|
||||
<Icons.ByName
|
||||
name={item.iconName}
|
||||
className="-ml-1"
|
||||
/>
|
||||
)}
|
||||
{item.label}
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
{item.items.map(subItem => this.content({ ...props, item: subItem }))}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
);
|
||||
}
|
||||
|
||||
// Regular menu item
|
||||
const isDisabled = item.selector && !item.selector({ servicesManager });
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
disabled={isDisabled}
|
||||
onSelect={() => {
|
||||
commandsManager.runAsync(item.commands, {
|
||||
...item.commandOptions,
|
||||
...rest,
|
||||
});
|
||||
}}
|
||||
className="gap-[6px]"
|
||||
>
|
||||
{item.iconName && (
|
||||
<Icons.ByName
|
||||
name={item.iconName}
|
||||
className="-ml-1"
|
||||
/>
|
||||
)}
|
||||
{item.label}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import hpMNGrid from './hangingprotocols/hpMNGrid';
|
||||
import { hpMN, hpMN8, hpMNMonitor2 } from './hangingprotocols/hpMNGrid';
|
||||
import hpMNCompare from './hangingprotocols/hpCompare';
|
||||
import hpMammography from './hangingprotocols/hpMammo';
|
||||
import hpScale from './hangingprotocols/hpScale';
|
||||
@@ -142,8 +142,16 @@ function getHangingProtocolModule() {
|
||||
},
|
||||
// Create a MxN hanging protocol available by default
|
||||
{
|
||||
name: hpMNGrid.id,
|
||||
protocol: hpMNGrid,
|
||||
name: hpMN.id,
|
||||
protocol: hpMN,
|
||||
},
|
||||
{
|
||||
name: hpMN8.id,
|
||||
protocol: hpMN8,
|
||||
},
|
||||
{
|
||||
name: hpMNMonitor2.id,
|
||||
protocol: hpMNMonitor2,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Types } from '@ohif/core';
|
||||
import { studyWithImages } from './utils/studySelectors';
|
||||
import { seriesWithImages } from './utils/seriesSelectors';
|
||||
import { viewportOptions } from './utils/viewportOptions';
|
||||
|
||||
/**
|
||||
* Sync group configuration for hydrating segmentations across viewports
|
||||
@@ -21,41 +24,15 @@ export const HYDRATE_SEG_SYNC_GROUP = {
|
||||
* `&hangingProtocolId=@ohif/mnGrid` added to the viewer URL
|
||||
* It is not included in the viewer mode by default.
|
||||
*/
|
||||
const hpMN: Types.HangingProtocol.Protocol = {
|
||||
export const hpMN: Types.HangingProtocol.Protocol = {
|
||||
id: '@ohif/mnGrid',
|
||||
description: 'Has various hanging protocol grid layouts',
|
||||
name: '2x2',
|
||||
protocolMatchingRules: [
|
||||
{
|
||||
id: 'OneOrMoreSeries',
|
||||
weight: 25,
|
||||
attribute: 'numberOfDisplaySetsWithImages',
|
||||
constraint: {
|
||||
greaterThan: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
protocolMatchingRules: studyWithImages,
|
||||
toolGroupIds: ['default'],
|
||||
displaySetSelectors: {
|
||||
defaultDisplaySetId: {
|
||||
seriesMatchingRules: [
|
||||
{
|
||||
attribute: 'numImageFrames',
|
||||
constraint: {
|
||||
greaterThan: { value: 0 },
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
// This display set will select the specified items by preference
|
||||
// It has no affect if nothing is specified in the URL.
|
||||
{
|
||||
attribute: 'isDisplaySetFromUrl',
|
||||
weight: 20,
|
||||
constraint: {
|
||||
equals: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
seriesMatchingRules: seriesWithImages,
|
||||
},
|
||||
},
|
||||
defaultViewport: {
|
||||
@@ -90,21 +67,7 @@ const hpMN: Types.HangingProtocol.Protocol = {
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'default',
|
||||
allowUnmatchedView: true,
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'hydrateseg',
|
||||
id: 'sameFORId',
|
||||
source: true,
|
||||
target: true,
|
||||
options: {
|
||||
matchingRules: ['sameFOR'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
id: 'defaultDisplaySetId',
|
||||
@@ -112,10 +75,7 @@ const hpMN: Types.HangingProtocol.Protocol = {
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'default',
|
||||
allowUnmatchedView: true,
|
||||
},
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 1,
|
||||
@@ -124,21 +84,7 @@ const hpMN: Types.HangingProtocol.Protocol = {
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'default',
|
||||
allowUnmatchedView: true,
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'hydrateseg',
|
||||
id: 'sameFORId',
|
||||
source: true,
|
||||
target: true,
|
||||
// options: {
|
||||
// matchingRules: ['sameFOR'],
|
||||
// },
|
||||
},
|
||||
],
|
||||
},
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 2,
|
||||
@@ -147,21 +93,7 @@ const hpMN: Types.HangingProtocol.Protocol = {
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'default',
|
||||
allowUnmatchedView: true,
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'hydrateseg',
|
||||
id: 'sameFORId',
|
||||
source: true,
|
||||
target: true,
|
||||
// options: {
|
||||
// matchingRules: ['sameFOR'],
|
||||
// },
|
||||
},
|
||||
],
|
||||
},
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 3,
|
||||
@@ -174,11 +106,7 @@ const hpMN: Types.HangingProtocol.Protocol = {
|
||||
|
||||
// 3x1 stage
|
||||
{
|
||||
id: '3x1',
|
||||
// Obsolete settings:
|
||||
requiredViewports: 1,
|
||||
preferredViewports: 3,
|
||||
// New equivalent:
|
||||
name: '3x1',
|
||||
stageActivation: {
|
||||
enabled: {
|
||||
minViewportsMatched: 3,
|
||||
@@ -193,10 +121,7 @@ const hpMN: Types.HangingProtocol.Protocol = {
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'default',
|
||||
allowUnmatchedView: true,
|
||||
},
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
id: 'defaultDisplaySetId',
|
||||
@@ -204,10 +129,7 @@ const hpMN: Types.HangingProtocol.Protocol = {
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'default',
|
||||
allowUnmatchedView: true,
|
||||
},
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
id: 'defaultDisplaySetId',
|
||||
@@ -216,10 +138,7 @@ const hpMN: Types.HangingProtocol.Protocol = {
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'default',
|
||||
allowUnmatchedView: true,
|
||||
},
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
id: 'defaultDisplaySetId',
|
||||
@@ -232,9 +151,7 @@ const hpMN: Types.HangingProtocol.Protocol = {
|
||||
|
||||
// A 2x1 stage
|
||||
{
|
||||
id: '2x1',
|
||||
requiredViewports: 1,
|
||||
preferredViewports: 2,
|
||||
name: '2x1',
|
||||
stageActivation: {
|
||||
enabled: {
|
||||
minViewportsMatched: 2,
|
||||
@@ -249,10 +166,7 @@ const hpMN: Types.HangingProtocol.Protocol = {
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'default',
|
||||
allowUnmatchedView: true,
|
||||
},
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
id: 'defaultDisplaySetId',
|
||||
@@ -260,10 +174,7 @@ const hpMN: Types.HangingProtocol.Protocol = {
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'default',
|
||||
allowUnmatchedView: true,
|
||||
},
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 1,
|
||||
@@ -276,9 +187,7 @@ const hpMN: Types.HangingProtocol.Protocol = {
|
||||
|
||||
// A 1x1 stage - should be automatically activated if there is only 1 viewable instance
|
||||
{
|
||||
id: '1x1',
|
||||
requiredViewports: 1,
|
||||
preferredViewports: 1,
|
||||
name: '1x1',
|
||||
stageActivation: {
|
||||
enabled: {
|
||||
minViewportsMatched: 1,
|
||||
@@ -293,10 +202,7 @@ const hpMN: Types.HangingProtocol.Protocol = {
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions: {
|
||||
toolGroupId: 'default',
|
||||
allowUnmatchedView: true,
|
||||
},
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
id: 'defaultDisplaySetId',
|
||||
@@ -309,4 +215,341 @@ const hpMN: Types.HangingProtocol.Protocol = {
|
||||
numberOfPriorsReferenced: -1,
|
||||
};
|
||||
|
||||
/**
|
||||
* This hanging protocol can be activated on the primary mode by directly
|
||||
* referencing it in a URL or by directly including it within a mode, e.g.:
|
||||
* `&hangingProtocolId=@ohif/mnGrid8` added to the viewer URL
|
||||
* It is not included in the viewer mode by default.
|
||||
*/
|
||||
export const hpMN8: Types.HangingProtocol.Protocol = {
|
||||
...hpMN,
|
||||
id: '@ohif/mnGrid8',
|
||||
description: 'Has various hanging protocol grid layouts up to 4x2',
|
||||
name: '4x2',
|
||||
stages: [
|
||||
{
|
||||
id: '4x2',
|
||||
name: '4x2',
|
||||
stageActivation: {
|
||||
enabled: {
|
||||
minViewportsMatched: 7,
|
||||
},
|
||||
},
|
||||
viewportStructure: {
|
||||
layoutType: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 1,
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 2,
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 3,
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 4,
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 5,
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 6,
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 7,
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
id: '3x2',
|
||||
name: '3x2',
|
||||
stageActivation: {
|
||||
enabled: {
|
||||
minViewportsMatched: 5,
|
||||
},
|
||||
},
|
||||
viewportStructure: {
|
||||
layoutType: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 3,
|
||||
},
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 1,
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 2,
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 3,
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 4,
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 5,
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
...hpMN.stages,
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* This hanging protocol extends the default protocol with additional
|
||||
* images on the second monitor. It assumes the first four images are shown
|
||||
* on monitor 0
|
||||
*/
|
||||
export const hpMNMonitor2: Types.HangingProtocol.Protocol = {
|
||||
...hpMN,
|
||||
id: '@ohif/mnGridMonitor2',
|
||||
description: 'Second monitor HP with 2x2 grid',
|
||||
name: '2x2 Monitor 2',
|
||||
stages: [
|
||||
{
|
||||
id: '2x2',
|
||||
name: '2x2',
|
||||
stageActivation: {
|
||||
enabled: {
|
||||
minViewportsMatched: 3,
|
||||
},
|
||||
},
|
||||
viewportStructure: {
|
||||
layoutType: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 2,
|
||||
},
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
id: 'defaultDisplaySetId',
|
||||
matchedDisplaySetsIndex: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 5,
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 6,
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 7,
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// A 2x1 stage
|
||||
{
|
||||
name: '2x1',
|
||||
stageActivation: {
|
||||
enabled: {
|
||||
minViewportsMatched: 1,
|
||||
},
|
||||
},
|
||||
viewportStructure: {
|
||||
layoutType: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
},
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
id: 'defaultDisplaySetId',
|
||||
matchedDisplaySetsIndex: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
matchedDisplaySetsIndex: 5,
|
||||
id: 'defaultDisplaySetId',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: '1x1',
|
||||
stageActivation: {
|
||||
enabled: {
|
||||
minViewportsMatched: 0,
|
||||
},
|
||||
},
|
||||
viewportStructure: {
|
||||
layoutType: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 1,
|
||||
},
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
id: 'defaultDisplaySetId',
|
||||
matchedDisplaySetsIndex: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: '1x1 Base',
|
||||
stageActivation: {
|
||||
enabled: {
|
||||
minViewportsMatched: 1,
|
||||
},
|
||||
},
|
||||
viewportStructure: {
|
||||
layoutType: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 1,
|
||||
},
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions,
|
||||
displaySets: [
|
||||
{
|
||||
id: 'defaultDisplaySetId',
|
||||
matchedDisplaySetsIndex: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default hpMN;
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
LCCPrior,
|
||||
RMLOPrior,
|
||||
LMLOPrior,
|
||||
} from './mammoDisplaySetSelector';
|
||||
} from './utils/mammoDisplaySetSelector';
|
||||
|
||||
const rightDisplayArea = {
|
||||
storeAsInitialCamera: true,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import viewCodeAttribute from './viewCode';
|
||||
import lateralityAttribute from './laterality';
|
||||
import registerHangingProtocolAttributes from './registerHangingProtocolAttributes';
|
||||
import viewCodeAttribute from './utils/viewCode';
|
||||
import lateralityAttribute from './utils/laterality';
|
||||
import registerHangingProtocolAttributes from './utils/registerHangingProtocolAttributes';
|
||||
import hpMammography from './hpMammo';
|
||||
import hpMNGrid from './hpMNGrid';
|
||||
import hpCompare from './hpCompare';
|
||||
export * from './hpMNGrid';
|
||||
|
||||
export {
|
||||
viewCodeAttribute,
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,23 @@
|
||||
import { Types } from '@ohif/core';
|
||||
|
||||
type MatchingRule = Types.HangingProtocol.MatchingRule;
|
||||
|
||||
export const seriesWithImages: MatchingRule[] = [
|
||||
{
|
||||
attribute: 'numImageFrames',
|
||||
constraint: {
|
||||
greaterThan: { value: 0 },
|
||||
},
|
||||
weight: 1,
|
||||
required: true,
|
||||
},
|
||||
// This display set will select the specified items by preference
|
||||
// It has no affect if nothing is specified in the URL.
|
||||
{
|
||||
attribute: 'isDisplaySetFromUrl',
|
||||
weight: 20,
|
||||
constraint: {
|
||||
equals: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Types } from '@ohif/core';
|
||||
|
||||
type MatchingRule = Types.HangingProtocol.MatchingRule;
|
||||
|
||||
export const studyWithImages: MatchingRule[] = [
|
||||
{
|
||||
id: 'OneOrMoreSeries',
|
||||
weight: 25,
|
||||
attribute: 'numberOfDisplaySetsWithImages',
|
||||
constraint: {
|
||||
greaterThan: 0,
|
||||
},
|
||||
},
|
||||
];
|
||||
File renamed without changes.
@@ -0,0 +1,18 @@
|
||||
/** A default viewport options */
|
||||
export const viewportOptions = {
|
||||
toolGroupId: 'default',
|
||||
allowUnmatchedView: true,
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'hydrateseg',
|
||||
id: 'sameFORId',
|
||||
source: true,
|
||||
target: true,
|
||||
options: {
|
||||
matchingRules: ['sameFOR'],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const hydrateSegDefault = viewportOptions;
|
||||
@@ -37,6 +37,8 @@ import promptLabelAnnotation from './utils/promptLabelAnnotation';
|
||||
import usePatientInfo from './hooks/usePatientInfo';
|
||||
import { PanelStudyBrowserHeader } from './Panels/StudyBrowser/PanelStudyBrowserHeader';
|
||||
import * as utils from './utils';
|
||||
import MoreDropdownMenu from './Components/MoreDropdownMenu';
|
||||
import requestDisplaySetCreationForStudy from './Panels/requestDisplaySetCreationForStudy';
|
||||
|
||||
const defaultExtension: Types.Extensions.Extension = {
|
||||
/**
|
||||
@@ -102,4 +104,6 @@ export {
|
||||
usePatientInfo,
|
||||
PanelStudyBrowserHeader,
|
||||
utils,
|
||||
MoreDropdownMenu,
|
||||
requestDisplaySetCreationForStudy,
|
||||
};
|
||||
+13
-9
@@ -4,14 +4,13 @@ import { useTranslation } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { utils } from '@ohif/core';
|
||||
import { useImageViewer, Dialog, ButtonEnums } from '@ohif/ui';
|
||||
import { useViewportGrid } from '@ohif/ui-next';
|
||||
import { useViewportGrid, DropdownMenu, DropdownMenuTrigger, Icons, Button } from '@ohif/ui-next';
|
||||
import { StudyBrowser } from '@ohif/ui-next';
|
||||
|
||||
import { useTrackedMeasurements } from '../../getContextModule';
|
||||
import { Separator } from '@ohif/ui-next';
|
||||
import { PanelStudyBrowserHeader } from '@ohif/extension-default';
|
||||
import { PanelStudyBrowserHeader, MoreDropdownMenu } from '@ohif/extension-default';
|
||||
import { defaultActionIcons, defaultViewPresets } from './constants';
|
||||
|
||||
const { formatDate, createStudyBrowserTabs } = utils;
|
||||
const thumbnailNoImageModalities = [
|
||||
'SR',
|
||||
@@ -24,6 +23,7 @@ const thumbnailNoImageModalities = [
|
||||
'OT',
|
||||
'PMAP',
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} param0
|
||||
@@ -485,10 +485,6 @@ export default function PanelStudyBrowserTracking({
|
||||
});
|
||||
};
|
||||
|
||||
const onThumbnailContextMenu = (commandName, options) => {
|
||||
commandsManager.runCommand(commandName, options);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<>
|
||||
@@ -522,7 +518,16 @@ export default function PanelStudyBrowserTracking({
|
||||
activeDisplaySetInstanceUIDs={activeViewportDisplaySetInstanceUIDs}
|
||||
showSettings={actionIcons.find(icon => icon.id === 'settings').value}
|
||||
viewPresets={viewPresets}
|
||||
onThumbnailContextMenu={onThumbnailContextMenu}
|
||||
ThumbnailMenuItems={MoreDropdownMenu({
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
menuItemsKey: 'studyBrowser.thumbnailMenuItems',
|
||||
})}
|
||||
StudyMenuItems={MoreDropdownMenu({
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
menuItemsKey: 'studyBrowser.studyMenuItems',
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -592,7 +597,6 @@ function _mapDisplaySets(
|
||||
.forEach(ds => {
|
||||
const imageSrc = thumbnailImageSrcMap[ds.displaySetInstanceUID];
|
||||
const componentType = _getComponentType(ds);
|
||||
const numPanes = viewportGridService.getNumViewportPanes();
|
||||
|
||||
const array =
|
||||
componentType === 'thumbnailTracked' ? thumbnailDisplaySets : thumbnailNoImageDisplaySets;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
//
|
||||
import PanelStudyBrowserTracking from './PanelStudyBrowserTracking';
|
||||
import getImageSrcFromImageId from './getImageSrcFromImageId';
|
||||
import requestDisplaySetCreationForStudy from './requestDisplaySetCreationForStudy';
|
||||
import { requestDisplaySetCreationForStudy } from '@ohif/extension-default';
|
||||
|
||||
function _getStudyForPatientUtility(extensionManager) {
|
||||
const utilityModule = extensionManager.getModuleEntry(
|
||||
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
function requestDisplaySetCreationForStudy(
|
||||
dataSource,
|
||||
displaySetService,
|
||||
StudyInstanceUID,
|
||||
madeInClient
|
||||
) {
|
||||
if (
|
||||
displaySetService.activeDisplaySets.some(
|
||||
displaySet => displaySet.StudyInstanceUID === StudyInstanceUID
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
dataSource.retrieve.series.metadata({ StudyInstanceUID, madeInClient });
|
||||
}
|
||||
|
||||
export default requestDisplaySetCreationForStudy;
|
||||
Reference in new issue
Block a user