Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f360abb
Add customization URL parameter
wayfarer3130 May 4, 2026
38ddc9c
fix: Preserve should be customizeable
wayfarer3130 May 7, 2026
cee9d2a
Update customizations docs
wayfarer3130 May 7, 2026
ca6381b
Merge remote-tracking branch 'origin/master' into feat/customization-…
wayfarer3130 May 7, 2026
a7ecb45
fix: Overlay items on patient name
wayfarer3130 May 7, 2026
74c5986
Add customization test
wayfarer3130 May 7, 2026
7068511
Fix resolve to absolute path
wayfarer3130 May 7, 2026
60efc1c
fix: Warn on no data in load
wayfarer3130 May 7, 2026
b80b5ca
Remove unused customization stuff
wayfarer3130 May 7, 2026
8cc46ad
Merge branch 'master' into feat/customization-url-parameter
wayfarer3130 May 11, 2026
393fd48
fix: PR comments
wayfarer3130 May 13, 2026
091e7e5
Merge remote-tracking branch 'origin/master' into feat/customization-…
wayfarer3130 May 20, 2026
b9dcce7
Merge remote-tracking branch 'origin/master' into feat/customization-…
wayfarer3130 May 25, 2026
44a04ed
Merge remote-tracking branch 'origin/master' into feat/customization-…
wayfarer3130 Jun 17, 2026
d65f8aa
Update stored parameters to only use an array for mulitples
wayfarer3130 Jun 18, 2026
839d232
Remove requires ohif.* special call out
wayfarer3130 Jun 18, 2026
7ea9998
Merge remote-tracking branch 'origin/master' into feat/customization-…
wayfarer3130 Jun 19, 2026
4066f6a
Merge remote-tracking branch 'origin/master' into feat/customization-…
wayfarer3130 Jun 19, 2026
e42e383
Remove strict mode
wayfarer3130 Jun 19, 2026
e3e4ac4
PR comments
wayfarer3130 Jun 19, 2026
bf3cd63
Merge remote-tracking branch 'origin/master' into feat/customization-…
wayfarer3130 Jun 19, 2026
877e3de
Merge remote-tracking branch 'origin/master' into feat/customization-…
wayfarer3130 Jun 19, 2026
b7952d9
Document segmentation examples
wayfarer3130 Jun 19, 2026
a22d530
Add three examples as requested
wayfarer3130 Jun 19, 2026
bb1fcd7
Merge remote-tracking branch 'origin/master' into feat/customization-…
wayfarer3130 Jun 22, 2026
0fa21ec
PR comments
wayfarer3130 Jun 22, 2026
6cb031b
Merge remote-tracking branch 'origin/master' into feat/customization-…
wayfarer3130 Jun 23, 2026
88ad5be
lock
wayfarer3130 Jun 23, 2026
2b79531
Remove old customizatoin export
wayfarer3130 Jun 24, 2026
cc80c19
fix: Ordering issues on customization loads
wayfarer3130 Jun 24, 2026
271be36
Merge branch 'master' into feat/customization-url-parameter
wayfarer3130 Jun 24, 2026
13e3a31
fix: Use correct default for dev builds app config
wayfarer3130 Jun 26, 2026
949bbcd
Merge remote-tracking branch 'origin/master' into feat/customization-…
wayfarer3130 Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import './CustomizableViewportOverlay.css';
import { useViewportRendering } from '../../hooks';

const EPSILON = 1e-4;
const { formatPN } = utils;
const { formatPN, formatValue } = utils;

type ViewportData = StackViewportData | VolumeViewportData;

