feat: Add a new hanging protocol @ohif/mn (#3305)

* feat: Add a new hanging protocol @ohif/mn

* Add @ohif/seg example

* PR comments and a couple more fixes to make things cleaner

* PR comment

* Added an example to cause the PR checks to rerun
This commit is contained in:
Bill Wallace 2023-04-18 11:55:25 -04:00 committed by GitHub
parent 6cf271d006
commit c508a2b6bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 184 additions and 358 deletions

View File

@ -0,0 +1,78 @@
import { Types } from '@ohif/core';
const segProtocol: Types.HangingProtocol.Protocol = {
id: '@ohif/seg',
// Don't store this hanging protocol as it applies to the currently active
// display set by default
// cacheId: null,
hasUpdatedPriorsInformation: false,
name: 'Segmentations',
// Just apply this one when specifically listed
protocolMatchingRules: [],
toolGroupIds: ['default'],
// -1 would be used to indicate active only, whereas other values are
// the number of required priors referenced - so 0 means active with
// 0 or more priors.
numberOfPriorsReferenced: 0,
// Default viewport is used to define the viewport when
// additional viewports are added using the layout tool
defaultViewport: {
viewportOptions: {
viewportType: 'stack',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
id: 'segDisplaySetId',
matchedDisplaySetsIndex: -1,
},
],
},
displaySetSelectors: {
segDisplaySetId: {
seriesMatchingRules: [
{
attribute: 'Modality',
constraint: {
equals: 'SEG',
},
},
],
},
},
stages: [
{
name: 'Segmentations',
viewportStructure: {
layoutType: 'grid',
properties: {
rows: 1,
columns: 1,
},
},
viewports: [
{
viewportOptions: { allowUnmatchedView: true },
displaySets: [
{
id: 'segDisplaySetId',
},
],
},
],
},
],
};
function getHangingProtocolModule() {
return [
{
name: segProtocol.id,
protocol: segProtocol,
},
];
}
export default getHangingProtocolModule;
export { segProtocol };

View File

@ -5,6 +5,7 @@ import { Types } from '@ohif/core';
import getSopClassHandlerModule from './getSopClassHandlerModule'; import getSopClassHandlerModule from './getSopClassHandlerModule';
import PanelSegmentation from './panels/PanelSegmentation'; import PanelSegmentation from './panels/PanelSegmentation';
import getHangingProtocolModule from './getHangingProtocolModule';
const Component = React.lazy(() => { const Component = React.lazy(() => {
return import( return import(
@ -79,6 +80,8 @@ const extension = {
* Examples include the default sop class handler provided by the default extension * Examples include the default sop class handler provided by the default extension
*/ */
getSopClassHandlerModule, getSopClassHandlerModule,
getHangingProtocolModule,
}; };
export default extension; export default extension;

View File

@ -1,11 +1,12 @@
import { Types } from '@ohif/core'; import { Types } from '@ohif/core';
const mpr: Types.HangingProtocol.Protocol = { const mpr: Types.HangingProtocol.Protocol = {
id: 'mpr',
name: 'Multi-Planar Reconstruction',
locked: true, locked: true,
hasUpdatedPriorsInformation: false, hasUpdatedPriorsInformation: false,
name: 'mpr', createdDate: '2021-02-23',
createdDate: '2021-02-23T19:22:08.894Z', modifiedDate: '2023-04-03',
modifiedDate: '2023-02-17',
availableTo: {}, availableTo: {},
editableBy: {}, editableBy: {},
// Unknown number of priors referenced - so just match any study // Unknown number of priors referenced - so just match any study
@ -303,7 +304,7 @@ const mprAnd3DVolumeViewport = {
function getHangingProtocolModule() { function getHangingProtocolModule() {
return [ return [
{ {
name: 'mpr', name: mpr.id,
protocol: mpr, protocol: mpr,
}, },
{ {

View File

@ -171,7 +171,7 @@ const commandsModule = ({
reset = false, reset = false,
}: HangingProtocolParams): boolean => { }: HangingProtocolParams): boolean => {
try { try {
// Stores in the state the reuseID to displaySetUID mapping // 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 // Pass in viewportId for the active viewport. This item will get set as
// the activeViewportId // the activeViewportId
const state = viewportGridService.getState(); const state = viewportGridService.getState();

View File

@ -1,3 +1,5 @@
import hpMNGrid from './hpMNGrid';
const defaultProtocol = { const defaultProtocol = {
id: 'default', id: 'default',
locked: true, locked: true,
@ -92,211 +94,6 @@ const defaultProtocol = {
], ],
createdDate: '2021-02-23T18:32:42.850Z', createdDate: '2021-02-23T18:32:42.850Z',
}, },
// This is an example of a 2x2 layout that requires at least 2 viewports
// filled to be navigatable to
{
name: '2x2',
// Indicate that the number of viewports needed is 2 filled viewports,
// but that 4 viewports are preferred.
stageActivation: {
enabled: {
minViewportsMatched: 4,
},
passive: {
minViewportsMatched: 2,
},
},
viewportStructure: {
layoutType: 'grid',
properties: {
rows: 2,
columns: 2,
},
},
viewports: [
{
viewportOptions: {
toolGroupId: 'default',
},
displaySets: [
{
id: 'defaultDisplaySetId',
matchedDisplaySetsIndex: 3,
},
],
},
{
viewportOptions: {
toolGroupId: 'default',
},
displaySets: [
{
id: 'defaultDisplaySetId',
matchedDisplaySetsIndex: 2,
},
],
},
{
viewportOptions: {
toolGroupId: 'default',
},
displaySets: [
{
id: 'defaultDisplaySetId',
matchedDisplaySetsIndex: 1,
},
],
},
{
viewportOptions: {},
displaySets: [
{
id: 'defaultDisplaySetId',
matchedDisplaySetsIndex: 0,
},
],
},
],
},
{
name: '3x1',
// Indicate that the number of viewports needed is 2 filled viewports,
// but that 4 viewports are preferred.
stageActivation: {
enabled: {
minViewportsMatched: 3,
},
},
viewportStructure: {
layoutType: 'grid',
properties: {
rows: 1,
columns: 3,
},
},
viewports: [
{
viewportOptions: {
toolGroupId: 'default',
},
displaySets: [
{
id: 'defaultDisplaySetId',
matchedDisplaySetsIndex: 2,
},
],
},
{
viewportOptions: {
toolGroupId: 'default',
},
displaySets: [
{
id: 'defaultDisplaySetId',
matchedDisplaySetsIndex: 1,
},
],
},
{
viewportOptions: {},
displaySets: [
{
id: 'defaultDisplaySetId',
},
],
},
],
},
// This is an example of a layout with more than one element in it
// It can be navigated to using , and . (prev/next stage)
{
name: '2x1',
// Indicate that the number of viewports needed is 1 filled viewport,
// but that 2 viewports are preferred.
stageActivation: {
enabled: {
minViewportsMatched: 3,
},
},
viewportStructure: {
layoutType: 'grid',
properties: {
rows: 1,
columns: 2,
},
},
viewports: [
{
viewportOptions: {
toolGroupId: 'default',
// initialImageOptions: {
// index: 180,
// preset: 'middle', // 'first', 'last', 'middle'
// },
},
displaySets: [
{
id: 'defaultDisplaySetId',
// Shows the second index of this image set
matchedDisplaySetsIndex: 1,
},
],
},
{
viewportOptions: {},
displaySets: [
{
id: 'defaultDisplaySetId',
},
],
},
],
},
{
name: '2x1',
// Indicate that the number of viewports needed is 1 filled viewport,
// but that 2 viewports are preferred.
stageActivation: {
enabled: {
minViewportsMatched: 3,
},
},
viewportStructure: {
layoutType: 'grid',
properties: {
rows: 2,
columns: 1,
},
},
viewports: [
{
viewportOptions: {},
displaySets: [
{
id: 'defaultDisplaySetId',
// Shows the second index of this image set
matchedDisplaySetsIndex: 1,
},
],
},
{
viewportOptions: {},
displaySets: [
{
id: 'defaultDisplaySetId',
},
],
},
],
},
], ],
}; };
@ -306,6 +103,11 @@ function getHangingProtocolModule() {
name: defaultProtocol.id, name: defaultProtocol.id,
protocol: defaultProtocol, protocol: defaultProtocol,
}, },
// Create a MxN hanging protocol available by default
{
name: hpMNGrid.id,
protocol: hpMNGrid,
},
]; ];
} }

View File

@ -1,24 +1,23 @@
import { Types } from '@ohif/core'; import { Types } from '@ohif/core';
/** /**
* This hanging protocol has multiple stages, which are enabled when * This hanging protocol can be activated on the primary mode by directly
* there are enough display sets with images to fill the stage, and * referencing it in a URL or by directly including it within a mode, e.g.:
* are passive when there is at least one display set. * `&hangingProtocolId=@ohif/mnGrid` added to the viewer URL
* Enabled display sets are navigated to by default, while passive ones * It is not included in the viewer mode by default.
* are navigated to manually using the ctrl+end keyboard shortcut.
*/ */
const hpMN: Types.HangingProtocol.Protocol = { const hpMN: Types.HangingProtocol.Protocol = {
hasUpdatedPriorsInformation: false, hasUpdatedPriorsInformation: false,
id: '@ohif/hp-extension.mn', id: '@ohif/mnGrid',
description: 'Has various hanging protocol layouts for use in testing', description: 'Has various hanging protocol grid layouts',
name: '2x2', name: '2x2',
protocolMatchingRules: [ protocolMatchingRules: [
{ {
id: 'OneOrMoreSeries', id: 'OneOrMoreSeries',
weight: 1, weight: 25,
attribute: 'numberOfDisplaySetsWithImages', attribute: 'numberOfDisplaySetsWithImages',
constraint: { constraint: {
greaterThan: 1, greaterThan: 0,
}, },
}, },
], ],
@ -32,6 +31,15 @@ const hpMN: Types.HangingProtocol.Protocol = {
greaterThan: { value: 0 }, greaterThan: { value: 0 },
}, },
}, },
// This display set will select the specified items by preference
// It has no affect if nothing is specified in the URL.
{
attribute: 'isDisplaySetFromUrl',
weight: 10,
constraint: {
equals: true,
},
},
], ],
}, },
}, },
@ -142,7 +150,6 @@ const hpMN: Types.HangingProtocol.Protocol = {
displaySets: [ displaySets: [
{ {
id: 'defaultDisplaySetId', id: 'defaultDisplaySetId',
reuseId: '0-0',
}, },
], ],
}, },
@ -153,9 +160,8 @@ const hpMN: Types.HangingProtocol.Protocol = {
}, },
displaySets: [ displaySets: [
{ {
id: 'defaultDisplaySetId',
matchedDisplaySetsIndex: 1, matchedDisplaySetsIndex: 1,
id: 'defaultDisplaySetId',
reuseId: '1-0',
}, },
], ],
}, },
@ -166,9 +172,8 @@ const hpMN: Types.HangingProtocol.Protocol = {
}, },
displaySets: [ displaySets: [
{ {
matchedDisplaySetsIndex: 2,
id: 'defaultDisplaySetId', id: 'defaultDisplaySetId',
reuseId: '0-1', matchedDisplaySetsIndex: 2,
}, },
], ],
}, },