Expand Down Expand Up @@ -184,7 +184,12 @@ function CustomizableViewportOverlay({
} else {
const renderItem = customizationService.transform(item);

if (typeof renderItem.contentF === 'function') {
if (
renderItem &&
typeof renderItem === 'object' &&
'contentF' in renderItem &&
typeof renderItem.contentF === 'function'
) {
return renderItem.contentF(overlayItemProps);
}
}
Expand Down Expand Up @@ -357,7 +362,8 @@ function OverlayItem(props) {
const { instance, customization = {} } = props;
const { color, attribute, title, label, background } = customization;
const value = customization.contentF?.(props, customization) ?? instance?.[attribute];
if (value === undefined || value === null) {
const displayValue = formatValue(value);
if (displayValue === null || displayValue === '') {
return null;
}
return (
Expand All @@ -367,7 +373,7 @@ function OverlayItem(props) {
title={title}
>
{label ? <span className="mr-1 shrink-0">{label}</span> : null}
<span className="ml-0 shrink-0">{value}</span>
<span className="ml-0 shrink-0">{displayValue}</span>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { Button } from '@ohif/core/types';

import { EVENTS } from '@cornerstonejs/core';
import { ViewportGridService } from '@ohif/core';
import { ViewportGridService, ToolbarService } from '@ohif/core';
import i18n from 'i18next';

const { TOOLBAR_SECTIONS } = ToolbarService;

const callbacks = (toolName: string) => [
{
commandName: 'setViewportForToolConfiguration',
Expand Down Expand Up @@ -708,4 +710,91 @@ const toolbarButtons: Button[] = [
// },
];

export default toolbarButtons;
/**
* Default toolbar layout: which buttons appear in each toolbar section.
* Registered as a customization so modes (and `?customization=` modules) can
* append/replace entries without rebuilding.
*/
export const toolbarSections = {
[TOOLBAR_SECTIONS.primary]: [
'MeasurementTools',
'Zoom',
'Pan',
'TrackballRotate',
'WindowLevel',
'Capture',
'Layout',
'Crosshairs',
'MoreTools',
],

[TOOLBAR_SECTIONS.viewportActionMenu.topLeft]: ['orientationMenu', 'dataOverlayMenu'],

[TOOLBAR_SECTIONS.viewportActionMenu.bottomMiddle]: ['AdvancedRenderingControls'],

AdvancedRenderingControls: [
'windowLevelMenuEmbedded',
'voiManualControlMenu',
'Colorbar',
'opacityMenu',
'thresholdMenu',
],

[TOOLBAR_SECTIONS.viewportActionMenu.topRight]: [
'modalityLoadBadge',
'trackingStatus',
'navigationComponent',
],

[TOOLBAR_SECTIONS.viewportActionMenu.bottomLeft]: ['windowLevelMenu'],

MeasurementTools: [
'Length',
'Bidirectional',
'ArrowAnnotate',
'EllipticalROI',
'RectangleROI',
'CircleROI',
'PlanarFreehandROI',
'SplineROI',
'LivewireContour',
],

MoreTools: [
'Reset',
'rotate-right',
'flipHorizontal',
'ImageSliceSync',
'ReferenceLines',
'ImageOverlayViewer',
'StackScroll',
'invert',
'Probe',
'Cine',
'Angle',
'CobbAngle',
'Magnify',
'CalibrationLine',
'TagBrowser',
'AdvancedMagnify',
'UltrasoundDirectionalTool',
'WindowLevelRegion',
'SegmentLabelTool',
],
};

/**
* Customizations registered (at default scope) by the cornerstone extension:
* - `cornerstone.toolbarButtons` – the default toolbar button definitions
* - `cornerstone.toolbarSections` – the default toolbar layout (section -> button ids)
*
* Modes read these by name in `onModeEnter`; URL `?customization=` modules can
* extend them with immutability-helper commands (e.g. `$push` a new button).
*/
const toolbarButtonsCustomization = {
'cornerstone.toolbarButtons': toolbarButtons,
'cornerstone.toolbarSections': toolbarSections,
};

export { toolbarButtons };
export default toolbarButtonsCustomization;
2 changes: 2 additions & 0 deletions extensions/cornerstone/src/getCustomizationModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import volumeRenderingCustomization from './customizations/volumeRenderingCustom
import colorbarCustomization from './customizations/colorbarCustomization';
import modalityColorMapCustomization from './customizations/modalityColorMapCustomization';
import windowLevelPresetsCustomization from './customizations/windowLevelPresetsCustomization';
import toolbarButtonsCustomization from './customizations/toolbarButtonsCustomization';
import miscCustomization from './customizations/miscCustomization';
import captureViewportModalCustomization from './customizations/captureViewportModalCustomization';
import viewportDownloadWarningCustomization from './customizations/viewportDownloadWarningCustomization';
Expand All @@ -32,6 +33,7 @@ function getCustomizationModule({ commandsManager, servicesManager, extensionMan
...colorbarCustomization,
...modalityColorMapCustomization,
...windowLevelPresetsCustomization,
...toolbarButtonsCustomization,
...miscCustomization,
...captureViewportModalCustomization,
...viewportDownloadWarningCustomization,
Expand Down
3 changes: 3 additions & 0 deletions extensions/cornerstone/src/initCornerstoneTools.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
WindowLevelTool,
SegmentBidirectionalTool,
StackScrollTool,
PlanarRotateTool,
VolumeRotateTool,
ZoomTool,
MIPJumpToClickTool,
Expand Down Expand Up @@ -74,6 +75,7 @@ export default function initCornerstoneTools(configuration = {}) {
addTool(SegmentBidirectionalTool);
addTool(WindowLevelTool);
addTool(StackScrollTool);
addTool(PlanarRotateTool);
addTool(VolumeRotateTool);
addTool(ZoomTool);
addTool(ProbeTool);
Expand Down Expand Up @@ -138,6 +140,7 @@ const toolNames = {
WindowLevel: WindowLevelTool.toolName,
StackScroll: StackScrollTool.toolName,
Zoom: ZoomTool.toolName,
PlanarRotate: PlanarRotateTool.toolName,
VolumeRotate: VolumeRotateTool.toolName,
MipJumpToClick: MIPJumpToClickTool.toolName,
Length: LengthTool.toolName,
Expand Down
2 changes: 2 additions & 0 deletions extensions/default/src/DicomWebDataSource/qido.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function processResults(qidoStudies) {
accession: getString(qidoStudy['00080050']) || '', // short string, probably a number?
mrn: getString(qidoStudy['00100020']) || '', // medicalRecordNumber
patientName: utils.formatPN(getName(qidoStudy['00100010'])) || '',
patientBirthDate: getString(qidoStudy['00100030']) || '', // YYYYMMDD
instances: Number(getString(qidoStudy['00201208'])) || 0, // number
description: getString(qidoStudy['00081030']) || '',
modalities: getString(getModalities(qidoStudy['00080060'], qidoStudy['00080061'])) || '',
Expand Down Expand Up @@ -153,6 +154,7 @@ function mapParams(params, options = {}) {
'00081030', // Study Description
'00080060', // Modality
'00080090', // Referring Physician's Name
'00100030', // Patient's Birth Date
// Add more fields here if you want them in the result
].join(',');

Expand Down
2 changes: 1 addition & 1 deletion extensions/default/src/ViewerLayout/ViewerHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function ViewerHeader({ appConfig }: withAppTypes<{ appConfig: AppTypes.Config }
if (dataSourceIdx !== -1 && existingDataSource) {
searchQuery.append('datasources', pathname.substring(dataSourceIdx + 1));
}
preserveQueryParameters(searchQuery);
preserveQueryParameters(searchQuery, customizationService);

navigate({
pathname: '/',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { utils } from '@ohif/core';

export default {
'ohif.overlayItem': function (props) {
Expand All @@ -13,7 +14,8 @@ export default {
: this.contentF && typeof this.contentF === 'function'
? this.contentF(props)
: null;
if (!value) {
const displayValue = utils.formatValue(value);
if (!displayValue) {
return null;
}

Expand All @@ -24,7 +26,7 @@ export default {
title={this.title || ''}
>
{this.label && <span className="mr-1 shrink-0">{this.label}</span>}
<span className="font-light">{value}</span>
<span className="font-light">{displayValue}</span>
</span>
);
},
Expand Down
98 changes: 22 additions & 76 deletions modes/basic/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import update from 'immutability-helper';
import { ToolbarService, utils } from '@ohif/core';
import { utils } from '@ohif/core';

import initToolGroups from './initToolGroups';
import toolbarButtons from './toolbarButtons';
import { id } from './id';

const { TOOLBAR_SECTIONS } = ToolbarService;
const { structuredCloneWithFunctions } = utils;

/**
Expand Down Expand Up @@ -146,9 +144,22 @@ export function onModeEnter({
// Init Default and SR ToolGroups
initToolGroups(extensionManager, toolGroupService, commandsManager);

toolbarService.register(this.toolbarButtons);
// Toolbar buttons and layout may be supplied either as a customization name
// (a string, resolved through the customization service so `?customization=`
// modules can extend the cornerstone-registered defaults) or as a literal
// value (the button array / sections object) for modes that define them inline.
const resolveToolbarCustomization = (value: unknown) =>
typeof value === 'string' ? customizationService.getCustomization(value) : value;

for (const [key, section] of Object.entries(this.toolbarSections)) {
const toolbarButtons = resolveToolbarCustomization(this.toolbarButtons) as any;
const toolbarSections = (resolveToolbarCustomization(this.toolbarSections) ?? {}) as Record<
string,
string[]
>;

toolbarService.register(toolbarButtons);

for (const [key, section] of Object.entries(toolbarSections)) {
toolbarService.updateSection(key, section);
}

Expand Down Expand Up @@ -213,74 +224,6 @@ export function onModeExit({ servicesManager }: withAppTypes) {
cornerstoneViewportService.destroy();
}

export const toolbarSections = {
[TOOLBAR_SECTIONS.primary]: [
'MeasurementTools',
'Zoom',
'Pan',
'TrackballRotate',
'WindowLevel',
'Capture',
'Layout',
'Crosshairs',
'MoreTools',
],

[TOOLBAR_SECTIONS.viewportActionMenu.topLeft]: ['orientationMenu', 'dataOverlayMenu'],

[TOOLBAR_SECTIONS.viewportActionMenu.bottomMiddle]: ['AdvancedRenderingControls'],

AdvancedRenderingControls: [
'windowLevelMenuEmbedded',
'voiManualControlMenu',
'Colorbar',
'opacityMenu',
'thresholdMenu',
],

[TOOLBAR_SECTIONS.viewportActionMenu.topRight]: [
'modalityLoadBadge',
'trackingStatus',
'navigationComponent',
],

[TOOLBAR_SECTIONS.viewportActionMenu.bottomLeft]: ['windowLevelMenu'],

MeasurementTools: [
'Length',
'Bidirectional',
'ArrowAnnotate',
'EllipticalROI',
'RectangleROI',
'CircleROI',
'PlanarFreehandROI',
'SplineROI',
'LivewireContour',
],

MoreTools: [
'Reset',
'rotate-right',
'flipHorizontal',
'ImageSliceSync',
'ReferenceLines',
'ImageOverlayViewer',
'StackScroll',
'invert',
'Probe',
'Cine',
'Angle',
'CobbAngle',
'Magnify',
'CalibrationLine',
'TagBrowser',
'AdvancedMagnify',
'UltrasoundDirectionalTool',
'WindowLevelRegion',
'SegmentLabelTool',
],
};

export const basicLayout = {
id: ohif.layout,
props: {
Expand Down Expand Up @@ -343,7 +286,10 @@ export const modeInstance = {
hide: false,
displayName: 'Non-Longitudinal Basic',
_activatePanelTriggersSubscriptions: [],
toolbarSections,
// Toolbar buttons and layout are referenced by customization name; the
// cornerstone extension registers the defaults and `?customization=` modules
// can extend them. onModeEnter resolves these names via the customization service.
toolbarSections: 'cornerstone.toolbarSections',

/**
* Lifecycle hooks
Expand All @@ -365,7 +311,7 @@ export const modeInstance = {
// general handler needs to come last. For this case, the dicomvideo must
// come first to remove video transfer syntax before ohif uses images
sopClassHandlers,
toolbarButtons,
toolbarButtons: 'cornerstone.toolbarButtons',
enableSegmentationEdit: false,
nonModeModalities: NON_IMAGE_MODALITIES,
};
Expand All @@ -390,4 +336,4 @@ export const mode = {
};

export default mode;
export { initToolGroups, toolbarButtons };
export { initToolGroups };
1 change: 1 addition & 0 deletions modes/basic/src/initToolGroups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function initDefaultToolGroup(extensionManager, toolGroupService, commandsManage
{ toolName: toolNames.Angle },
{ toolName: toolNames.CobbAngle },
{ toolName: toolNames.Magnify },
{ toolName: toolNames.PlanarRotate },
{ toolName: toolNames.CalibrationLine },
{
toolName: toolNames.PlanarFreehandContourSegmentation,
Expand Down
4 changes: 2 additions & 2 deletions modes/longitudinal/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import i18n from 'i18next';
import { id } from './id';
import { initToolGroups, toolbarButtons, cornerstone,
import { initToolGroups, cornerstone,
ohif,
dicomsr,
dicomvideo,
Expand Down Expand Up @@ -73,4 +73,4 @@ const mode = {
};

export default mode;
export { initToolGroups, toolbarButtons };
export { initToolGroups };
Loading
Loading