View File

@ -38,12 +38,12 @@ const findingsContextMenu = {
}, },
// The example below shows how to include a delegating sub-menu, // The example below shows how to include a delegating sub-menu,
// Only available on the @ohif/hp-extension.mn hanging protocol // Only available on the @ohif/mnGrid hanging protocol
// To demonstrate, select the 3x1 layout from the protocol menu // To demonstrate, select the 3x1 layout from the protocol menu
// and right click on a measurement. // and right click on a measurement.
{ {
label: 'IncludeSubMenu', label: 'IncludeSubMenu',
selector: ({ protocol }) => protocol?.id === '@ohif/hp-extension.mn', selector: ({ protocol }) => protocol?.id === '@ohif/mnGrid',
delegating: true, delegating: true,
subMenu: 'orientationSelectionSubMenu', subMenu: 'orientationSelectionSubMenu',
}, },

View File

@ -1,17 +0,0 @@
import hpMN from './hpMN';
const hangingProtocols = [
{
name: '@ohif/hp-extension.mn',
protocol: hpMN,
},
];
/**
* Registers a single study hanging protocol which can be referenced as
* `@ohif/hp-exgtension.mn`, that has initial layouts which show images
* only display sets, up to a 2x2 view.
*/
export default function getHangingProtocolModule() {
return hangingProtocols;
}

View File

@ -2,14 +2,11 @@ import { Types } from '@ohif/core';
import { id } from './id'; import { id } from './id';
import getHangingProtocolModule from './hp';
import getCustomizationModule from './getCustomizationModule'; import getCustomizationModule from './getCustomizationModule';
// import {setViewportZoomPan, storeViewportZoomPan } from './custom-viewport/setViewportZoomPan'; // import {setViewportZoomPan, storeViewportZoomPan } from './custom-viewport/setViewportZoomPan';
import sameAs from './custom-attribute/sameAs'; import sameAs from './custom-attribute/sameAs';
import numberOfDisplaySets from './custom-attribute/numberOfDisplaySets'; import numberOfDisplaySets from './custom-attribute/numberOfDisplaySets';
import numberOfDisplaySetsWithImages from './custom-attribute/numberOfDisplaySetsWithImages';
import maxNumImageFrames from './custom-attribute/maxNumImageFrames'; import maxNumImageFrames from './custom-attribute/maxNumImageFrames';
import seriesDescriptionsFromDisplaySets from './custom-attribute/seriesDescriptionsFromDisplaySets';
/** /**
* The test extension provides additional behaviour for testing various * The test extension provides additional behaviour for testing various
@ -31,21 +28,11 @@ const testExtension: Types.Extensions.Extension = {
*/ */
preRegistration: ({ servicesManager }: Types.Extensions.ExtensionParams) => { preRegistration: ({ servicesManager }: Types.Extensions.ExtensionParams) => {
const { hangingProtocolService } = servicesManager.services; const { hangingProtocolService } = servicesManager.services;
hangingProtocolService.addCustomAttribute(
'seriesDescriptions',
'Series Descriptions',
seriesDescriptionsFromDisplaySets
);
hangingProtocolService.addCustomAttribute( hangingProtocolService.addCustomAttribute(
'numberOfDisplaySets', 'numberOfDisplaySets',
'Number of displays sets', 'Number of displays sets',
numberOfDisplaySets numberOfDisplaySets
); );
hangingProtocolService.addCustomAttribute(
'numberOfDisplaySetsWithImages',
'Number of displays sets with images',
numberOfDisplaySetsWithImages
);
hangingProtocolService.addCustomAttribute( hangingProtocolService.addCustomAttribute(
'maxNumImageFrames', 'maxNumImageFrames',
'Maximum of number of image frames', 'Maximum of number of image frames',
@ -58,9 +45,6 @@ const testExtension: Types.Extensions.Extension = {
); );
}, },
/** Registers some additional hanging protocols. See hp/index.tsx for more details */
getHangingProtocolModule,
/** Registers some customizations */ /** Registers some customizations */
getCustomizationModule, getCustomizationModule,
}; };

View File

@ -357,7 +357,7 @@ const toolbarButtons = [
{ {
commandName: 'setHangingProtocol', commandName: 'setHangingProtocol',
commandOptions: { commandOptions: {
protocolId: '@ohif/hp-extension.mn', protocolId: '@ohif/mnGrid',
stageId: '2x2', stageId: '2x2',
}, },
context: 'DEFAULT', context: 'DEFAULT',
@ -372,7 +372,7 @@ const toolbarButtons = [
{ {
commandName: 'setHangingProtocol', commandName: 'setHangingProtocol',
commandOptions: { commandOptions: {
protocolId: '@ohif/hp-extension.mn', protocolId: '@ohif/mnGrid',
stageId: '3x1', stageId: '3x1',
}, },
context: 'DEFAULT', context: 'DEFAULT',
@ -387,7 +387,7 @@ const toolbarButtons = [
{ {
commandName: 'setHangingProtocol', commandName: 'setHangingProtocol',
commandOptions: { commandOptions: {
protocolId: '@ohif/hp-extension.mn', protocolId: '@ohif/mnGrid',
stageId: '2x1', stageId: '2x1',
}, },
context: 'DEFAULT', context: 'DEFAULT',
@ -402,7 +402,7 @@ const toolbarButtons = [
{ {
commandName: 'setHangingProtocol', commandName: 'setHangingProtocol',
commandOptions: { commandOptions: {
protocolId: '@ohif/hp-extension.mn', protocolId: '@ohif/mnGrid',
stageId: '1x1', stageId: '1x1',
}, },
context: 'DEFAULT', context: 'DEFAULT',

View File

@ -9,7 +9,9 @@ import * as HangingProtocol from '../../types/HangingProtocol';
import { import {
isDisplaySetFromUrl, isDisplaySetFromUrl,
sopInstanceLocation, sopInstanceLocation,
} from './isDisplaySetFromUrl'; } from './custom-attribute/isDisplaySetFromUrl';
import numberOfDisplaySetsWithImages from './custom-attribute/numberOfDisplaySetsWithImages';
import seriesDescriptionsFromDisplaySets from './custom-attribute/seriesDescriptionsFromDisplaySets';
type Protocol = HangingProtocol.Protocol | HangingProtocol.ProtocolGenerator; type Protocol = HangingProtocol.Protocol | HangingProtocol.ProtocolGenerator;
@ -93,6 +95,16 @@ export default class HangingProtocolService extends PubSubService {
name: 'Gets the position of the specified sop instance', name: 'Gets the position of the specified sop instance',
callback: sopInstanceLocation, callback: sopInstanceLocation,
}, },
seriesDescriptions: {
name: 'seriesDescriptions',
description: 'List of Series Descriptions',
callback: seriesDescriptionsFromDisplaySets,
},
numberOfDisplaySetsWithImages: {
name: 'numberOfDisplaySetsWithImages',
desription: 'Number of displays sets with images',
numberOfDisplaySetsWithImages,
},
}; };
listeners = {}; listeners = {};
registeredImageLoadStrategies = {}; registeredImageLoadStrategies = {};

View File

@ -1,4 +1,4 @@
import { getSplitParam } from '../../utils'; import { getSplitParam } from '../../../utils';
/** Indicates if the given display set is the one specified in the /** Indicates if the given display set is the one specified in the
* displaySet parameter in the URL * displaySet parameter in the URL
@ -31,7 +31,7 @@ const isDisplaySetFromUrl = (displaySet): boolean => {
* Returns undefined to fallback to the defaultValue * Returns undefined to fallback to the defaultValue
*/ */
function sopInstanceLocation(displaySets) { function sopInstanceLocation(displaySets) {
const displaySet = displaySets[0]; const displaySet = displaySets?.[0];
if (!displaySet) return; if (!displaySet) return;
const initialSOPInstanceUID = getSplitParam('initialsopinstanceuid'); const initialSOPInstanceUID = getSplitParam('initialsopinstanceuid');
if (!initialSOPInstanceUID) return; if (!initialSOPInstanceUID) return;

View File

@ -2,7 +2,7 @@ describe('OHIF HP', () => {
const beforeSetup = () => { const beforeSetup = () => {
cy.checkStudyRouteInViewer( cy.checkStudyRouteInViewer(
'1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1', '1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1',
'&hangingProtocolId=@ohif/hp-extension.mn' '&hangingProtocolId=@ohif/mnGrid'
); );
cy.expectMinimumThumbnails(3); cy.expectMinimumThumbnails(3);
cy.initCornerstoneToolsAliases(); cy.initCornerstoneToolsAliases();

View File

@ -2,7 +2,7 @@ describe('OHIF Double Click', () => {
beforeEach(() => { beforeEach(() => {
cy.checkStudyRouteInViewer( cy.checkStudyRouteInViewer(
'1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1', '1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1',
'&hangingProtocolId=@ohif/hp-extension.mn' '&hangingProtocolId=@ohif/mnGrid'
); );
cy.expectMinimumThumbnails(3); cy.expectMinimumThumbnails(3);
cy.initCornerstoneToolsAliases(); cy.initCornerstoneToolsAliases();

View File

@ -14,7 +14,7 @@ window.config = {
{ {
friendlyName: 'StaticWado test data', friendlyName: 'StaticWado test data',
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
sourceName: 'dicomweb', sourceName: 'e2e',
configuration: { configuration: {
// The most important field to set for static WADO // The most important field to set for static WADO
staticWado: true, staticWado: true,
@ -32,6 +32,51 @@ window.config = {
singlepart: 'video,thumbnail,pdf', singlepart: 'video,thumbnail,pdf',
}, },
}, },
{
friendlyName: 'Static WADO Local Data',
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
sourceName: 'local',
configuration: {
name: 'DCM4CHEE',
qidoRoot: '/dicomweb',
wadoRoot: '/dicomweb',
qidoSupportsIncludeField: false,
supportsReject: true,
supportsStow: true,
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
enableStudyLazyLoad: true,
supportsFuzzyMatching: false,
supportsWildcard: true,
staticWado: true,
singlepart: 'bulkdata,video,pdf',
},
},
{
friendlyName: 'dcmjs DICOMWeb Server',
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
sourceName: 'ohif',
configuration: {
name: 'aws',
// old server
// wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
// qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
// wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
// new server
wadoUriRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
qidoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
wadoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
qidoSupportsIncludeField: false,
supportsReject: false,
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
enableStudyLazyLoad: true,
supportsFuzzyMatching: false,
supportsWildcard: true,
staticWado: true,
singlepart: 'bulkdata,video,pdf',
},
},
// { // {
// friendlyName: 'StaticWado default data', // friendlyName: 'StaticWado default data',
// namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', // namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
@ -92,93 +137,6 @@ window.config = {
// )) // ))
// }, // },
// }, // },
defaultDataSourceName: 'dicomweb', defaultDataSourceName: 'e2e',
hotkeys: [ hotkeys: [],
{
commandName: 'incrementActiveViewport',
label: 'Next Viewport',
keys: ['right'],
},
{
commandName: 'decrementActiveViewport',
label: 'Previous Viewport',
keys: ['left'],
},
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
{
commandName: 'flipViewportHorizontal',
label: 'Flip Horizontally',
keys: ['h'],
},
{
commandName: 'flipViewportVertical',
label: 'Flip Vertically',
keys: ['v'],
},
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
// {
// commandName: 'previousViewportDisplaySet',
// label: 'Previous Series',
// keys: ['pagedown'],
// },
// {
// commandName: 'nextViewportDisplaySet',
// label: 'Next Series',
// keys: ['pageup'],
// },
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
// ~ Window level presets
{
commandName: 'windowLevelPreset1',
label: 'W/L Preset 1',
keys: ['1'],
},
{
commandName: 'windowLevelPreset2',
label: 'W/L Preset 2',
keys: ['2'],
},
{
commandName: 'windowLevelPreset3',
label: 'W/L Preset 3',
keys: ['3'],
},
{
commandName: 'windowLevelPreset4',
label: 'W/L Preset 4',
keys: ['4'],
},
{
commandName: 'windowLevelPreset5',
label: 'W/L Preset 5',
keys: ['5'],
},
{
commandName: 'windowLevelPreset6',
label: 'W/L Preset 6',
keys: ['6'],
},
{
commandName: 'windowLevelPreset7',
label: 'W/L Preset 7',
keys: ['7'],
},
{
commandName: 'windowLevelPreset8',
label: 'W/L Preset 8',
keys: ['8'],
},
{
commandName: 'windowLevelPreset9',
label: 'W/L Preset 9',
keys: ['9'],
},
],
}; };