fix/migration 3p11 (#5370)
This commit is contained in:
1 parent
df0593aac9
commit
6a1838bf0d
391 files changed
+2322
-215
No files matched your search
@@ -0,0 +1,72 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
sidebar_label: Commands
|
||||
summary: Migration guide for OHIF 3.11's different commands
|
||||
---
|
||||
|
||||
|
||||
|
||||
## updateStoredPositionPresentation
|
||||
|
||||
now uses displaySetInstanceUIDs instead of displaySetInstanceUID as a parameter.
|
||||
|
||||
|
||||
## `loadSRMeasurements` Command
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* The `loadSRMeasurements` command, previously part of the `@ohif/extension-cornerstone-dicom-sr` extension, has been **removed**.
|
||||
* Its functionality of hydrating a Structured Report (SR) and displaying its referenced series in a viewport is now primarily handled by the new `hydrateSecondaryDisplaySet` command available in the `@ohif/extension-cornerstone` extension.
|
||||
* The `hydrateStructuredReport` command (from `@ohif/extension-cornerstone-dicom-sr`) now solely focuses on hydrating the SR and returning its data, without directly manipulating viewports.
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
If you were previously using the `loadSRMeasurements` command to load and display SR measurements, you should update your code to use the `hydrateSecondaryDisplaySet` command.
|
||||
|
||||
1. **Identify `loadSRMeasurements` Usage:**
|
||||
Locate where your code calls `commandsManager.runCommand('loadSRMeasurements', ...)`.
|
||||
|
||||
2. **Update to `hydrateSecondaryDisplaySet`:**
|
||||
Replace the call to `loadSRMeasurements` with `hydrateSecondaryDisplaySet`. You will need to pass the full `displaySet` object for the SR and the target `viewportId`.
|
||||
|
||||
```diff
|
||||
- // Old way: using loadSRMeasurements
|
||||
- commandsManager.runCommand('loadSRMeasurements', {
|
||||
- displaySetInstanceUID: srDisplaySetInstanceUID,
|
||||
- // viewportId was implicitly the active one or not directly specifiable here
|
||||
- });
|
||||
-
|
||||
+ // New way: using hydrateSecondaryDisplaySet
|
||||
+ const { displaySetService, viewportGridService } = servicesManager.services;
|
||||
+
|
||||
+ // 1. Get the SR displaySet object
|
||||
+ const srDisplaySet = displaySetService.getDisplaySetByUID(srDisplaySetInstanceUID);
|
||||
+
|
||||
+ // 2. Determine the target viewportId (e.g., active viewport)
|
||||
+ const viewportId = viewportGridService.getActiveViewportId(); // Or your specific viewportId
|
||||
+
|
||||
+ if (srDisplaySet && viewportId) {
|
||||
+ commandsManager.runCommand('hydrateSecondaryDisplaySet', {
|
||||
+ displaySet: srDisplaySet,
|
||||
+ viewportId: viewportId,
|
||||
+ });
|
||||
+ } else {
|
||||
+ console.warn('SR DisplaySet or ViewportId not found, cannot hydrate.');
|
||||
+ }
|
||||
```
|
||||
|
||||
**Explanation:**
|
||||
|
||||
* The `loadSRMeasurements` command was responsible for both hydrating the SR (getting its measurement data and referenced series UIDs) and then updating the viewport to show the referenced series.
|
||||
* The new `hydrateSecondaryDisplaySet` command, when given an SR `displaySet` (`displaySet.Modality === 'SR'`), will:
|
||||
1. Internally call the `hydrateStructuredReport` command to parse the SR and get its details (including `SeriesInstanceUIDs` of referenced images).
|
||||
2. Then, it will automatically find the corresponding image display sets for those `SeriesInstanceUIDs`.
|
||||
3. Finally, it will update the specified `viewportId` to display the primary referenced image series.
|
||||
* This change centralizes the logic for hydrating secondary display sets (like SR, SEG, RTSTRUCT) and updating viewports into the `hydrateSecondaryDisplaySet` command within the core Cornerstone extension.
|
||||
|
||||
**Note on UI/Button Changes:**
|
||||
The UI button typically associated with "Load SR" (often seen in viewport corners or specific contexts) has also been refactored. The hydration of SRs is now often triggered by:
|
||||
* The `TrackedMeasurementsContext` if the `@ohif/extension-measurement-tracking` is in use.
|
||||
* The new `ModalityLoadBadge` component, which can appear in viewports containing SR, SEG, or RTSTRUCT display sets, offering a "LOAD" action that calls `hydrateSecondaryDisplaySet`.
|
||||
|
||||
If you had custom UI invoking `loadSRMeasurements`, you'll need to adapt it to call `hydrateSecondaryDisplaySet` as described above.
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
title: Hydration
|
||||
sidebar_position: 3
|
||||
sidebar_label: Hydration
|
||||
summary: Migration guide for OHIF 3.11's hydration system changes, including the transition to a centralized hydration dialog and command-based hydration for secondary display sets.
|
||||
---
|
||||
|
||||
## Update Hydration Logic:
|
||||
* The `promptHydrateSEG` and `promptHydrateRT` functions have been updated to use the generic `utils.promptHydrationDialog` from `@ohif/extension-cornerstone`.
|
||||
* The actual hydration is now often triggered by the `hydrateSecondaryDisplaySet` command.
|
||||
|
||||
*Example: `promptHydrateRT` change*
|
||||
|
||||
```diff
|
||||
// extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts
|
||||
- const RESPONSE = {
|
||||
- NO_NEVER: -1,
|
||||
- CANCEL: 0,
|
||||
- HYDRATE_SEG: 5,
|
||||
- };
|
||||
+ import { utils, Types } from '@ohif/extension-cornerstone';
|
||||
|
||||
function promptHydrateRT({
|
||||
servicesManager,
|
||||
rtDisplaySet,
|
||||
viewportId,
|
||||
preHydrateCallbacks,
|
||||
hydrateRTDisplaySet,
|
||||
-}: withAppTypes) {
|
||||
- const { uiViewportDialogService, customizationService } = servicesManager.services;
|
||||
- // ... lots of old promise and dialog logic
|
||||
- return new Promise(async function (resolve, reject) {
|
||||
- // ...
|
||||
- });
|
||||
-}
|
||||
-
|
||||
-function _askHydrate(
|
||||
- // ...
|
||||
-) {
|
||||
- // ...
|
||||
-}
|
||||
+}: {
|
||||
+ servicesManager: AppTypes.ServicesManager;
|
||||
+ rtDisplaySet: AppTypes.DisplaySet;
|
||||
+ viewportId: string;
|
||||
+ preHydrateCallbacks?: Types.HydrationCallback[];
|
||||
+ hydrateRTDisplaySet: Types.HydrationCallback;
|
||||
+}) {
|
||||
+ return utils.promptHydrationDialog({
|
||||
+ servicesManager,
|
||||
+ viewportId,
|
||||
+ displaySet: rtDisplaySet,
|
||||
+ preHydrateCallbacks,
|
||||
+ hydrateCallback: hydrateRTDisplaySet,
|
||||
+ type: 'RTSTRUCT',
|
||||
+ });
|
||||
}
|
||||
```
|
||||
The `hydrateRTDisplaySet` callback passed to this function would now typically involve the `hydrateSecondaryDisplaySet` command.
|
||||
```diff
|
||||
// extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx
|
||||
useEffect(() => {
|
||||
if (rtIsLoading) {
|
||||
return;
|
||||
}
|
||||
promptHydrateRT({
|
||||
servicesManager,
|
||||
viewportId,
|
||||
rtDisplaySet,
|
||||
- preHydrateCallbacks: [storePresentationState],
|
||||
- hydrateRTDisplaySet,
|
||||
- }).then(isHydrated => {
|
||||
- if (isHydrated) {
|
||||
- setIsHydrated(true);
|
||||
- }
|
||||
+ hydrateRTDisplaySet: async () => {
|
||||
+ return commandsManager.runCommand('hydrateSecondaryDisplaySet', {
|
||||
+ displaySet: rtDisplaySet,
|
||||
+ viewportId,
|
||||
+ });
|
||||
+ },
|
||||
});
|
||||
- }, [servicesManager, viewportId, rtDisplaySet, rtIsLoading]);
|
||||
+ }, [servicesManager, viewportId, rtDisplaySet, rtIsLoading, commandsManager]);
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
sidebar_label: 3.10 -> 3.11
|
||||
---
|
||||
|
||||
# Migration Guide
|
||||
|
||||
This guide provides information about migrating from OHIF version 3.10 to version 3.11.
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
sidebar_label: Other Changes
|
||||
summary: Migration guide for OHIF 3.11 additional changes
|
||||
---
|
||||
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* **`connectToolsToMeasurementService` parameters:** The `connectToolsToMeasurementService` function from the `@ohif/cornerstone-extensions` now take different arguments.
|
||||
* **`data-viewportId`** The `data-viewportId` naming was not compliant with react and was causing warnings. Rename references to `data-viewportid`.
|
||||
* **`setIsReferenceViewable`** is no longer available or required by ViewportGridService. Instead, the cornerstone viewports
|
||||
themselves provide the isReferenceViewable. This occurs because there were a lot more deciding issues to navigate to viewports than could be added to viewport grid service.
|
||||
* **`JUMP_TO_MEASUREMENT_VIEWPORT` and `JUMP_TO_MEASUREMENT_LAYOUT`** are combined into `JUMP_TO_MEASUREMENT` with no
|
||||
consume event. Only the single event is fired. This will need to be handled to redirect the changes to the appropriate viewport type as it was not possible to figure that out with generic information available in `ViewportGridService`
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Update `connectToolsToMeasurementService` Method Calls:**
|
||||
* Now the connectToolsToMeasurementService receives all service object arguments (servicesManager, commandsManager and extensionManager).
|
||||
|
||||
```diff
|
||||
// Before
|
||||
- connectToolsToMeasurementService(servicesManager);
|
||||
|
||||
// After
|
||||
+ connectToolsToMeasurementService({
|
||||
+ servicesManager,
|
||||
+ commandsManager,
|
||||
+ extensionsManager
|
||||
+ });
|
||||
```
|
||||
|
||||
**Images sort by position patient**
|
||||
|
||||
The ImageSet sort has been modified: images are now sorted by ImagePositionPatient by default. If ImagePositionPatient is not available, the sort will fall back to InstanceNumber.
|
||||
|
||||
**To revert to the previous sorting method, you can add the customization instanceSortingCriteria as shown below:**
|
||||
|
||||
```
|
||||
customizationService.setCustomizations({
|
||||
'instanceSortingCriteria': {
|
||||
$set: {defaultSortFunctionName: 'default'},
|
||||
},
|
||||
});
|
||||
```
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
sidebar_label: Toolbar Service
|
||||
summary: Migration guide for OHIF 3.11's toolbar service changes, including the transition from `ViewportActionCornersService` to `ToolbarService`
|
||||
---
|
||||
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* **`ViewportActionCornersService` Removed:** The `ViewportActionCornersService` and its associated provider (`ViewportActionCornersProvider`) and hook (`useViewportActionCorners`) have been removed. Functionality for viewport corner items is now integrated into the `ToolbarService` and standard toolbar components.
|
||||
* **Viewport Corner Items as Toolbar Buttons:** Items previously managed by `ViewportActionCornersService` are now configured as regular toolbar buttons. They are assigned to specific toolbar sections (e.g., `viewportActionMenu.topLeft`) and rendered using `Toolbar` components within the viewport corners.
|
||||
* **`ToolbarService` API Updates:**
|
||||
* `ToolbarService.addButtons()` has been renamed to `ToolbarService.register()` to better reflect its purpose of registering button definitions rather than just adding them.
|
||||
* `ToolbarService.createButtonSection()` has been renamed to `ToolbarService.updateSection()` to better reflect that it is not about creating new sections but updating existing ones.
|
||||
* `ToolbarService` now has a `sections` property (e.g., `toolbarService.sections.viewportActionMenu.topLeft`) providing predefined section names.
|
||||
* **Enhanced `useToolbar` Hook:**
|
||||
* The `useToolbar` hook now returns additional state management functions for toolbar items:
|
||||
* `openItem`, `closeItem`, `isItemOpen` (for managing dropdown/popover states).
|
||||
* `lockItem`, `unlockItem`, `toggleLock`, `isItemLocked`.
|
||||
* `showItem`, `hideItem`, `toggleVisibility`, `isItemVisible`.
|
||||
* The `onInteraction` callback now receives `itemId` and `viewportId`.
|
||||
* **Toolbar Button Configuration:**
|
||||
* The `groupId` prop in button configurations (e.g., for `ohif.toolButtonList`, `ohif.toolBoxButtonGroup`) is generally replaced by directly using `buttonSection` to define the set of buttons.
|
||||
* Button `evaluate` functions can now leverage `evaluateProps.hideWhenDisabled` to automatically hide a button when it's disabled.
|
||||
* **New UI Components & Hooks for Viewport Corners:**
|
||||
* Specialized components like `ModalityLoadBadge`, `NavigationComponent`, `TrackingStatus`, `ViewportDataOverlayMenuWrapper`, `ViewportOrientationMenuWrapper`, `WindowLevelActionMenuWrapper` are now used as toolbar buttons, typically in viewport action menu sections.
|
||||
* `useViewportHover` hook can be used to determine if a viewport is hovered or active, controlling the visibility of corner toolbars.
|
||||
* **`IconPresentationProvider`:** A new `IconPresentationProvider` and `useIconPresentation` hook have been introduced to standardize icon sizing and styling within toolbars and related components.
|
||||
* **Legacy Toolbar Components Removed:** `ToolbarSplitButtonWithServicesLegacy` and `ToolbarButtonGroupWithServicesLegacy` have been removed.
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Update `ToolbarService` Method Calls:**
|
||||
* Although the previous method also works but gives warning in the console when used.
|
||||
* Replace all instances of `toolbarService.addButtons(...)` with `toolbarService.register(...)`.
|
||||
* Replace all instances of `toolbarService.createButtonSection(...)` with `toolbarService.updateSection(...)`.
|
||||
|
||||
```diff
|
||||
// Before
|
||||
- toolbarService.addButtons(toolbarButtons);
|
||||
- toolbarService.createButtonSection('primary', ['Zoom', 'Pan']);
|
||||
|
||||
// After
|
||||
+ toolbarService.register(toolbarButtons);
|
||||
+ toolbarService.updateSection('primary', ['Zoom', 'Pan']);
|
||||
```
|
||||
|
||||
2. **Migrate Viewport Action Corner Items:**
|
||||
* Remove any direct usage of the old `ViewportActionCornersService`, `useViewportActionCorners`, or `ViewportActionCornersProvider`.
|
||||
* Define your viewport corner items (like orientation menu, W/L menu, data overlay menu) as standard toolbar buttons using `toolbarService.register()`.
|
||||
* Assign these buttons to the new dedicated viewport action menu sections. You can access these section names via `toolbarService.sections.viewportActionMenu.<location>`, e.g., `toolbarService.sections.viewportActionMenu.topLeft`.
|
||||
|
||||
```diff
|
||||
// Before: Customization in viewportActionMenuCustomizations.ts (now deleted)
|
||||
// or direct use of ViewportActionCornersService.addComponent
|
||||
- // Example: viewportActionCornersService.addComponent({ viewportId, id: 'orientationMenu', component: MyOrientationMenu, location: 'topLeft' });
|
||||
|
||||
// After: In your mode's onModeEnter or similar setup
|
||||
+ const myViewportCornerButtons = [
|
||||
+ {
|
||||
+ id: 'orientationMenu',
|
||||
+ uiType: 'ohif.orientationMenu', // Or your custom component registered as a UI type
|
||||
+ props: { /* ... props for your component ... */ }
|
||||
+ },
|
||||
+ // ... other corner buttons
|
||||
+ ];
|
||||
+ toolbarService.register(myViewportCornerButtons);
|
||||
+ toolbarService.updateSection(
|
||||
+ toolbarService.sections.viewportActionMenu.topLeft,
|
||||
+ ['orientationMenu', /* other button IDs */]
|
||||
+ );
|
||||
```
|
||||
* The `OHIFViewportActionCorners.tsx` component now internally uses `Toolbar` components for each corner, which are populated by these sections.
|
||||
* For custom components that act as menus (e.g., popovers), use the `onOpen`, `onClose`, `isOpen` props passed down by the `Toolbar` component (which get these from `useToolbar`).
|
||||
|
||||
```diff
|
||||
// Before: Custom component might have managed its own open state
|
||||
- // const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
- // const handleOpenChange = (open) => setIsMenuOpen(open);
|
||||
|
||||
// After: Custom component receives isOpen, onOpen, onClose from Toolbar
|
||||
+ function MyCustomMenuButton({ isOpen, onOpen, onClose, ...rest }) {
|
||||
+ const handleOpenChange = (openState: boolean) => {
|
||||
+ if (openState) {
|
||||
+ onOpen?.();
|
||||
+ } else {
|
||||
+ onClose?.();
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
+ return (
|
||||
+ <Popover open={isOpen} onOpenChange={handleOpenChange}>
|
||||
+ {/* ... PopoverTrigger and PopoverContent ... */}
|
||||
+ </Popover>
|
||||
+ );
|
||||
+ }
|
||||
```
|
||||
|
||||
3. **Adapt Toolbar Button and Component Configurations:**
|
||||
|
||||
The configuration of toolbar buttons, especially how they relate to sections
|
||||
|
||||
* **Button Section Association via `props.buttonSection`:**
|
||||
|
||||
The toolbar service now offers two ways to define this association:
|
||||
|
||||
* **A. Simple Approach: `buttonSection: true` (Implicitly Uses Button's Own ID)**
|
||||
|
||||
If a button definition includes `props: { buttonSection: true }`, the `ToolbarService` automatically sets the effective `buttonSection` ID to be the same as the button's own `id`.
|
||||
|
||||
```javascript
|
||||
// Example: A ToolButtonList component's definition in toolbarButtons.ts
|
||||
// {
|
||||
// id: 'MeasurementTools', // ID of this ToolButtonList component
|
||||
// uiType: 'ohif.toolButtonList',
|
||||
// props: {
|
||||
// buttonSection: true // This ToolButtonList will render the section named 'MeasurementTools'
|
||||
// }
|
||||
// }
|
||||
```
|
||||
|
||||
later you can use it like
|
||||
|
||||
|
||||
```javascript
|
||||
toolbarService.updateSection('MeasurementTools', ['Length', 'Bidirectional', ...]);
|
||||
```
|
||||
|
||||
* **B. Flexible Approach: `buttonSection: 'customSectionName'` (Explicit Section ID)**
|
||||
|
||||
You can explicitly provide a string for `props.buttonSection` if the button should be associated with a section ID that is different from its own `id`, or if you prefer explicit naming.
|
||||
|
||||
```javascript
|
||||
// Example: A ToolButtonList component's definition
|
||||
// {
|
||||
// id: 'MySpecialToolList', // ID of this ToolButtonList component
|
||||
// uiType: 'ohif.toolButtonList',
|
||||
// props: {
|
||||
// buttonSection: 'toolsForAdvancedUsers', // This list renders 'toolsForAdvancedUsers' section
|
||||
// }
|
||||
// }
|
||||
```
|
||||
|
||||
* **`evaluate` Function Enhancement:**
|
||||
* Button `evaluate` functions can now leverage `evaluateProps: { hideWhenDisabled: true }` in your button definition to automatically hide a button when it's disabled.
|
||||
|
||||
* **Wrapper Component `onInteraction` (e.g., `ToolButtonListWrapper`):**
|
||||
* Update wrappers like `ToolBoxButtonGroupWrapper` and `ToolButtonListWrapper`:
|
||||
* The `groupId` prop is replaced by `id` (which is the ID of the wrapper button component itself).
|
||||
* The `onInteraction` callback in these wrappers now provides `id` (the wrapper's ID) instead of `groupId`.
|
||||
|
||||
|
||||
4. **Adopt `IconPresentationProvider` (Optional but Recommended):**
|
||||
* For consistent icon styling across your application's toolbars, wrap a high-level component (like your main `Header` or layout component) with `<IconPresentationProvider size="yourDefaultSize">`.
|
||||
* Custom tool button components can then use the `useIconPresentation` hook to get appropriate class names for icons or a pre-styled `IconContainer`.
|
||||
|
||||
```diff
|
||||
// In your main App.tsx or Header.tsx
|
||||
+ import { IconPresentationProvider, ToolButton } from '@ohif/ui-next';
|
||||
// ...
|
||||
+ <IconPresentationProvider
|
||||
+ size="large" // Or "medium", "small", "tiny", or a number
|
||||
+ IconContainer={ToolButton} // Optional: default is Button
|
||||
+ containerProps={{ variant: 'primary', className: 'custom-container-class' }} // Optional
|
||||
+ >
|
||||
{/* Your Header content including Toolbars */}
|
||||
+ </IconPresentationProvider>
|
||||
|
||||
// In a custom tool button using icons
|
||||
+ import { useIconPresentation, Icons } from '@ohif/ui-next';
|
||||
+ function MyCustomToolButton({ iconName }) {
|
||||
+ const { className: iconClassName } = useIconPresentation();
|
||||
+ return <button><Icons.ByName name={iconName} className={iconClassName} /></button>;
|
||||
+ }
|
||||
```
|
||||
|
||||
5. **Remove Legacy Component Usage:**
|
||||
* Replace any usage of `ToolbarSplitButtonWithServicesLegacy` and `ToolbarButtonGroupWithServicesLegacy` with the newer patterns, typically by configuring individual buttons and using `ToolButtonList` or `ButtonGroup` from `@ohif/ui-next` directly, driven by `useToolbar`.
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
sidebar_label: UI
|
||||
summary: Migration guide for OHIF 3.11's UI changes, including the transition from `ViewportActionCornersService` to `ToolbarService` and the introduction of `useToolbar` hook.
|
||||
---
|
||||
|
||||
|
||||
## New useIconPresentation hook
|
||||
|
||||
This section details the introduction of the `IconPresentationProvider` and `useIconPresentation` hook, offering an optional way to manage icon size and container styling within the UI.
|
||||
|
||||
### Key Changes:
|
||||
|
||||
* **New Context and Hook:** Introduction of `IconPresentationProvider` and `useIconPresentation` to provide a standardized way to control the size and potentially the container component/props for icons and related interactive elements (like `ToolButton`).
|
||||
|
||||
### Migration Steps:
|
||||
|
||||
Using the `IconPresentationProvider` is **entirely optional**. If you do not wrap your components with this provider, components like `ToolButton` will continue to use their explicit `size` prop and default styling.
|
||||
|
||||
However, if you wish to centrally manage the presentation of icons within a specific part of your application's component tree, follow these steps:
|
||||
|
||||
1. **Identify the component subtree:** Determine which section of your UI you want to apply consistent icon styling to.
|
||||
|
||||
2. **Wrap with `IconPresentationProvider`:** Wrap the root of that component subtree with the `IconPresentationProvider`. Pass the desired `size` prop. You can also optionally provide a custom `IconContainer` component and `containerProps` if you want to change the wrapper around the icon itself (e.g., switching from a `Button` to a `ToolButton` or applying specific styling).
|
||||
|
||||
```jsx
|
||||
import { IconPresentationProvider, ToolButton } from '@ohif/ui-next';
|
||||
|
||||
function MyComponentTree() {
|
||||
return (
|
||||
// Icons and ToolButtons within this provider will inherit 'large' size
|
||||
<IconPresentationProvider size="large">
|
||||
{/* Any components inside that consume the context */}
|
||||
<ToolButton id="myTool" icon="Circle" tooltip="Draw Circle" />
|
||||
{/* ... other components ... */}
|
||||
</IconPresentationProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
3. **Consume the context in components (if building custom components):** If you are building a custom component that renders an icon and want it to respect the provider's settings, use the `useIconPresentation` hook within that component. This hook provides the configured size, a calculated CSS class name (`className`), the specified `IconContainer` component, and its `containerProps`.
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import { useIconPresentation, Icons } from '@ohif/ui-next';
|
||||
|
||||
function MyCustomIconButton({ iconName, ...rest }) {
|
||||
// This hook reads the nearest IconPresentationProvider context
|
||||
const { className, IconContainer, containerProps } = useIconPresentation();
|
||||
|
||||
// Use the provided IconContainer and its props
|
||||
return (
|
||||
<IconContainer {...rest} {...containerProps}>
|
||||
{/* Use the calculated className for the icon */}
|
||||
<Icons.ByName name={iconName} className={className} />
|
||||
</IconContainer>
|
||||
);
|
||||
}
|
||||
```
|
||||
*Note: Built-in components like `ToolButton` in `@ohif/ui-next` have been updated internally to consume this context automatically if a provider is available.*
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
sidebar_label: Viewport Corners
|
||||
summary: Migration guide for OHIF 3.11's viewport corners customization changes, including the transition from individual item configurations to location-based arrays and the removal of index priorities.
|
||||
---
|
||||
|
||||
|
||||
Okay, here's a migration guide based on the provided diff, focusing on the introduction of `TrackingStatus`, `ModalityLoadBadge`, and `NavigationComponent`.
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* **Deprecated `ViewportActionCornersService`**: The `ViewportActionCornersService` and its associated provider (`ViewportActionCornersProvider`) have been removed. UI elements previously managed by this service are now typically handled by dedicated components integrated via the `ToolbarService`.
|
||||
* **New Centralized UI Components**:
|
||||
* `ModalityLoadBadge`: A new component in `@ohif/extension-cornerstone` that displays the status (e.g., SEG/RT/SR loaded or requiring hydration) and a "LOAD" button for secondary display sets (SEG, RTSTRUCT, SR). This replaces the inline status and load logic within individual viewport components like `OHIFCornerstoneSEGViewport` and `OHIFCornerstoneRTViewport`.
|
||||
* `TrackingStatus`: A new component in `@ohif/extension-cornerstone` to indicate if measurements in a viewport are being tracked. This replaces inline tracking status indicators previously in `OHIFCornerstoneSRMeasurementViewport` and `TrackedCornerstoneViewport`.
|
||||
* `NavigationComponent`: A new component in `@ohif/extension-cornerstone` that provides navigation arrows (e.g., for segments in SEG/RT or measurements in SR/tracked series). This replaces the `ViewportActionArrows` previously instantiated directly within viewport components.
|
||||
* **Viewport Simplification**: Viewport components like `OHIFCornerstoneSEGViewport`, `OHIFCornerstoneRTViewport`, and `OHIFCornerstoneSRMeasurementViewport` have been simplified. They no longer manage their own status indicators, load buttons, or navigation arrows. They now primarily delegate rendering to `OHIFCornerstoneViewport`.
|
||||
* **Refactored Hydration Prompts**: Utility functions like `promptHydrateSEG` and `promptHydrateRT` now use a centralized `utils.promptHydrationDialog` from `@ohif/extension-cornerstone`.
|
||||
* **Centralized Hydration Command**: A new command `hydrateSecondaryDisplaySet` has been added to `@ohif/extension-cornerstone` to handle the hydration logic for SEG, RTSTRUCT, and SR display sets.
|
||||
* **New Hooks**: Several new hooks have been introduced in `@ohif/extension-cornerstone` (e.g., `useViewportDisplaySets`, `useMeasurementTracking`, `useViewportSegmentations`, `useViewportHover`) to provide data and state for these new UI components.
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Remove `ViewportActionCornersService` Usage**:
|
||||
* If you were using `ViewportActionCornersService` to add custom components to viewport corners, you will need to refactor this. The recommended approach is to define these components as toolbar buttons and place them in designated viewport action menu sections (e.g., `viewportActionMenu.topLeft`) using the `ToolbarService`.
|
||||
* The internal status components (`_getStatusComponent`) and `ViewportActionArrows` within specific viewports (SEG, RT, SR) have been removed. Their functionality is now provided by `ModalityLoadBadge`, `TrackingStatus`, and `NavigationComponent`.
|
||||
|
||||
|
||||
3. **Integrate New UI Components via `ToolbarService`**:
|
||||
* The `ModalityLoadBadge`, `TrackingStatus`, and `NavigationComponent` are now registered with the `ToolbarService` within the `@ohif/extension-cornerstone`'s `getToolbarModule`.
|
||||
* Modes (e.g., `longitudinal`) should define toolbar sections for viewport corners and add these components to those sections.
|
||||
|
||||
*Example: Adding components to viewport corners in `longitudinal` mode*
|
||||
```diff
|
||||
// modes/longitudinal/src/index.ts
|
||||
function modeFactory({ modeConfiguration }) {
|
||||
return {
|
||||
// ...
|
||||
onModeEnter: ({ servicesManager, extensionManager, commandsManager }: withAppTypes) => {
|
||||
// ...
|
||||
toolbarService.addButtons(toolbarButtons);
|
||||
toolbarService.createButtonSection('primary', [
|
||||
// ... primary tools
|
||||
]);
|
||||
|
||||
+ toolbarService.updateSection(toolbarService.sections.viewportActionMenu.topLeft, [
|
||||
+ 'orientationMenu',
|
||||
+ 'dataOverlayMenu',
|
||||
+ 'windowLevelMenu',
|
||||
+ ]);
|
||||
+ toolbarService.updateSection(toolbarService.sections.viewportActionMenu.topRight, [
|
||||
+ 'modalityLoadBadge',
|
||||
+ 'trackingStatus',
|
||||
+ 'navigationComponent',
|
||||
+ ]);
|
||||
// ...
|
||||
},
|
||||
```
|
||||
And ensure these buttons are defined in your mode's `toolbarButtons.ts`:
|
||||
```diff
|
||||
// modes/longitudinal/src/toolbarButtons.ts
|
||||
+ {
|
||||
+ id: 'modalityLoadBadge',
|
||||
+ uiType: 'ohif.modalityLoadBadge',
|
||||
+ props: {
|
||||
+ // ... props like icon, label, tooltip, evaluate
|
||||
+ evaluate: {
|
||||
+ name: 'evaluate.modalityLoadBadge',
|
||||
+ hideWhenDisabled: true,
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ {
|
||||
+ id: 'navigationComponent',
|
||||
+ uiType: 'ohif.navigationComponent',
|
||||
+ props: {
|
||||
+ // ... props
|
||||
+ evaluate: {
|
||||
+ name: 'evaluate.navigationComponent',
|
||||
+ hideWhenDisabled: true,
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ {
|
||||
+ id: 'trackingStatus',
|
||||
+ uiType: 'ohif.trackingStatus',
|
||||
+ props: {
|
||||
+ // ... props
|
||||
+ evaluate: {
|
||||
+ name: 'evaluate.trackingStatus',
|
||||
+ hideWhenDisabled: true,
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
```
|
||||
|
||||
|
||||
5. **Direct Import of `OHIFCornerstoneViewport`**:
|
||||
* Extensions that were previously getting the cornerstone viewport component dynamically via `extensionManager.getModuleEntry('@ohif/extension-cornerstone.viewportModule.cornerstone')` should now import `OHIFCornerstoneViewport` directly from `@ohif/extension-cornerstone`.
|
||||
|
||||
```diff
|
||||
// extensions/cornerstone-dicom-pmap/src/viewports/OHIFCornerstonePMAPViewport.tsx
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useViewportGrid } from '@ohif/ui-next';
|
||||
+import { OHIFCornerstoneViewport } from '@ohif/extension-cornerstone';
|
||||
|
||||
function OHIFCornerstonePMAPViewport(props: withAppTypes) {
|
||||
// ...
|
||||
const getCornerstoneViewport = useCallback(() => {
|
||||
- const { component: Component } = extensionManager.getModuleEntry(
|
||||
- '@ohif/extension-cornerstone.viewportModule.cornerstone'
|
||||
- );
|
||||
// ...
|
||||
return (
|
||||
- <Component
|
||||
+ <OHIFCornerstoneViewport
|
||||
{...props}
|
||||
// ...
|
||||
- ></Component>
|
||||
+ />
|
||||
);
|
||||
// ...
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
sidebar_label: 3.11 -> 3.12 beta
|
||||
---
|
||||
|
||||
# Migration Guide
|
||||
|
||||
This guide provides information about migrating from OHIF version 3.11 to version 3.12 beta
|
||||
@@ -0,0 +1,328 @@
|
||||
---
|
||||
id: 0-general
|
||||
title: General
|
||||
summary: General migration changes from OHIF 3.8 to 3.9, including removing SharedArrayBuffer requirements, React 18 updates, Polyfill removal, webpack changes, scroll utility relocation, Crosshairs improvements, and toolbar button evaluation enhancements.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# No SharedArrayBuffer anymore!
|
||||
|
||||
We have streamlined the process of loading volumes without sacrificing speed by eliminating the need for shared array buffers. This change resolves issues across various frameworks, where previously, specific security headers were required. Now, you can remove any previously set headers, which lowers the barrier for adopting Cornerstone 3D in frameworks that didn't support those headers. Shared array buffers are no longer necessary, and all related headers can be removed.
|
||||
|
||||
You can remove `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` from your custom headers if you don't need them in other
|
||||
aspects of your app.
|
||||
|
||||
# React 18 Migration Guide
|
||||
As we upgrade to React 18, we're making some exciting changes to improve performance and developer experience. This guide will help you navigate the key updates and ensure your custom extensions and modes are compatible with the new version.
|
||||
What's Changing?
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Before" label="Before" default>
|
||||
|
||||
```md
|
||||
- React 17
|
||||
- Using `defaultProps`
|
||||
- `babel-inline-svg` for SVG imports
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="After" label="After">
|
||||
|
||||
```md
|
||||
- React 18
|
||||
- Default parameters for props
|
||||
- `svgr` for SVG imports
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
## Update React version:
|
||||
In your custom extensions and modes, change the version of react and react-dom to ^18.3.1.
|
||||
|
||||
## Replace defaultProps with default parameters:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Before" label="Before" default>
|
||||
|
||||
```jsx
|
||||
const MyComponent = ({ prop1, prop2 }) => {
|
||||
return <div>{prop1} {prop2}</div>
|
||||
}
|
||||
|
||||
MyComponent.defaultProps = {
|
||||
prop1: 'default value',
|
||||
prop2: 'default value'
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="After" label="After">
|
||||
|
||||
```jsx
|
||||
const MyComponent = ({ prop1 = 'default value', prop2 = 'default value' }) => {
|
||||
return <div>{prop1} {prop2}</div>
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Update SVG imports:
|
||||
|
||||
You might need to update your SVG imports to use the `ReactComponent` syntax, if you want to use the old Icon component. However, we have made a significant change to how we handle Icons, read the UI Migration Guide for more information.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Before" label="Before" default>
|
||||
|
||||
```javascript
|
||||
import arrowDown from './../../assets/icons/arrow-down.svg';
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="After" label="After">
|
||||
|
||||
```javascript
|
||||
import { ReactComponent as arrowDown } from './../../assets/icons/arrow-down.svg';
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## Polyfill.io
|
||||
|
||||
We have removed the Polyfill.io script from the Viewer. If you require polyfills, you can add them to your project manually. This change primarily affects Internet Explorer, which Microsoft has already [ended support for](https://learn.microsoft.com/en-us/lifecycle/faq/internet-explorer-microsoft-edge#is-internet-explorer-11-the-last-version-of-internet-explorer-).
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Webpack changes
|
||||
|
||||
We previously were copying dicom-image-loader wasm files to the public folder via
|
||||
|
||||
```js
|
||||
// platform/app/.webpack/webpack.pwa.js
|
||||
{
|
||||
from: '../../../node_modules/@cornerstonejs/dicom-image-loader/dist/dynamic-import',
|
||||
to: DIST_DIR,
|
||||
},
|
||||
```
|
||||
|
||||
but now after our upgrade to Cornerstone 3D 2.0, we don't need to do this anymore.
|
||||
|
||||
|
||||
---
|
||||
## Scroll utility
|
||||
|
||||
|
||||
The `jumpToSlice` utility has been relocated from `@cornerstonejs/tools` utilities to `@cornerstonejs/core/utilities`.
|
||||
|
||||
migration
|
||||
|
||||
```js
|
||||
import { jumpToSlice } from '@cornerstonejs/core/utilities';
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Crosshairs
|
||||
|
||||
They now have new colors in their associated viewports in the MPR view. However, you can turn this feature off.
|
||||
|
||||
To disable it, remove the configuration from the `initToolGroups` in your mode.
|
||||
|
||||
```
|
||||
{
|
||||
configuration: {
|
||||
viewportIndicators: true,
|
||||
viewportIndicatorsConfig: {
|
||||
circleRadius: 5,
|
||||
xOffset: 0.95,
|
||||
yOffset: 0.05,
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
## useAuthorizationCodeFlow
|
||||
|
||||
`useAuthorizationCodeFlow` config is deprecated
|
||||
|
||||
now internally we detect the authorizationCodeFlow if the response_type is equal to `code`
|
||||
|
||||
you can remove the config from the appConfig
|
||||
|
||||
---
|
||||
|
||||
## StackScrollMouseWheel -> StackScroll Tool + Mouse bindings
|
||||
|
||||
If you previously used:
|
||||
|
||||
```js
|
||||
{ toolName: toolNames.StackScrollMouseWheel, bindings: [] }
|
||||
```
|
||||
|
||||
in your `initToolGroups`, you should now use:
|
||||
|
||||
```js
|
||||
{
|
||||
toolName: toolNames.StackScroll,
|
||||
bindings: [{ mouseButton: Enums.MouseBindings.Wheel }],
|
||||
}
|
||||
```
|
||||
|
||||
This change allows for more flexible mouse bindings and keyboard combinations.
|
||||
|
||||
## VolumeRotateMouseWheel -> VolumeRotate Tool + Mouse bindings
|
||||
|
||||
Before:
|
||||
|
||||
```js
|
||||
{
|
||||
toolName: toolNames.VolumeRotateMouseWheel,
|
||||
configuration: {
|
||||
rotateIncrementDegrees: 5,
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
Now:
|
||||
|
||||
```js
|
||||
{
|
||||
toolName: toolNames.VolumeRotate,
|
||||
bindings: [{ mouseButton: Enums.MouseBindings.Wheel }],
|
||||
configuration: {
|
||||
rotateIncrementDegrees: 5,
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CustomizationService
|
||||
|
||||
The `CustomizationService` uses `contentF` instead of `content`.
|
||||
|
||||
So make sure your customizations are updated accordingly.
|
||||
|
||||
---
|
||||
|
||||
## SidePanel auto switch if open
|
||||
|
||||
In `basic viewer` mode, when the side panel is open and the segmentation panel is active, adding a measurement will automatically switch to the measurement panel. This switch won't happen if the side panel is closed. To enable or disable this feature, adjust your mode configuration accordingly.
|
||||
|
||||
To prevent this behavior, remove the following code from your mode:
|
||||
|
||||
```js
|
||||
panelService.addActivatePanelTriggers('your.panel.id', [
|
||||
{
|
||||
sourcePubSubService: segmentationService,
|
||||
sourceEvents: [segmentationService.EVENTS.SEGMENTATION_ADDED],
|
||||
},
|
||||
])
|
||||
|
||||
panelService.addActivatePanelTriggers('your.panel.id', [
|
||||
{
|
||||
sourcePubSubService: measurementService,
|
||||
sourceEvents: [
|
||||
measurementService.EVENTS.MEASUREMENT_ADDED,
|
||||
measurementService.EVENTS.RAW_MEASUREMENT_ADDED,
|
||||
],
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DicomUpload
|
||||
|
||||
The DICOM upload functionality in OHIF has been refactored to use the standard customization service pattern. Now you don't need to put
|
||||
|
||||
`customizationService: { dicomUploadComponent: '@ohif/extension-cornerstone.customizationModule.cornerstoneDicomUploadComponent', },`
|
||||
|
||||
in your config, we will automatically add that if you have `dicomUploadEnabled`
|
||||
|
||||
---
|
||||
|
||||
## Viewport and Modality Support for Toolbar Buttons
|
||||
|
||||
Previously, toolbar buttons had limited support for disabling themselves based on the active viewport type (e.g., `volume3d`, `video`, `sr`) or the modality of the displayed data (e.g., `US`, `SM`). This led to inconsistencies and sometimes enabled tools in contexts where they weren't applicable.
|
||||
|
||||
The new implementation introduces more robust and flexible evaluators to control the enabled/disabled state of toolbar buttons based on viewport types and modalities.
|
||||
|
||||
**Key Changes**
|
||||
|
||||
1. **New Evaluators:** New evaluators have been added to the `getToolbarModule`:
|
||||
- `evaluate.viewport.supported`: Disables a button if the active viewport's type is listed in the `unsupportedViewportTypes` property.
|
||||
- `evaluate.modality.supported`: Disables a button based on the modalities of the displayed data. It checks for both `unsupportedModalities` (exclusion) and `supportedModalities` (inclusion).
|
||||
2. **Removal of Legacy Evaluators:**
|
||||
- Evaluators such as `evaluate.not.sm`, `evaluate.action.not.video`, `evaluate.not3D`, and `evaluate.isUS` have been removed. Migrate your toolbar button definitions to use the new evaluators mentioned above.
|
||||
|
||||
|
||||
**Replace Legacy Evaluators:**
|
||||
- Replace `evaluate.not.sm` with:
|
||||
|
||||
```json
|
||||
{
|
||||
name: 'evaluate.viewport.supported',
|
||||
unsupportedViewportTypes: ['sm'],
|
||||
}
|
||||
```
|
||||
|
||||
- Replace `evaluate.action.not.video` with:
|
||||
|
||||
```json
|
||||
{
|
||||
name: 'evaluate.viewport.supported',
|
||||
unsupportedViewportTypes: ['video'],
|
||||
}
|
||||
```
|
||||
|
||||
- Replace `evaluate.not3D` with:
|
||||
|
||||
```json
|
||||
{
|
||||
name: 'evaluate.viewport.supported',
|
||||
unsupportedViewportTypes: ['volume3d'],
|
||||
}
|
||||
```
|
||||
|
||||
- Replace `evaluate.isUS` with:
|
||||
|
||||
```json
|
||||
{
|
||||
name: 'evaluate.modality.supported',
|
||||
supportedModalities: ['US'],
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Example Migration</summary>
|
||||
|
||||
Before:
|
||||
|
||||
```json
|
||||
evaluate: ['evaluate.cine', 'evaluate.not3D'],
|
||||
```
|
||||
|
||||
After
|
||||
|
||||
```json
|
||||
evaluate: [
|
||||
'evaluate.cine',
|
||||
{
|
||||
name: 'evaluate.viewport.supported',
|
||||
unsupportedViewportTypes: ['volume3d'],
|
||||
},
|
||||
],
|
||||
```
|
||||
</details>
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
---
|
||||
id: seg-new-arch
|
||||
title: New Architecture
|
||||
summary: Overview of the new viewport-centric segmentation architecture in OHIF 3.9, which replaces the previous toolGroup-centric approach and introduces clearer separation between segmentation data and its visual representation.
|
||||
---
|
||||
|
||||
|
||||
## New Architecture
|
||||
|
||||
* **Viewport-Centric Architecture**
|
||||
* Previous: Segmentations were tied to toolGroups
|
||||
* Now: Segmentations are tied directly to viewports
|
||||
* Impact: More granular control but requires significant code changes
|
||||
|
||||
* **Representation Management**
|
||||
* Previous: Required managing segmentation representation UIDs
|
||||
* Now: Uses simpler segmentationId + type combination
|
||||
* Impact: Simplified but requires API updates
|
||||
|
||||
|
||||
|
||||
If you are not familiar with the difference between a segmentation and a segmentation representation, below
|
||||
|
||||
<details>
|
||||
<summary>Read More</summary>
|
||||
|
||||
In Cornerstone3DTools, we have decoupled the concept of a Segmentation from a Segmentation Representation. This means that from one Segmentation we can create multiple Segmentation Representations. For instance, a Segmentation Representation of a 3D Labelmap, can be created from a Segmentation data, and a Segmentation Representation of a Contour can be created from the same Segmentation data. This way we have decouple the presentational aspect of a Segmentation from the underlying data.
|
||||
|
||||
|
||||
Similar relationship structure has been adapted in popular medical imaging softwares such as 3D Slicer with the addition of polymorph segmentation.
|
||||
|
||||
- https://github.com/PerkLab/PolySeg
|
||||
- https://www.slicer.org/
|
||||
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
The new architecture in Cornerstone3D 2.0 makes a clear distinction between:
|
||||
|
||||
* A segmentation (the data structure containing segments)
|
||||
* A segmentation representation (how that segmentation is visualized in a specific viewport)
|
||||
|
||||
Let's now review what has changed
|
||||
+434
@@ -0,0 +1,434 @@
|
||||
---
|
||||
id: seg-api
|
||||
title: SegmentationService API
|
||||
summary: Detailed guide to the SegmentationService API changes in OHIF 3.9, covering the transition from toolGroup to viewport-centric segmentation management, updates to key methods like getActiveSegmentation, setActiveSegmentation, and addSegment.
|
||||
---
|
||||
|
||||
|
||||
|
||||
Below we will review the changes to the API of the `SegmentationService`
|
||||
|
||||
# SegmentationService API
|
||||
|
||||
## Events
|
||||
|
||||
SEGMENTATION_UPDATED -> SEGMENTATION_MODIFIED
|
||||
|
||||
|
||||
Just a rename to match the cornerstone terminology
|
||||
|
||||
## VolumeId vs SegmentationId
|
||||
|
||||
Previously, we used the SegmentationId as the VolumeId for volume-based segmentations, which led to confusion and issues.
|
||||
|
||||
Now, we have two separate IDs: one for the segmentation and one for the volume.
|
||||
|
||||
`segmentationService.getLabelmapVolume(segmentationId)` will return the volume associated with the segmentation.
|
||||
|
||||
If your code uses `cache.getVolume(segmentationId)`, update it to use the new `getLabelmapVolume` method.
|
||||
|
||||
|
||||
## getSegmentation(segmentationId)
|
||||
|
||||
remains the same it will return the segmentation object = cornerstone segmentation object with the following properties:
|
||||
|
||||
```js
|
||||
/**
|
||||
* Global Segmentation Data which is used for the segmentation
|
||||
*/
|
||||
type Segmentation = {
|
||||
/** segmentation id */
|
||||
segmentationId: string;
|
||||
/** segmentation label */
|
||||
label: string;
|
||||
segments: {
|
||||
[segmentIndex: number]: Segment;
|
||||
};
|
||||
/**
|
||||
* Representations of the segmentation. Each segmentation "can" be viewed
|
||||
* in various representations. For instance, if a DICOM SEG is loaded, the main
|
||||
* representation is the labelmap. However, for DICOM RT the main representation
|
||||
* is contours, and other representations can be derived from the contour (currently
|
||||
* only labelmap representation is supported)
|
||||
*/
|
||||
representationData: RepresentationsData;
|
||||
/**
|
||||
* Segmentation level stats, Note each segment can have its own stats
|
||||
* This is used for caching stats for the segmentation level
|
||||
*/
|
||||
cachedStats: { [key: string]: unknown };
|
||||
};
|
||||
|
||||
export type Segment = {
|
||||
/** segment index */
|
||||
segmentIndex: number;
|
||||
/** segment label */
|
||||
label: string;
|
||||
/** is segment locked for editing */
|
||||
locked: boolean;
|
||||
/** cached stats for the segment, e.g., pt suv mean, max etc. */
|
||||
cachedStats: { [key: string]: unknown };
|
||||
/** is segment active for editing, at the same time only one segment can be active for editing */
|
||||
active: boolean;
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Compared to Cornerstone3D 1.x</summary>
|
||||
|
||||
Previously this function was returning this
|
||||
|
||||
```js
|
||||
export type Segmentation = {
|
||||
segmentationId: string;
|
||||
type: Enums.SegmentationRepresentations;
|
||||
label: string;
|
||||
activeSegmentIndex: number;
|
||||
segmentsLocked: Set<number>;
|
||||
cachedStats: { [key: string]: number };
|
||||
segmentLabels: { [key: string]: string };
|
||||
representationData: SegmentationRepresentationData;
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
As you can see `segmentLabels`, `segmentsLocked`, `activeSegmentIndex`, are all gathered under the new `segments` object. We now have support for per segment cachedStats as well.
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## getSegmentations
|
||||
|
||||
It provides all segmentations in the state. Previously, it accepted a `filterNonhydrated` flag, but since we've moved away from hydration and every loaded segmentation is now hydrated by default, it returns all segmentations.
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## getActiveSegmentation
|
||||
|
||||
|
||||
After migrating to viewport-specific segmentations, different viewports can have distinct active segmentations for editing. The panel will always display the active segmentation when the active viewport changes.
|
||||
|
||||
Before (3.8)
|
||||
|
||||
```js
|
||||
// Returns full segmentation object
|
||||
public getActiveSegmentation(): Segmentation {
|
||||
const segmentations = this.getSegmentations();
|
||||
return segmentations.find(segmentation => segmentation.isActive);
|
||||
}
|
||||
```
|
||||
|
||||
After (3.9)
|
||||
|
||||
```js
|
||||
public getActiveSegmentation(viewportId: string): Segmentation | null {
|
||||
return cstSegmentation.activeSegmentation.getActiveSegmentation(viewportId);
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Key Changes</summary>
|
||||
|
||||
1. **Viewport Specificity**
|
||||
- Before: Global active segmentation across all tool groups
|
||||
- After: Active segmentation per viewport
|
||||
2. **Required Parameters**
|
||||
- Before: No parameters needed
|
||||
- After: Requires viewportId parameter
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Migration Examples</summary>
|
||||
|
||||
**Before:**
|
||||
|
||||
```js
|
||||
// Get active segmentation
|
||||
const activeSegmentation = segmentationService.getActiveSegmentation();
|
||||
if (activeSegmentation) {
|
||||
console.log('Active segmentation:', activeSegmentation.segmentationId);
|
||||
console.log('Active segment:', activeSegmentation.activeSegmentIndex);
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
|
||||
```js
|
||||
// Get active segmentation for specific viewport
|
||||
const activeSegmentation = segmentationService.getActiveSegmentation('viewport1');
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## getToolGroupIdsWithSegmentation
|
||||
|
||||
is now -> `getViewportIdsWithSegmentation` as you guessed
|
||||
|
||||
|
||||
|
||||
## setActiveSegmentationForToolGroup
|
||||
|
||||
-> setActiveSegmentation
|
||||
|
||||
|
||||
|
||||
**Before (OHIF 3.8)**
|
||||
|
||||
```js
|
||||
setActiveSegmentationForToolGroup(
|
||||
segmentationId: string,
|
||||
toolGroupId?: string,
|
||||
suppressEvents?: boolean
|
||||
): void
|
||||
```
|
||||
|
||||
**After (OHIF 3.9)**
|
||||
|
||||
```js
|
||||
setActiveSegmentation(
|
||||
viewportId: string,
|
||||
segmentationId: string
|
||||
): void
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Migration Examples</summary>
|
||||
|
||||
1. **Basic Usage Update**
|
||||
|
||||
```js
|
||||
// Before - OHIF 3.8
|
||||
segmentationService.setActiveSegmentationForToolGroup(
|
||||
segmentationId,
|
||||
toolGroupId
|
||||
);
|
||||
// After - OHIF 3.9
|
||||
segmentationService.setActiveSegmentation(
|
||||
viewportId,
|
||||
segmentationId
|
||||
);
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## addSegment
|
||||
|
||||
The `addSegment` method in OHIF 3.9 has been updated to handle segmentation properties in a viewport-centric way, removing tool group dependencies and simplifying the configuration structure.
|
||||
|
||||
|
||||
**Before (OHIF 3.8)**
|
||||
|
||||
```js
|
||||
addSegment(
|
||||
segmentationId: string,
|
||||
config: {
|
||||
segmentIndex?: number;
|
||||
toolGroupId?: string;
|
||||
properties?: {
|
||||
label?: string;
|
||||
color?: ohifTypes.RGB;
|
||||
opacity?: number;
|
||||
visibility?: boolean;
|
||||
isLocked?: boolean;
|
||||
active?: boolean;
|
||||
};
|
||||
}
|
||||
): void
|
||||
```
|
||||
|
||||
**After (OHIF 3.9)**
|
||||
|
||||
```js
|
||||
addSegment(
|
||||
segmentationId: string,
|
||||
config: {
|
||||
segmentIndex?: number;
|
||||
label?: string;
|
||||
isLocked?: boolean;
|
||||
active?: boolean;
|
||||
color?: csTypes.Color;
|
||||
visibility?: boolean;
|
||||
}
|
||||
): void
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Key Changes</summary>
|
||||
|
||||
1. **Configuration Structure**
|
||||
- Removed double nested `properties` object
|
||||
- Configuration options now at top level
|
||||
- Removed `toolGroupId` parameter
|
||||
- Removed `opacity` parameter (now part of color)
|
||||
2. **Segment Index Generation**
|
||||
- Changed from length-based to max-value-based indexing
|
||||
- More reliable for non-sequential segment indices
|
||||
3. **Color Handling**
|
||||
- Color now includes alpha channel (opacity)
|
||||
- Applied to all relevant viewports automatically
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Migration Examples</summary>
|
||||
|
||||
1. **Basic Segment Creation**
|
||||
|
||||
```js
|
||||
// Before - OHIF 3.8
|
||||
segmentationService.addSegment(segmentationId, {
|
||||
properties: {
|
||||
label: 'Segment 1'
|
||||
}
|
||||
});
|
||||
// After - OHIF 3.9
|
||||
segmentationService.addSegment(segmentationId, {
|
||||
label: 'Segment 1'
|
||||
});
|
||||
```
|
||||
|
||||
2. **Creating Segment with Color**
|
||||
|
||||
```js
|
||||
// Before - OHIF 3.8
|
||||
segmentationService.addSegment(segmentationId, {
|
||||
properties: {
|
||||
color: [255, 0, 0],
|
||||
opacity: 255
|
||||
}
|
||||
});
|
||||
// After - OHIF 3.9
|
||||
segmentationService.addSegment(segmentationId, {
|
||||
color: [255, 0, 0, 255] // RGB + Alpha
|
||||
});
|
||||
```
|
||||
|
||||
3. **Setting Visibility and Lock Status**
|
||||
|
||||
```js
|
||||
// Before - OHIF 3.8
|
||||
segmentationService.addSegment(segmentationId, {
|
||||
toolGroupId: 'myToolGroup',
|
||||
properties: {
|
||||
visibility: true,
|
||||
isLocked: true
|
||||
}
|
||||
});
|
||||
// After - OHIF 3.9
|
||||
segmentationService.addSegment(segmentationId, {
|
||||
visibility: true,
|
||||
isLocked: true
|
||||
});
|
||||
```
|
||||
|
||||
4. **Complete Configuration Example**
|
||||
|
||||
```js
|
||||
// Before - OHIF 3.8
|
||||
segmentationService.addSegment(segmentationId, {
|
||||
segmentIndex: 1,
|
||||
toolGroupId: 'myToolGroup',
|
||||
properties: {
|
||||
label: 'Tumor',
|
||||
color: [255, 0, 0],
|
||||
opacity: 200,
|
||||
visibility: true,
|
||||
isLocked: false,
|
||||
active: true
|
||||
}
|
||||
});
|
||||
// After - OHIF 3.9
|
||||
segmentationService.addSegment(segmentationId, {
|
||||
segmentIndex: 1,
|
||||
label: 'Tumor',
|
||||
color: [255, 0, 0, 200], // RGB + Alpha
|
||||
visibility: true,
|
||||
isLocked: false,
|
||||
active: true
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Important Changes</summary>
|
||||
|
||||
1. **Tool Group Removal**
|
||||
```js
|
||||
// Before - OHIF 3.8
|
||||
segmentationService.addSegment(segmentationId, {
|
||||
toolGroupId: 'myToolGroup'
|
||||
// ... other properties
|
||||
});
|
||||
// After - OHIF 3.9
|
||||
// No tool group needed - automatically applies to all relevant viewports
|
||||
segmentationService.addSegment(segmentationId, {
|
||||
// ... properties
|
||||
});
|
||||
```
|
||||
|
||||
2. **Segment Index Generation**
|
||||
```js
|
||||
// Before - OHIF 3.8
|
||||
// Used array length
|
||||
segmentIndex = segmentation.segments.length === 0 ? 1 : segmentation.segments.length;
|
||||
// After - OHIF 3.9
|
||||
// Uses highest existing index + 1
|
||||
segmentIndex = Math.max(...Object.keys(csSegmentation.segments).map(Number)) + 1;
|
||||
```
|
||||
|
||||
3. **Color and Opacity**
|
||||
```js
|
||||
// Before - OHIF 3.8
|
||||
segmentationService.addSegment(segmentationId, {
|
||||
properties: {
|
||||
color: [255, 0, 0],
|
||||
opacity: 200
|
||||
}
|
||||
});
|
||||
|
||||
// After - OHIF 3.9
|
||||
segmentationService.addSegment(segmentationId, {
|
||||
color: [255, 0, 0, 200] // Combined color and opacity
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## getActiveSegment
|
||||
|
||||
now requires viewportId, since we have moved away from global active segmentation to viewport specific one
|
||||
|
||||
**API Changes**
|
||||
|
||||
```js
|
||||
// Before
|
||||
getActiveSegment(): Segment
|
||||
|
||||
// After
|
||||
getActiveSegment(viewportId: string): Segment | null
|
||||
```
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
---
|
||||
id: seg-representation
|
||||
title: Segmentation Representations
|
||||
summary: Migration guide for segmentation representation management in OHIF 3.9, explaining the transition from toolGroup-based to viewport-centric representation handling, and introducing the new specifier pattern for more flexible API usage.
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
## Segmentation Representation Management API
|
||||
|
||||
```js
|
||||
addSegmentationRepresentationToToolGroup
|
||||
removeSegmentationRepresentationFromToolGroup
|
||||
getSegmentationRepresentationsForToolGroup
|
||||
```
|
||||
|
||||
In Cornerstone3D 2.0, segmentation representation management has shifted from a tool group-centric approach to a viewport-centric approach. This architectural change provides better control over segmentation rendering and simplifies the mental model for managing segmentations.
|
||||
|
||||
|
||||
### Adding Segmentation Representations
|
||||
|
||||
**Before (3.8)**:
|
||||
|
||||
```js
|
||||
// Tool group-based approach
|
||||
await segmentation.addSegmentationRepresentationToToolGroup(
|
||||
toolGroupId,
|
||||
segmentationId,
|
||||
hydrateSegmentation,
|
||||
csToolsEnums.SegmentationRepresentations.Labelmap
|
||||
);
|
||||
```
|
||||
|
||||
**After (3.9)**:
|
||||
|
||||
```js
|
||||
// Viewport-centric approach
|
||||
await segmentation.addSegmentationRepresentation(
|
||||
viewportId,
|
||||
{
|
||||
segmentationId: segmentationId,
|
||||
type: csToolsEnums.SegmentationRepresentations.Labelmap,
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Removing Segmentation Representations
|
||||
|
||||
**Before** :
|
||||
|
||||
```js
|
||||
// Remove specific representations from a tool group
|
||||
segmentation.removeSegmentationRepresentationFromToolGroup(
|
||||
toolGroupId,
|
||||
[segmentationRepresentationUID]
|
||||
);
|
||||
// Remove all representations from a tool group
|
||||
segmentation.removeSegmentationRepresentationFromToolGroup(toolGroupId);
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```js
|
||||
// Remove specific representation from a viewport
|
||||
segmentation.removeSegmentationRepresentation(
|
||||
viewportId,
|
||||
{
|
||||
segmentationId: segmentationId,
|
||||
type: csToolsEnums.SegmentationRepresentations.Labelmap
|
||||
}
|
||||
);
|
||||
// Remove all representations from a viewport
|
||||
segmentation.removeSegmentationRepresentations(viewportId);
|
||||
```
|
||||
|
||||
### Getting Segmentation Representations
|
||||
|
||||
**Before**:
|
||||
|
||||
```js
|
||||
// Get representations for a tool group
|
||||
const representations = segmentation.getSegmentationRepresentationsForToolGroup(toolGroupId);
|
||||
```
|
||||
|
||||
**After** :
|
||||
|
||||
```js
|
||||
// Get all representations for a viewport
|
||||
const representations = segmentation.getSegmentationRepresentations(viewportId);
|
||||
|
||||
// Get specific type of representations
|
||||
const labelmapReps = segmentation.getSegmentationRepresentations(viewportId, {
|
||||
type: csToolsEnums.SegmentationRepresentations.Labelmap
|
||||
});
|
||||
|
||||
// Get representations for specific segmentation
|
||||
const segmentationReps = segmentation.getSegmentationRepresentations(viewportId, {
|
||||
segmentationId: segmentationId
|
||||
});
|
||||
|
||||
// Get specific representation
|
||||
const representation = segmentation.getSegmentationRepresentation(viewportId, {
|
||||
segmentationId: segmentationId,
|
||||
type: csToolsEnums.SegmentationRepresentations.Labelmap
|
||||
});
|
||||
```
|
||||
|
||||
### Understanding the Specifier Pattern
|
||||
|
||||
The Cornerstone3D 2.0 (OHIF 3.9) API introduces a "specifier" pattern that provides more flexible and precise control over segmentation representations. A specifier is an object that can include:
|
||||
|
||||
```js
|
||||
type Specifier = {
|
||||
segmentationId?: string; // The ID of the segmentation
|
||||
type?: SegmentationRepresentations; // The type of representation (Labelmap, Contour, etc.)
|
||||
}
|
||||
```
|
||||
|
||||
The specifier pattern allows for:
|
||||
|
||||
1. **Precise Targeting**: You can target specific segmentations and representation types
|
||||
- Allows direct access to individual segmentations
|
||||
- Enables filtering by representation type
|
||||
|
||||
2. **Flexible Querying**: You can get all representations of a certain type or for a specific segmentation
|
||||
- Query by segmentation ID
|
||||
- Query by representation type
|
||||
- Combine queries for specific needs
|
||||
|
||||
3. **Granular Control**: You can manage representations at different levels of specificity
|
||||
- Viewport level control
|
||||
- Segmentation level control
|
||||
- Individual representation type control
|
||||
|
||||
### Examples of Specifier Usage
|
||||
|
||||
```js
|
||||
// Get all labelmap representations in a viewport
|
||||
const labelmaps = segmentation.getSegmentationRepresentations(viewportId, {
|
||||
type: csToolsEnums.SegmentationRepresentations.Labelmap
|
||||
});
|
||||
|
||||
// Get all representations of a specific segmentation (including contour, labelmap, surface)
|
||||
const segReps = segmentation.getSegmentationRepresentations(viewportId, {
|
||||
segmentationId: 'seg123'
|
||||
});
|
||||
|
||||
// Get a specific representation
|
||||
const specificRep = segmentation.getSegmentationRepresentation(viewportId, {
|
||||
segmentationId: 'seg123',
|
||||
type: csToolsEnums.SegmentationRepresentations.Labelmap
|
||||
});
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Benefits of the New Approach</summary>
|
||||
|
||||
1. **Direct Viewport Control**:
|
||||
- Each viewport can have its own unique representation configuration
|
||||
- No need to create separate tool groups for different viewport representations
|
||||
2. **Simpler Mental Model**:
|
||||
- Representations are directly tied to where they're displayed
|
||||
- No intermediate tool group layer to manage
|
||||
3. **More Flexible Rendering**:
|
||||
- Each viewport can render the same segmentation differently
|
||||
- Better support for multiple views of the same data
|
||||
4. **Improved Type Safety**:
|
||||
- Specifier pattern provides better TypeScript support
|
||||
- More explicit API with clearer intentions
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Migration Tips</summary>
|
||||
|
||||
1. **Replace Tool Group References**:
|
||||
- Search your codebase for `toolGroupId` references in segmentation code
|
||||
- Replace with appropriate `viewportId` references
|
||||
2. **Update Event Handlers**:
|
||||
- Update any code listening for segmentation events
|
||||
- Events now include viewportId instead of toolGroupId
|
||||
3. **Review Representation Management**:
|
||||
- Identify where you manage segmentation representations
|
||||
- Convert to using the new viewport-centric methods
|
||||
4. **Consider Viewport Context**:
|
||||
- Think about segmentation representation in terms of viewport display
|
||||
- Use specifiers to target specific representations when needed
|
||||
|
||||
</details>
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
---
|
||||
id: seg-creation
|
||||
title: Segmentation Creation
|
||||
summary: Migration guide for segmentation creation methods in OHIF 3.9, covering renamed methods like createLabelmapForViewport and createLabelmapForDisplaySet, updated parameter structures, and transition from tool group to viewport-centric approach.
|
||||
---
|
||||
|
||||
## createEmptySegmentationForViewport
|
||||
|
||||
is now `createLabelmapForViewport` to align with other segmentation creation methods.
|
||||
|
||||
Run it using `commandsManager.runCommand('createLabelmapForViewport', {viewportId})`.
|
||||
|
||||
## createSegmentationForDisplaySet
|
||||
|
||||
is now -> `createLabelmapForDisplaySet`
|
||||
|
||||
Since we are moving towards segmentations be contours as well, this is renamed to clearly state the purpose.
|
||||
Since OHIF 3.9 introduced Stack Segmentation support, we no longer generate a volume-based labelmap or convert the viewport to a volume viewport by default. Our default creation is now stack-based.
|
||||
|
||||
API Changes
|
||||
- `createSegmentationForDisplaySet` has been renamed to `createLabelmapForDisplaySet`.
|
||||
- Pass a `displaySet` object instead of a `displaySetInstanceUID`. This change enhances type safety and flexibility, accommodating future updates to the `displaySetService`.
|
||||
|
||||
**Before (OHIF 3.8)**
|
||||
|
||||
```js
|
||||
async createSegmentationForDisplaySet(
|
||||
displaySetInstanceUID: string,
|
||||
options?: {
|
||||
segmentationId: string;
|
||||
FrameOfReferenceUID: string;
|
||||
label: string;
|
||||
}
|
||||
): Promise<string>
|
||||
```
|
||||
|
||||
**After (OHIF 3.9)**
|
||||
|
||||
```js
|
||||
// Method 1: Display Set Based
|
||||
async createLabelmapForDisplaySet(
|
||||
displaySet: DisplaySet,
|
||||
options?: {
|
||||
segmentationId?: string;
|
||||
label: string;
|
||||
segments?: {
|
||||
[segmentIndex: number]: Partial<Segment>
|
||||
};
|
||||
}
|
||||
): Promise<string>
|
||||
```
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Migration Examples</summary>
|
||||
|
||||
|
||||
```js
|
||||
// Before - OHIF 3.8
|
||||
const segmentationId = await segmentationService.createSegmentationForDisplaySet(
|
||||
displaySetInstanceUID,
|
||||
{
|
||||
label: 'My Segmentation'
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
```js
|
||||
// After - OHIF 3.9
|
||||
// Option 1: If you have a display set UID
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
|
||||
const segmentationId = await segmentationService.createLabelmapForDisplaySet(
|
||||
displaySet,
|
||||
{
|
||||
label: 'My Segmentation'
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## createSegmentationForRTDisplaySet
|
||||
|
||||
|
||||
**Before (OHIF 3.8)**
|
||||
|
||||
```js
|
||||
async createSegmentationForRTDisplaySet(
|
||||
rtDisplaySet,
|
||||
segmentationId?: string,
|
||||
suppressEvents = false
|
||||
): Promise<string>
|
||||
```
|
||||
|
||||
**After (OHIF 3.9)**
|
||||
|
||||
```js
|
||||
async createSegmentationForRTDisplaySet(
|
||||
rtDisplaySet,
|
||||
options: {
|
||||
segmentationId?: string;
|
||||
type: SegmentationRepresentations; // not required, defaults to Contour
|
||||
}
|
||||
): Promise<string>
|
||||
```
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Migration Examples</summary>
|
||||
|
||||
if you were not passing segmentationId, you don't need to change anything
|
||||
|
||||
|
||||
```js
|
||||
// Before - OHIF 3.8
|
||||
const segmentationId = await segmentationService.createSegmentationForRTDisplaySet(
|
||||
rtDisplaySet
|
||||
);
|
||||
|
||||
// After - OHIF 3.9
|
||||
const segmentationId = await segmentationService.createSegmentationForRTDisplaySet(
|
||||
rtDisplaySet,
|
||||
);
|
||||
```
|
||||
|
||||
if you were passing segmentationId, you need to update the API to pass an options object and set the segmentationId in there.
|
||||
|
||||
```js
|
||||
// Before - OHIF 3.8
|
||||
const segmentationId = await segmentationService.createSegmentationForRTDisplaySet(
|
||||
rtDisplaySet,
|
||||
'custom-id',
|
||||
);
|
||||
// After - OHIF 3.9
|
||||
const segmentationId = await segmentationService.createSegmentationForRTDisplaySet(
|
||||
rtDisplaySet,
|
||||
{
|
||||
segmentationId: 'custom-id',
|
||||
type: csToolsEnums.SegmentationRepresentations.Contour
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## createSegmentationForSEGDisplaySet Changes
|
||||
|
||||
**Before (OHIF 3.8)**
|
||||
|
||||
```js
|
||||
async createSegmentationForSEGDisplaySet(
|
||||
segDisplaySet,
|
||||
segmentationId?: string,
|
||||
suppressEvents = false
|
||||
): Promise<string>
|
||||
```
|
||||
|
||||
**After (OHIF 3.9)**
|
||||
|
||||
```js
|
||||
async createSegmentationForSEGDisplaySet(
|
||||
segDisplaySet,
|
||||
options: {
|
||||
segmentationId?: string;
|
||||
type: SegmentationRepresentations; // not required, defaults to Labelmap
|
||||
}
|
||||
): Promise<string>
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Migration Examples</summary>
|
||||
|
||||
1. **Basic Usage Update**
|
||||
|
||||
```
|
||||
// Before - OHIF 3.8
|
||||
const segmentationId = await segmentationService.createSegmentationForSEGDisplaySet(
|
||||
segDisplaySet
|
||||
);
|
||||
// After - OHIF 3.9
|
||||
const segmentationId = await segmentationService.createSegmentationForSEGDisplaySet(
|
||||
segDisplaySet,
|
||||
{
|
||||
type: csToolsEnums.SegmentationRepresentations.Labelmap
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
2. **Custom Configuration**
|
||||
|
||||
```
|
||||
// Before - OHIF 3.8
|
||||
const segmentationId = await segmentationService.createSegmentationForSEGDisplaySet(
|
||||
segDisplaySet,
|
||||
'custom-id',
|
||||
false
|
||||
);
|
||||
// After - OHIF 3.9
|
||||
const segmentationId = await segmentationService.createSegmentationForSEGDisplaySet(
|
||||
segDisplaySet,
|
||||
{
|
||||
segmentationId: 'custom-id',
|
||||
type: csToolsEnums.SegmentationRepresentations.Labelmap
|
||||
}
|
||||
);
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
---
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
---
|
||||
id: seg-service-mod
|
||||
title: SegmentationService Modifications
|
||||
summary: Migration guide for segmentation representation management API changes in OHIF 3.9, showing how to transition from tool group-based methods to viewport-centric approaches for adding, removing, and querying segmentation representations.
|
||||
---
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Segmentation Representation Management API
|
||||
|
||||
```js
|
||||
addSegmentationRepresentationToToolGroup
|
||||
removeSegmentationRepresentationFromToolGroup
|
||||
getSegmentationRepresentationsForToolGroup
|
||||
```
|
||||
|
||||
In Cornerstone3D 2.0, segmentation representation management has shifted from a tool group-centric approach to a viewport-centric approach. This architectural change provides better control over segmentation rendering and simplifies the mental model for managing segmentations.
|
||||
|
||||
|
||||
### Adding Segmentation Representations
|
||||
|
||||
**Before (3.8)**:
|
||||
|
||||
```js
|
||||
// Tool group-based approach
|
||||
await segmentation.addSegmentationRepresentationToToolGroup(
|
||||
toolGroupId,
|
||||
segmentationId,
|
||||
hydrateSegmentation,
|
||||
csToolsEnums.SegmentationRepresentations.Labelmap
|
||||
);
|
||||
```
|
||||
|
||||
**After (3.9)**:
|
||||
|
||||
```js
|
||||
// Viewport-centric approach
|
||||
await segmentation.addSegmentationRepresentation(
|
||||
viewportId,
|
||||
{
|
||||
segmentationId: segmentationId,
|
||||
type: csToolsEnums.SegmentationRepresentations.Labelmap,
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Removing Segmentation Representations
|
||||
|
||||
**Before** :
|
||||
|
||||
```js
|
||||
// Remove specific representations from a tool group
|
||||
segmentation.removeSegmentationRepresentationFromToolGroup(
|
||||
toolGroupId,
|
||||
[segmentationRepresentationUID]
|
||||
);
|
||||
// Remove all representations from a tool group
|
||||
segmentation.removeSegmentationRepresentationFromToolGroup(toolGroupId);
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```js
|
||||
// Remove specific representation from a viewport
|
||||
segmentation.removeSegmentationRepresentation(
|
||||
viewportId,
|
||||
{
|
||||
segmentationId: segmentationId,
|
||||
type: csToolsEnums.SegmentationRepresentations.Labelmap
|
||||
}
|
||||
);
|
||||
// Remove all representations from a viewport
|
||||
segmentation.removeSegmentationRepresentations(viewportId);
|
||||
```
|
||||
|
||||
### Getting Segmentation Representations
|
||||
|
||||
**Before**:
|
||||
|
||||
```js
|
||||
// Get representations for a tool group
|
||||
const representations = segmentation.getSegmentationRepresentationsForToolGroup(toolGroupId);
|
||||
```
|
||||
|
||||
**After** :
|
||||
|
||||
```js
|
||||
// Get all representations for a viewport
|
||||
const representations = segmentation.getSegmentationRepresentations(viewportId);
|
||||
|
||||
// Get specific type of representations
|
||||
const labelmapReps = segmentation.getSegmentationRepresentations(viewportId, {
|
||||
type: csToolsEnums.SegmentationRepresentations.Labelmap
|
||||
});
|
||||
|
||||
// Get representations for specific segmentation
|
||||
const segmentationReps = segmentation.getSegmentationRepresentations(viewportId, {
|
||||
segmentationId: segmentationId
|
||||
});
|
||||
|
||||
// Get specific representation
|
||||
const representation = segmentation.getSegmentationRepresentation(viewportId, {
|
||||
segmentationId: segmentationId,
|
||||
type: csToolsEnums.SegmentationRepresentations.Labelmap
|
||||
});
|
||||
```
|
||||
|
||||
### Understanding the Specifier Pattern
|
||||
|
||||
The Cornerstone3D 2.0 (OHIF 3.9) API introduces a "specifier" pattern that provides more flexible and precise control over segmentation representations. A specifier is an object that can include:
|
||||
|
||||
```js
|
||||
type Specifier = {
|
||||
segmentationId?: string; // The ID of the segmentation
|
||||
type?: SegmentationRepresentations; // The type of representation (Labelmap, Contour, etc.)
|
||||
}
|
||||
```
|
||||
|
||||
The specifier pattern allows for:
|
||||
|
||||
1. **Precise Targeting**: You can target specific segmentations and representation types
|
||||
- Allows direct access to individual segmentations
|
||||
- Enables filtering by representation type
|
||||
|
||||
2. **Flexible Querying**: You can get all representations of a certain type or for a specific segmentation
|
||||
- Query by segmentation ID
|
||||
- Query by representation type
|
||||
- Combine queries for specific needs
|
||||
|
||||
3. **Granular Control**: You can manage representations at different levels of specificity
|
||||
- Viewport level control
|
||||
- Segmentation level control
|
||||
- Individual representation type control
|
||||
|
||||
### Examples of Specifier Usage
|
||||
|
||||
```js
|
||||
// Get all labelmap representations in a viewport
|
||||
const labelmaps = segmentation.getSegmentationRepresentations(viewportId, {
|
||||
type: csToolsEnums.SegmentationRepresentations.Labelmap
|
||||
});
|
||||
|
||||
// Get all representations of a specific segmentation (including contour, labelmap, surface)
|
||||
const segReps = segmentation.getSegmentationRepresentations(viewportId, {
|
||||
segmentationId: 'seg123'
|
||||
});
|
||||
|
||||
// Get a specific representation
|
||||
const specificRep = segmentation.getSegmentationRepresentation(viewportId, {
|
||||
segmentationId: 'seg123',
|
||||
type: csToolsEnums.SegmentationRepresentations.Labelmap
|
||||
});
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Benefits of the New Approach</summary>
|
||||
|
||||
1. **Direct Viewport Control**:
|
||||
- Each viewport can have its own unique representation configuration
|
||||
- No need to create separate tool groups for different viewport representations
|
||||
2. **Simpler Mental Model**:
|
||||
- Representations are directly tied to where they're displayed
|
||||
- No intermediate tool group layer to manage
|
||||
3. **More Flexible Rendering**:
|
||||
- Each viewport can render the same segmentation differently
|
||||
- Better support for multiple views of the same data
|
||||
4. **Improved Type Safety**:
|
||||
- Specifier pattern provides better TypeScript support
|
||||
- More explicit API with clearer intentions
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Migration Tips</summary>
|
||||
|
||||
1. **Replace Tool Group References**:
|
||||
- Search your codebase for `toolGroupId` references in segmentation code
|
||||
- Replace with appropriate `viewportId` references
|
||||
2. **Update Event Handlers**:
|
||||
- Update any code listening for segmentation events
|
||||
- Events now include viewportId instead of toolGroupId
|
||||
3. **Review Representation Management**:
|
||||
- Identify where you manage segmentation representations
|
||||
- Convert to using the new viewport-centric methods
|
||||
4. **Consider Viewport Context**:
|
||||
- Think about segmentation representation in terms of viewport display
|
||||
- Use specifiers to target specific representations when needed
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
---
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
---
|
||||
id: seg-style
|
||||
title: SegmentationService Style
|
||||
summary: Migration guide for segmentation styling API changes in OHIF 3.9, covering the transition to viewport-specific visibility controls, new style specification system using the specifier pattern, and updated methods for setting colors and toggling visibility.
|
||||
---
|
||||
|
||||
|
||||
## Style
|
||||
|
||||
|
||||
### setSegmentVisibility
|
||||
|
||||
since visibility is viewport concern and representation is what is being toggled ->
|
||||
|
||||
**Before (OHIF 3.8)**
|
||||
|
||||
```js
|
||||
setSegmentVisibility(
|
||||
segmentationId: string,
|
||||
segmentIndex: number,
|
||||
isVisible: boolean,
|
||||
toolGroupId?: string
|
||||
): void
|
||||
```
|
||||
|
||||
**After (OHIF 3.9)**
|
||||
|
||||
```js
|
||||
setSegmentVisibility(
|
||||
viewportId: string,
|
||||
segmentationId: string,
|
||||
segmentIndex: number,
|
||||
isVisible: boolean,
|
||||
type?: SegmentationRepresentations
|
||||
): void
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Migration Example</summary>
|
||||
|
||||
```js
|
||||
// Before
|
||||
segmentationService.setSegmentVisibility(
|
||||
'segmentation1',
|
||||
1,
|
||||
true,
|
||||
'toolGroup1'
|
||||
);
|
||||
// After
|
||||
segmentationService.setSegmentVisibility(
|
||||
'viewport1',
|
||||
'segmentation1',
|
||||
1,
|
||||
true
|
||||
);
|
||||
```
|
||||
|
||||
**Getting Viewport IDs**
|
||||
|
||||
When you need to update visibility across multiple viewports:
|
||||
|
||||
```js
|
||||
// Before
|
||||
const toolGroupIds = ['toolGroup1', 'toolGroup2'];
|
||||
toolGroupIds.forEach(toolGroupId => {
|
||||
segmentationService.setSegmentVisibility(
|
||||
'segmentation1',
|
||||
1,
|
||||
true,
|
||||
toolGroupId
|
||||
);
|
||||
});
|
||||
// After
|
||||
const viewportIds = segmentationService.getViewportIdsWithSegmentation('segmentation1');
|
||||
viewportIds.forEach(viewportId => {
|
||||
segmentationService.setSegmentVisibility(
|
||||
viewportId,
|
||||
'segmentation1',
|
||||
1,
|
||||
true
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
### get/set Configuration -> get/setStyle
|
||||
|
||||
The segmentation configuration system has been completely redesigned:
|
||||
|
||||
- Moved from global/toolGroup configuration to viewport-specific styles
|
||||
- Split rendering of inactive segmentations into separate API
|
||||
- More granular control over styles at different levels (global, segmentation, viewport, segment)
|
||||
|
||||
|
||||
**Before (OHIF 3.8)**
|
||||
|
||||
```js
|
||||
interface SegmentationConfig {
|
||||
brushSize: number;
|
||||
brushThresholdGate: number;
|
||||
fillAlpha: number;
|
||||
fillAlphaInactive: number;
|
||||
outlineWidthActive: number;
|
||||
renderFill: boolean;
|
||||
renderInactiveSegmentations: boolean;
|
||||
renderOutline: boolean;
|
||||
outlineOpacity: number;
|
||||
outlineOpacityInactive: number;
|
||||
}
|
||||
```
|
||||
|
||||
**After (OHIF 3.9)**
|
||||
|
||||
```js
|
||||
// Style Types
|
||||
interface StyleSpecifier {
|
||||
viewportId?: string;
|
||||
segmentationId?: string;
|
||||
type: SegmentationRepresentations;
|
||||
segmentIndex?: number;
|
||||
}
|
||||
interface LabelmapStyle {
|
||||
renderOutline: boolean;
|
||||
outlineWidth: number;
|
||||
renderFill: boolean;
|
||||
fillAlpha: number;
|
||||
outlineAlpha: number;
|
||||
// ....
|
||||
}
|
||||
// Functions
|
||||
getStyle(specifier: StyleSpecifier): LabelmapStyle | ContourStyle | SurfaceStyle;
|
||||
setStyle(specifier: StyleSpecifier, style: LabelmapStyle | ContourStyle | SurfaceStyle): void;
|
||||
setRenderInactiveSegmentations(viewportId: string, renderInactive: boolean): void;
|
||||
getRenderInactiveSegmentations(viewportId: string): boolean;
|
||||
```
|
||||
|
||||
|
||||
**Before:**
|
||||
|
||||
```js
|
||||
// Get global configuration
|
||||
const config = segmentationService.getConfiguration();
|
||||
console.log(config.fillAlpha, config.renderOutline);
|
||||
// Get tool group specific config
|
||||
const toolGroupConfig = segmentationService.getConfiguration('toolGroup1');
|
||||
```
|
||||
|
||||
**After:**
|
||||
|
||||
```js
|
||||
// Get global style for labelmap
|
||||
const labelmapStyle = segmentationService.getStyle({
|
||||
type: SegmentationRepresentations.Labelmap
|
||||
});
|
||||
// Get viewport-specific style
|
||||
const viewportStyle = segmentationService.getStyle({
|
||||
viewportId: 'viewport1',
|
||||
type: SegmentationRepresentations.Labelmap
|
||||
});
|
||||
// Get segmentation-specific style
|
||||
const segmentationStyle = segmentationService.getStyle({
|
||||
segmentationId: 'seg1',
|
||||
type: SegmentationRepresentations.Labelmap
|
||||
});
|
||||
// Get segment-specific style
|
||||
const segmentStyle = segmentationService.getStyle({
|
||||
segmentationId: 'seg1',
|
||||
type: SegmentationRepresentations.Labelmap,
|
||||
segmentIndex: 1
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
**Setting Configuration/Style**
|
||||
|
||||
**Before:**
|
||||
|
||||
```js
|
||||
segmentationService.setConfiguration({
|
||||
fillAlpha: 0.5,
|
||||
outlineWidthActive: 2,
|
||||
renderOutline: true,
|
||||
renderFill: true,
|
||||
renderInactiveSegmentations: true
|
||||
});
|
||||
```
|
||||
|
||||
**After:**
|
||||
|
||||
```js
|
||||
// Set global style
|
||||
segmentationService.setStyle(
|
||||
{ type: SegmentationRepresentations.Labelmap },
|
||||
{
|
||||
fillAlpha: 0.5,
|
||||
outlineWidth: 2,
|
||||
renderOutline: true,
|
||||
renderFill: true
|
||||
}
|
||||
);
|
||||
// Set viewport-specific style
|
||||
segmentationService.setStyle(
|
||||
{
|
||||
viewportId: 'viewport1',
|
||||
type: SegmentationRepresentations.Labelmap
|
||||
},
|
||||
{
|
||||
fillAlpha: 0.5,
|
||||
outlineWidth: 2
|
||||
}
|
||||
);
|
||||
// Handle inactive segmentations separately
|
||||
segmentationService.setRenderInactiveSegmentations('viewport1', true);
|
||||
```
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Migration Examples</summary>
|
||||
|
||||
**Combining Multiple Style Settings**
|
||||
|
||||
**Before:**
|
||||
|
||||
```js
|
||||
segmentationService.setConfiguration({
|
||||
fillAlpha: 0.5,
|
||||
fillAlphaInactive: 0.2,
|
||||
outlineWidthActive: 2,
|
||||
outlineOpacity: 1,
|
||||
outlineOpacityInactive: 0.5,
|
||||
renderOutline: true,
|
||||
renderFill: true,
|
||||
renderInactiveSegmentations: true
|
||||
});
|
||||
```
|
||||
|
||||
**After:**
|
||||
|
||||
```js
|
||||
// Set base style
|
||||
segmentationService.setStyle(
|
||||
{ type: SegmentationRepresentations.Labelmap },
|
||||
{
|
||||
fillAlpha: 0.5,
|
||||
outlineWidth: 2,
|
||||
outlineAlpha: 1,
|
||||
renderOutline: true,
|
||||
renderFill: true
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
**Set inactive rendering per viewport**
|
||||
|
||||
```js
|
||||
segmentationService.setRenderInactiveSegmentations('viewport1', true);
|
||||
// Set style for inactive segments if needed
|
||||
segmentationService.setStyle(
|
||||
{
|
||||
viewportId: 'viewport1',
|
||||
type: SegmentationRepresentations.Labelmap,
|
||||
segmentationId: 'seg1'
|
||||
},
|
||||
{
|
||||
fillAlpha: 0.2,
|
||||
outlineAlpha: 0.5
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## setSegmentRGBAColor , setSegmentOpacity, setSegmentRGBA
|
||||
Previously, the SegmentationService had multiple redundant methods for setting colors and opacity (`setSegmentRGBA`, `setSegmentColor`, `setSegmentOpacity`). This led to confusion and potential state inconsistencies between the service and Cornerstone.js Tools.
|
||||
|
||||
The old methods (`setSegmentRGBA`, `setSegmentRGBA`, and `setSegmentOpacity`) are now removed.
|
||||
|
||||
|
||||
1. Replace `setSegmentRGBAColor`, `setSegmentRGBA`, and `setSegmentOpacity` calls: Replace all instances of the old methods with the new `setSegmentColor` method. Note that you now need to provide the `viewportId` as the first argument since segment color is managed per viewport and representation in cornerstone3D.
|
||||
|
||||
|
||||
**Before**
|
||||
|
||||
```js
|
||||
// Old API:
|
||||
segmentationService.setSegmentRGBAColor(segmentationId, segmentIndex, rgbaColor, toolGroupId);
|
||||
segmentationService.setSegmentRGBA(segmentationId, segmentIndex, rgbaColor, toolGroupId);
|
||||
segmentationService.setSegmentOpacity(segmentationId, segmentIndex, opacity, toolGroupId);
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```js
|
||||
// New API:
|
||||
segmentationService.setSegmentColor(viewportId, segmentationId, segmentIndex, color); // color is an array of [red, green, blue, alpha]
|
||||
```
|
||||
|
||||
The new `color` argument is an array representing the RGBA color, where the alpha component determines the opacity. Since the Cornerstone Tools library handles segment color per viewport and representation, we require the `viewportId` as an argument now.
|
||||
|
||||
|
||||
|
||||
2. **Retrieve Segment Color using** `getSegmentColor`: The new `getSegmentColor` provides a way to fetch the color of a segment within a specific viewport.
|
||||
|
||||
```js
|
||||
const color = segmentationService.getSegmentColor(viewportId, segmentationId, segmentIndex); //returns [r, g, b, a]
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## ToggleSegmentationVisibility
|
||||
|
||||
In Cornerstone3D v2.x, `toggleSegmentationVisibility` has been replaced with `toggleSegmentationRepresentationVisibility`. This change reflects the fact that
|
||||
a representation is what is being toggled, not the segmentation.
|
||||
|
||||
|
||||
**Before (OHIF 3.8)**
|
||||
|
||||
```js
|
||||
// Toggle visibility for a segmentation globally
|
||||
segmentationService.toggleSegmentationVisibility(segmentationId);
|
||||
```
|
||||
|
||||
**After (OHIF 3.9)**
|
||||
|
||||
```js
|
||||
// Toggle visibility for a segmentation representation in a specific viewport
|
||||
segmentationService.toggleSegmentationRepresentationVisibility(viewportId, {
|
||||
segmentationId: segmentationId,
|
||||
type: csToolsEnums.SegmentationRepresentations.Labelmap
|
||||
});
|
||||
```
|
||||
|
||||
**Migration Steps**
|
||||
|
||||
1. Update all calls to `toggleSegmentationVisibility` to use `toggleSegmentationRepresentationVisibility`
|
||||
2. Add the required `viewportId` parameter
|
||||
3. Add a `type` parameter specifying the representation type (e.g., Labelmap, Contour)
|
||||
4. If you were toggling visibility across all viewports, you'll need to loop through the viewports:
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Additional Notes</summary>
|
||||
|
||||
|
||||
- Each viewport can now have independent visibility settings for the same segmentation
|
||||
- The visibility state is specific to the representation type (Labelmap, Contour, etc.)
|
||||
- To check current visibility, use `getSegmentationRepresentationVisibility(viewportId, { segmentationId, type })`
|
||||
</details>
|
||||
|
||||
---
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
---
|
||||
id: seg-other
|
||||
title: Other Changes
|
||||
summary: Migration guide for additional segmentation service changes in OHIF 3.9, covering updates to addOrUpdateSegmentation with new data structures, loadSegmentationsForViewport, highlightSegment, and jumpToSegmentCenter methods with viewport-centric approach.
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
## addOrUpdateSegmentation
|
||||
|
||||
This was a public method but there is a good chance you were not using it
|
||||
|
||||
|
||||
**Before (OHIF 3.8)**
|
||||
|
||||
```js
|
||||
// Before
|
||||
addOrUpdateSegmentation(
|
||||
segmentation: Segmentation,
|
||||
suppressEvents = false,
|
||||
notYetUpdatedAtSource = false
|
||||
): string
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```js
|
||||
addOrUpdateSegmentation(
|
||||
segmentationInput: SegmentationPublicInput | Partial<Segmentation>
|
||||
)
|
||||
```
|
||||
|
||||
### Data Structure Changes
|
||||
|
||||
The segmentation object that was used previously was a custom segmentation object that was used internally by the SegmentationService. But
|
||||
we have moved to the cornerstone public segmentation input type.
|
||||
|
||||
**Before:**
|
||||
|
||||
```js
|
||||
const segmentation = {
|
||||
id: 'segmentation1',
|
||||
type: SegmentationRepresentations.Labelmap,
|
||||
isActive: true,
|
||||
activeSegmentIndex: 1,
|
||||
segments: [
|
||||
{
|
||||
segmentIndex: 1,
|
||||
color: [255, 0, 0],
|
||||
isVisible: true,
|
||||
isLocked: false,
|
||||
opacity: 255
|
||||
}
|
||||
],
|
||||
label: 'Segmentation 1',
|
||||
cachedStats: {},
|
||||
representationData: {
|
||||
LABELMAP: {
|
||||
volumeId: 'volume1',
|
||||
referencedVolumeId: 'reference1'
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
**After:**
|
||||
|
||||
This matches the cornerstone public segmentation input type.
|
||||
|
||||
```js
|
||||
const segmentationInput = {
|
||||
segmentationId: 'segmentation1',
|
||||
representation: {
|
||||
type: SegmentationRepresentations.Labelmap,
|
||||
data: {
|
||||
imageIds: segmentationImageIds,
|
||||
referencedVolumeId: 'reference1'
|
||||
}
|
||||
},
|
||||
config: {
|
||||
label: 'Segmentation 1',
|
||||
segments: {
|
||||
1: {
|
||||
label: 'Segment 1',
|
||||
active: true,
|
||||
locked: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Migration Examples</summary>
|
||||
|
||||
|
||||
```js
|
||||
// Before
|
||||
const newSegmentation = {
|
||||
id: 'seg1',
|
||||
type: SegmentationRepresentations.Labelmap,
|
||||
segments: [...],
|
||||
representationData: {
|
||||
LABELMAP: {
|
||||
volumeId: 'volume1',
|
||||
referencedVolumeId: 'reference1'
|
||||
}
|
||||
}
|
||||
};
|
||||
segmentationService.addOrUpdateSegmentation(newSegmentation);
|
||||
|
||||
// After
|
||||
segmentationService.addOrUpdateSegmentation({
|
||||
segmentationId: 'seg1',
|
||||
representation: {
|
||||
type: SegmentationRepresentations.Labelmap,
|
||||
data: {
|
||||
imageIds: segmentationImageIds,
|
||||
referencedVolumeId: 'reference1'
|
||||
}
|
||||
},
|
||||
config: {
|
||||
segments: {
|
||||
1: {
|
||||
label: 'Segment 1',
|
||||
active: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
**Updating Existing Segmentation**
|
||||
|
||||
```js
|
||||
// Before
|
||||
const updatedSegmentation = {
|
||||
...existingSegmentation,
|
||||
segments: [...modifiedSegments],
|
||||
activeSegmentIndex: 2
|
||||
};
|
||||
segmentationService.addOrUpdateSegmentation(updatedSegmentation);
|
||||
|
||||
// After
|
||||
segmentationService.addOrUpdateSegmentation({
|
||||
segmentationId: 'seg1',
|
||||
config: {
|
||||
segments: {
|
||||
2: { active: true },
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## loadSegmentationsForViewport
|
||||
|
||||
same as addOrUpdateSegmentation, you should pass in the new segmentation data structure.
|
||||
|
||||
For instance
|
||||
|
||||
**Before**
|
||||
|
||||
```js
|
||||
const segmentations = [
|
||||
{
|
||||
id: '1',
|
||||
label: 'Segmentations',
|
||||
segments: labels.map((label, index) => ({
|
||||
segmentIndex: index + 1,
|
||||
label
|
||||
})),
|
||||
isActive: true,
|
||||
activeSegmentIndex: 1,
|
||||
},
|
||||
];
|
||||
|
||||
commandsManager.runCommand('loadSegmentationsForViewport', {
|
||||
segmentations,
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
**After**
|
||||
|
||||
```js
|
||||
|
||||
const labels = ['Segment 1', 'Segment 2', 'Segment 3'];
|
||||
|
||||
const segmentations = [
|
||||
{
|
||||
segmentationId: '1',
|
||||
representation: {
|
||||
type: Enums.SegmentationRepresentations.Labelmap,
|
||||
},
|
||||
config: {
|
||||
label: 'Segmentations',
|
||||
segments: labels.reduce((acc, label, index) => {
|
||||
acc[index + 1] = {
|
||||
label,
|
||||
active: index === 0, // First segment is active
|
||||
locked: false,
|
||||
};
|
||||
return acc;
|
||||
}, {}),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
commandsManager.runCommand('loadSegmentationsForViewport', {
|
||||
segmentations,
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
## highlightSegment
|
||||
|
||||
**Before (OHIF 3.8)**
|
||||
|
||||
```js
|
||||
// Before (v1.x)
|
||||
highlightSegment(
|
||||
segmentationId: string,
|
||||
segmentIndex: number,
|
||||
toolGroupId?: string,
|
||||
alpha = 0.9,
|
||||
animationLength = 750,
|
||||
hideOthers = true,
|
||||
highlightFunctionType = 'ease-in-out'
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
**After (OHIF 3.9)**
|
||||
|
||||
```js
|
||||
highlightSegment(
|
||||
segmentationId: string,
|
||||
segmentIndex: number,
|
||||
viewportId?: string, // notice viewportId instead of toolGroupId
|
||||
alpha = 0.9,
|
||||
animationLength = 750,
|
||||
hideOthers = true,
|
||||
highlightFunctionType = 'ease-in-out'
|
||||
)
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Key Changes</summary>
|
||||
|
||||
1. Removed `toolGroupId` in favor of `viewportId`
|
||||
2. If no viewportId is provided, highlights in all relevant viewports
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Migration Examples</summary>
|
||||
|
||||
**Basic Usage**
|
||||
|
||||
```js
|
||||
// Before
|
||||
segmentationService.highlightSegment(
|
||||
'seg1',
|
||||
1,
|
||||
'toolGroup1',
|
||||
0.9,
|
||||
750,
|
||||
true,
|
||||
);
|
||||
// After
|
||||
segmentationService.highlightSegment(
|
||||
'seg1',
|
||||
1,
|
||||
'viewport1',
|
||||
0.9,
|
||||
750,
|
||||
true
|
||||
);
|
||||
```
|
||||
|
||||
**Highlighting in Multiple Views**
|
||||
|
||||
```js
|
||||
// Before
|
||||
const toolGroupIds = ['toolGroup1', 'toolGroup2'];
|
||||
toolGroupIds.forEach(toolGroupId => {
|
||||
segmentationService.highlightSegment(
|
||||
'seg1',
|
||||
1,
|
||||
toolGroupId
|
||||
);
|
||||
});
|
||||
// After - Method 1: Let service handle multiple viewports
|
||||
segmentationService.highlightSegment('seg1', 1);
|
||||
// After - Method 2: Explicitly specify viewports
|
||||
const viewportIds = ['viewport1', 'viewport2'];
|
||||
viewportIds.forEach(viewportId => {
|
||||
segmentationService.highlightSegment(
|
||||
'seg1',
|
||||
1,
|
||||
viewportId
|
||||
);
|
||||
});
|
||||
```
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## jumpToSegmentCenter
|
||||
|
||||
**Before (OHIF 3.8)**
|
||||
|
||||
```js
|
||||
jumpToSegmentCenter(
|
||||
segmentationId: string,
|
||||
segmentIndex: number,
|
||||
toolGroupId?: string,
|
||||
highlightAlpha = 0.9,
|
||||
highlightSegment = true,
|
||||
animationLength = 750,
|
||||
highlightHideOthers = false,
|
||||
highlightFunctionType = 'ease-in-out'
|
||||
)
|
||||
```
|
||||
|
||||
**After (OHIF 3.9)**
|
||||
|
||||
```js
|
||||
jumpToSegmentCenter(
|
||||
segmentationId: string,
|
||||
segmentIndex: number,
|
||||
viewportId? string, // notice viewportId instead of toolGroupId
|
||||
highlightAlpha = 0.9,
|
||||
highlightSegment = true,
|
||||
animationLength = 750,
|
||||
highlightHideOthers = false,
|
||||
highlightFunctionType = 'ease-in-out'
|
||||
)
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Key Changes</summary>
|
||||
|
||||
1. Removed `toolGroupId` parameter infavor of viewportId
|
||||
2. Automatically handles relevant viewports if `viewportId` not provided
|
||||
|
||||
|
||||
```
|
||||
// Before
|
||||
segmentationService.jumpToSegmentCenter(
|
||||
'seg1',
|
||||
1,
|
||||
'toolGroup1'
|
||||
);
|
||||
// After
|
||||
segmentationService.jumpToSegmentCenter(
|
||||
'seg1',
|
||||
1,
|
||||
'viewportId1'
|
||||
);
|
||||
```
|
||||
|
||||
</details>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
---
|
||||
id: segmentation-index
|
||||
title: Segmentation
|
||||
sidebar_position: 1
|
||||
summary: Migration guide for segmentation architecture changes in OHIF 3.9, covering the shift from tool group-centric to viewport-centric architecture to support advanced visualization capabilities and more flexible segmentation handling.
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
|
||||
|
||||
:::info
|
||||
This migration involves significant architectural changes to the segmentation system. While we typically aim for incremental updates, the shift from a tool group-centric to a viewport-centric architecture was necessary to support OHIF 3.9's advanced visualization capabilities, and more flexible segmentation handling.
|
||||
|
||||
Don't worry - we'll guide you through each change step by step!
|
||||
:::
|
||||
|
||||
|
||||
<DocCardList items={useCurrentSidebarCategory().items}/>
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
id: 2-renamings
|
||||
title: Renamings
|
||||
sidebar_position: 2
|
||||
summary: Migration guide for renamed components in OHIF 3.9, including the Panel Measurements name change from 'measure' to 'panelMeasurement' and addIcon utility changes to support both UI packages.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
|
||||
## Panel Measurements
|
||||
|
||||
The panel in the default extension is renamed from `measure` to `panelMeasurement` to be more consistent with the rest of the extensions.
|
||||
|
||||
**Action Needed**
|
||||
|
||||
Update any references to the `measure` panel to `panelMeasurement` in your code.
|
||||
|
||||
Find and replace
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Before" label="Before 🕰️" default>
|
||||
@ohif/extension-default.panelModule.measure
|
||||
</TabItem>
|
||||
<TabItem value="After" label="After 🚀" >
|
||||
@ohif/extension-cornerstone.panelModule.panelMeasurement
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## addIcon from ui
|
||||
|
||||
The addIcon from the ui package has had a version added in the default extension as
|
||||
`utils.addIcon` which adds to both `ui` and `ui-next`.
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
id: 3-data-sources
|
||||
title: Data Sources
|
||||
sidebar_position: 3
|
||||
summary: Migration guide for BulkDataURI configuration changes in OHIF 3.9, explaining the transition from a simple boolean flag to a more flexible configuration object with additional control options.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
## BulkDataURI Configuration
|
||||
|
||||
We've updated the configuration for BulkDataURI to provide more flexibility and control. This guide will help you migrate from the old configuration to the new one.
|
||||
|
||||
### What's Changing?
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Before" label="Before 🕰️" default>
|
||||
|
||||
```javascript
|
||||
useBulkDataURI: false,
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="After" label="After 🚀">
|
||||
|
||||
```javascript
|
||||
bulkDataURI: {
|
||||
enabled: true,
|
||||
// Additional configuration **options**
|
||||
},
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
**Additional Notes:**
|
||||
- The new configuration allows for more granular control over BulkDataURI behavior.
|
||||
- You can now add custom URL prefixing logic using the startsWith and prefixWith properties.
|
||||
- This change enables easier correction of retrieval URLs, especially in scenarios where URLs pass through multiple systems.
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Measurements
|
||||
summary: Migration guide for measurement changes in OHIF 3.9, covering the new structured 'displayText' object with primary and secondary arrays for better organization, and the renaming of 'selected' property to 'isSelected'.
|
||||
---
|
||||
|
||||
|
||||
## Display Text
|
||||
|
||||
|
||||
Previously, `displayText` for measurements was often a simple string or an array of strings. This approach made it difficult to distinguish between primary measurement values (e.g., length, area) and secondary information (e.g., series number, instance number). It also limited styling options for differentiating these types of information.
|
||||
|
||||
The new approach introduces a structured object for `displayText`, consisting of `primary` and `secondary` arrays. This separation allows for better organization and presentation of measurement information. The `primary` array is intended for the main measurement values (on the left), while the `secondary` array is for contextual information like series and instance numbers (on the right)
|
||||
|
||||
### Migration Steps
|
||||
|
||||
If you have custom measurement tools or modify existing ones, you need to update the `getDisplayText` functions within the `measurementServiceMappings` to return a structured object in the new format.
|
||||
|
||||
**Update Measurement Mappings:** If your extension defines custom measurement tools or modifies existing ones, update the `getDisplayText` functions within the `measurementServiceMappings` to return a structured object in the new format.
|
||||
|
||||
```js
|
||||
// Old Implementation (example for Length tool)
|
||||
function getDisplayText(mappedAnnotations, displaySet, customizationService) {
|
||||
// ...
|
||||
return `${roundedLength} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})`;
|
||||
}
|
||||
// New Implementation
|
||||
function getDisplayText(mappedAnnotations, displaySet) {
|
||||
// ...
|
||||
return {
|
||||
primary: [`${roundedLength} ${unit}`], // Primary measurement value
|
||||
secondary: [`S: ${SeriesNumber}${instanceText}${frameText}`], // Secondary information
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### selected property
|
||||
|
||||
`selected` property on measurements is now renamed to `isSelected` to match the rest of `isLocked` , `isVisible` naming convention.
|
||||
|
||||
Migration: you probably don't need to perform any migration
|
||||
|
||||
---
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
---
|
||||
id: viewport-action-corner
|
||||
title: ViewportActionCorner
|
||||
summary: Migration guide for ViewportActionCornerService in OHIF 3.9, introducing the new addComponent and addComponents methods that provide more reliable positioning of multiple components within viewport corners.
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
## Key Changes and Rationale
|
||||
|
||||
Previously, the `ViewportActionCornersService` used the `setComponent` or `setComponents` methods to add components to viewport corners. These methods, when used with multiple components, would essentially overwrite existing components at the same location, unless great care was taken with the `indexPriority` property. This made it difficult to reliably position multiple components within the same corner.
|
||||
|
||||
The new approach introduces the methods `addComponent` and `addComponents`, which insert components into the viewport corners based on an optional `indexPriority` property and provide predictable ordering based on the relative `indexPriority` of the components already at the corner. If no `indexPriority` is given, components are added to the end (for the left side) or the beginning (for the right side) by default.
|
||||
|
||||
### Migration Steps
|
||||
|
||||
**Update Component Addition Methods:** Replace calls to `setComponent` and `setComponents` with `addComponent` and `addComponents`, respectively.
|
||||
|
||||
```js
|
||||
// Old API
|
||||
viewportActionCornersService.setComponent({
|
||||
viewportId,
|
||||
id: 'myComponent',
|
||||
component: <MyComponent />,
|
||||
location: viewportActionCornersService.LOCATIONS.topRight
|
||||
});
|
||||
```
|
||||
|
||||
**New API**
|
||||
|
||||
```js
|
||||
viewportActionCornersService.addComponent({
|
||||
viewportId,
|
||||
id: 'myComponent',
|
||||
component: <MyComponent />,
|
||||
location: viewportActionCornersService.LOCATIONS.topRight,
|
||||
indexPriority: 1, // indexPriority is now optional and determines placement order within the corner
|
||||
});
|
||||
|
||||
```
|
||||
+344
@@ -0,0 +1,344 @@
|
||||
---
|
||||
id: state-sync-service
|
||||
title: StateSyncService
|
||||
summary: Migration guide for transitioning from StateSyncService to Zustand stores in OHIF 3.9, covering all store types including LutPresentationStore, PositionPresentationStore, ViewportGridStore, and more with examples of old and new API usage.
|
||||
---
|
||||
|
||||
|
||||
## Migrating from StateSyncService to Zustand Stores
|
||||
|
||||
The `StateSyncService` has been deprecated in favor of more modern and efficient state management using Zustand stores. This migration guide outlines the reasons for the change and provides step-by-step instructions on how to migrate your extension or mode from using `StateSyncService` to Zustand.
|
||||
|
||||
## Why Migrate?
|
||||
|
||||
The `StateSyncService` had limitations:
|
||||
|
||||
- **Limited Reactivity:** Updates weren't always reactive, requiring manual re-renders.
|
||||
- **Lack of Granularity:** It stored large chunks of state, hindering performance.
|
||||
- **Complexity:** Managing and syncing state across components was cumbersome.
|
||||
|
||||
Zustand offers several advantages:
|
||||
|
||||
- **Lightweight and Fast:** Zustand is a minimal and performant state management library.
|
||||
- **Granular Control:** Create individual stores for specific data, improving reactivity and performance.
|
||||
- **Simplified API:** Easy-to-use hooks for subscribing and updating state.
|
||||
|
||||
## Migration Steps:
|
||||
|
||||
1. **Identify State to Migrate:** Determine which parts of your extension or mode rely on the `StateSyncService`. Typical examples include:
|
||||
- **Viewport Presentations:** LUT and position information for viewports.
|
||||
- **Layout State:** Custom grid layouts and one-up toggling.
|
||||
- **Synchronizers:** State for cross-viewport synchronization.
|
||||
- **UI State:** UI-specific settings.
|
||||
2. **Replace StateSyncService Usage:** In your extension or mode:
|
||||
- **Import Zustand Stores:** Import the new stores you created.
|
||||
- **Replace** `getState()` and `store()`: Use the Zustand hooks (`useStore`, `set`, `get`) to access and update state in your components.
|
||||
- **Handle Presentation IDs:** Implement logic for generating and managing presentation IDs within your stores or relevant components. This can involve using unique keys based on viewport options, display sets, and unique indices. See the `presentationUtils.ts` file for example implementations.
|
||||
- **Rehydrate State:** On mode entry, rehydrate your Zustand stores with any relevant persisted state from localStorage or other storage mechanisms.
|
||||
- **Clear State on Mode Exit:** Ensure you clear your Zustand stores appropriately on mode exit to prevent memory leaks.
|
||||
|
||||
|
||||
|
||||
### `LutPresentationStore`
|
||||
|
||||
|
||||
**Before (StateSyncService):**
|
||||
|
||||
```js
|
||||
const stateSyncService = servicesManager.services.stateSyncService;
|
||||
const lutPresentationStore = stateSyncService.getState().lutPresentationStore;
|
||||
const lutPresentation = lutPresentationStore[presentationId];
|
||||
// ...to update
|
||||
stateSyncService.store({
|
||||
lutPresentationStore: {
|
||||
...lutPresentationStore,
|
||||
[presentationId]: newLutPresentation,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**After (Zustand):**
|
||||
|
||||
```js
|
||||
import { useLutPresentationStore } from '../stores/useLutPresentationStore';
|
||||
const { lutPresentationStore, setLutPresentation } = useLutPresentationStore();
|
||||
const lutPresentation = lutPresentationStore[presentationId];
|
||||
// ...to update
|
||||
setLutPresentation(presentationId, newLutPresentation);
|
||||
```
|
||||
|
||||
The `getPresentationId` for `lutPresentationStore` was previously registered in `platform/core`. Now, the Zustand store provides this functionality.
|
||||
|
||||
```js
|
||||
// Fetch getPresentationId functions from respective Zustand stores
|
||||
const { getPresentationId: getLutPresentationId } = useLutPresentationStore.getState();
|
||||
|
||||
// Register presentation id providers
|
||||
viewportGridService.addPresentationIdProvider('lutPresentationId', getLutPresentationId);
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
### `PositionPresentationStore`
|
||||
|
||||
**Before (StateSyncService):**
|
||||
|
||||
```js
|
||||
const stateSyncService = servicesManager.services.stateSyncService;
|
||||
const positionPresentationStore = stateSyncService.getState().positionPresentationStore;
|
||||
const positionPresentation = positionPresentationStore[presentationId];
|
||||
// ...to update
|
||||
stateSyncService.store({
|
||||
positionPresentationStore: {
|
||||
...positionPresentationStore,
|
||||
[presentationId]: newPositionPresentation,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**After (Zustand):**
|
||||
|
||||
```js
|
||||
import { usePositionPresentationStore } from '../stores/usePositionPresentationStore';
|
||||
const { positionPresentationStore, setPositionPresentation } = usePositionPresentationStore();
|
||||
const positionPresentation = positionPresentationStore[presentationId];
|
||||
// ...to update
|
||||
setPositionPresentation(presentationId, newPositionPresentation);
|
||||
```
|
||||
|
||||
Similar to lutPresentationId, the PositionPresentationId is also registered from outside
|
||||
|
||||
```js
|
||||
|
||||
const { getPresentationId: getPositionPresentationId } = usePositionPresentationStore.getState();
|
||||
|
||||
// register presentation id providers
|
||||
viewportGridService.addPresentationIdProvider(
|
||||
'positionPresentationId',
|
||||
getPositionPresentationId
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `ViewportGridStore`
|
||||
|
||||
**Before (StateSyncService):**
|
||||
|
||||
```js
|
||||
const stateSyncService = servicesManager.services.stateSyncService;
|
||||
const viewportGridStore = stateSyncService.getState().viewportGridStore;
|
||||
const gridState = viewportGridStore[storeId];
|
||||
// ...to update
|
||||
stateSyncService.store({
|
||||
viewportGridStore: {
|
||||
...viewportGridStore,
|
||||
[storeId]: newGridState,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**After (Zustand):**
|
||||
|
||||
```js
|
||||
import { useViewportGridStore } from '../stores/useViewportGridStore';
|
||||
const { viewportGridState, setViewportGridState } = useViewportGridStore.getState();
|
||||
const gridState = viewportGridState[storeId];
|
||||
// ...to update
|
||||
setViewportGridState(storeId, newGridState);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `DisplaySetSelectorStore`
|
||||
|
||||
|
||||
**Before (StateSyncService):**
|
||||
|
||||
```js
|
||||
const stateSyncService = servicesManager.services.stateSyncService;
|
||||
const displaySetSelectorMap = stateSyncService.getState().displaySetSelectorMap;
|
||||
const displaySetUID = displaySetSelectorMap[selectorKey];
|
||||
// ...to update
|
||||
stateSyncService.store({
|
||||
displaySetSelectorMap: {
|
||||
...displaySetSelectorMap,
|
||||
[selectorKey]: newDisplaySetUID,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**After (Zustand):**
|
||||
|
||||
```js
|
||||
import { useDisplaySetSelectorStore } from '../stores/useDisplaySetSelectorStore';
|
||||
const { displaySetSelectorMap, setDisplaySetSelector } = useDisplaySetSelectorStore();
|
||||
const displaySetUID = displaySetSelectorMap[selectorKey];
|
||||
// ...to update
|
||||
setDisplaySetSelector(selectorKey, newDisplaySetUID);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `HangingProtocolStageIndexStore`
|
||||
|
||||
|
||||
**Before (StateSyncService):**
|
||||
|
||||
```js
|
||||
const stateSyncService = servicesManager.services.stateSyncService;
|
||||
const hangingProtocolStageIndexMap = stateSyncService.getState().hangingProtocolStageIndexMap;
|
||||
const hpInfo = hangingProtocolStageIndexMap[cacheId];
|
||||
// ...to update
|
||||
stateSyncService.store({
|
||||
hangingProtocolStageIndexMap: {
|
||||
...hangingProtocolStageIndexMap,
|
||||
[cacheId]: newHpInfo,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**After (Zustand):**
|
||||
|
||||
```js
|
||||
import { useHangingProtocolStageIndexStore } from '../stores/useHangingProtocolStageIndexStore';
|
||||
const { hangingProtocolStageIndexMap, setHangingProtocolStageIndex } = useHangingProtocolStageIndexStore();
|
||||
const hpInfo = hangingProtocolStageIndexMap[cacheId];
|
||||
// ...to update
|
||||
setHangingProtocolStageIndex(cacheId, newHpInfo);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `ToggleHangingProtocolStore`
|
||||
|
||||
|
||||
**Before (StateSyncService):**
|
||||
|
||||
```js
|
||||
const stateSyncService = servicesManager.services.stateSyncService;
|
||||
const toggleHangingProtocol = stateSyncService.getState().toggleHangingProtocol;
|
||||
const previousHpInfo = toggleHangingProtocol[storedHanging];
|
||||
// ...to update
|
||||
stateSyncService.store({
|
||||
toggleHangingProtocol: {
|
||||
...toggleHangingProtocol,
|
||||
[storedHanging]: newHpInfo,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**After (Zustand):**
|
||||
|
||||
```js
|
||||
import { useToggleHangingProtocolStore } from '../stores/useToggleHangingProtocolStore';
|
||||
const { toggleHangingProtocol, setToggleHangingProtocol } = useToggleHangingProtocolStore();
|
||||
const previousHpInfo = toggleHangingProtocol[storedHanging];
|
||||
// ...to update
|
||||
setToggleHangingProtocol(storedHanging, newHpInfo);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `ToggleOneUpViewportGridStore`
|
||||
|
||||
|
||||
**Before (StateSyncService):**
|
||||
|
||||
```js
|
||||
const stateSyncService = servicesManager.services.stateSyncService;
|
||||
const toggleOneUpViewportGridStore = stateSyncService.getState().toggleOneUpViewportGridStore;
|
||||
const previousGridState = toggleOneUpViewportGridStore.layout; // Assuming layout was a property
|
||||
// ...to update
|
||||
stateSyncService.store({
|
||||
toggleOneUpViewportGridStore: newGridState,
|
||||
});
|
||||
```
|
||||
|
||||
**After (Zustand):**
|
||||
|
||||
```js
|
||||
import { useToggleOneUpViewportGridStore } from '../stores/useToggleOneUpViewportGridStore';
|
||||
const { toggleOneUpViewportGridStore, setToggleOneUpViewportGridStore } = useToggleOneUpViewportGridStore();
|
||||
const previousGridState = toggleOneUpViewportGridStore; // No nested layout property
|
||||
// ...to update
|
||||
setToggleOneUpViewportGridStore(newGridState);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `UIStateStore`
|
||||
|
||||
|
||||
**Before (StateSyncService):**
|
||||
|
||||
```js
|
||||
const stateSyncService = servicesManager.services.stateSyncService;
|
||||
const uiState = stateSyncService.getState().uiStateStore[someUIKey];
|
||||
// ...to update
|
||||
stateSyncService.store({
|
||||
uiStateStore: {
|
||||
...stateSyncService.getState().uiStateStore,
|
||||
[someUIKey]: newUIState,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**After (Zustand):**
|
||||
|
||||
```js
|
||||
import { useUIStateStore } from '../stores/useUIStateStore';
|
||||
const { uiState, setUIState } = useUIStateStore();
|
||||
const currentUIState = uiState[someUIKey];
|
||||
// ...to update
|
||||
setUIState(someUIKey, newUIState);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `ViewportsByPositionStore`
|
||||
|
||||
|
||||
**Before (StateSyncService):**
|
||||
|
||||
```js
|
||||
const stateSyncService = servicesManager.services.stateSyncService;
|
||||
const viewportsByPosition = stateSyncService.getState().viewportsByPosition;
|
||||
const cachedViewport = viewportsByPosition[positionId];
|
||||
// ...to update
|
||||
stateSyncService.store({
|
||||
viewportsByPosition: {
|
||||
...viewportsByPosition,
|
||||
[positionId]: newViewport,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**After (Zustand):**
|
||||
|
||||
```js
|
||||
import { useViewportsByPositionStore } from '../stores/useViewportsByPositionStore';
|
||||
const { viewportsByPosition, setViewportsByPosition } = useViewportsByPositionStore();
|
||||
const cachedViewport = viewportsByPosition[positionId];
|
||||
// ...to update
|
||||
setViewportsByPosition(positionId, newViewport);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `SegmentationPresentationStore`
|
||||
|
||||
**After (Zustand):**
|
||||
|
||||
```js
|
||||
import { useSegmentationPresentationStore } from '../stores/useSegmentationPresentationStore';
|
||||
const { segmentationPresentationStore, setSegmentationPresentation } =
|
||||
useSegmentationPresentationStore();
|
||||
// ...to update
|
||||
setSegmentationPresentation(presentationId, newSegmentationPresentation);
|
||||
// You likely have functions within the store like:
|
||||
// addSegmentationPresentation
|
||||
// setSegmentationVisibility
|
||||
// etc.
|
||||
```
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
id: 6-rtstruct
|
||||
title: RTSTRUCT
|
||||
sidebar_position: 6
|
||||
summary: Migration guide for RT Structure Set rendering in OHIF 3.9, explaining the transition from VTK-based rendering to SVG-based rendering for improved stability and speed while maintaining stack viewports instead of converting to volume viewports.
|
||||
---
|
||||
|
||||
|
||||
|
||||
# RTStructure Set has transitioned from VTK actors to SVG.
|
||||
|
||||
We have transitioned from VTK-based rendering to SVG-based rendering for RTStructure Set contours. This change should not require any modifications to your codebase. We anticipate improved stability and speed in our contour rendering.
|
||||
|
||||
As a result of this update, viewports rendering RTStructure Sets will no longer convert to volume viewports. Instead, they will remain as stack viewports.
|
||||
|
||||
|
||||
Read more in Pull Requests:
|
||||
- https://github.com/OHIF/Viewers/pull/4074
|
||||
- https://github.com/OHIF/Viewers/pull/4157
|
||||
@@ -0,0 +1,157 @@
|
||||
---
|
||||
title: UI
|
||||
summary: Migration guide for UI changes in OHIF 3.9, covering the new UI components from @ohif/ui-next, UINotificationService updates with Sonner integration, viewport pane Tailwind class changes, Header component refactoring, and managing both UI libraries.
|
||||
---
|
||||
|
||||
## New Components
|
||||
|
||||
You can explore our new playground at `docs.ohif.org/ui` to see the latest components and their properties. We haven't provided a migration guide yet because the old components are still available. Feel free to update your codebase, including custom extensions and UI, to use the new Button, Dropdown, Icons, and other new components from `@ohif/ui-next`. The old methods (importing from `@ohif/ui`) will continue to work for now. However, the new components have a slightly different API, and we plan to deprecate the old components in a future release, as we see the new ones as the future of OHIF.
|
||||
|
||||
|
||||
|
||||
|
||||
## `UINotificationService`
|
||||
|
||||
|
||||
We've switched our custom notification service to the Sonner component from https://sonner.emilkowal.ski/
|
||||
|
||||
### 1. Toast Positions (Kebab-Case)
|
||||
|
||||
Toast positions are now defined using kebab-case instead of camelCase. For instance, `topRight` becomes `top-right`, `bottomRight` becomes `bottom-right`, etc. Ensure your position strings are updated accordingly.
|
||||
|
||||
**Old API:**
|
||||
|
||||
```js
|
||||
uiNotificationService.show({
|
||||
title: 'My Title',
|
||||
message: 'My Message',
|
||||
duration: 3000,
|
||||
position: 'topRight',
|
||||
type: 'error',
|
||||
autoClose: true,
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
**New API:**
|
||||
|
||||
```js
|
||||
uiNotificationService.show({
|
||||
title: 'My Title',
|
||||
message: 'My Message',
|
||||
duration: 3000,
|
||||
position: 'top-right', // Note the change to kebab-case
|
||||
type: 'error',
|
||||
autoClose: true,
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Promise Support
|
||||
|
||||
The `show()` method now supports promises, enabling you to display loading notifications and automatically update them based on the promise's resolution or rejection. This significantly simplifies asynchronous operation feedback.
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
const myPromise = someAsyncOperation();
|
||||
const notificationId = uiNotificationService.show({
|
||||
title: 'Loading Data',
|
||||
message: 'Fetching data from server...',
|
||||
type: 'info',
|
||||
promise: myPromise,
|
||||
promiseMessages: {
|
||||
loading: 'Fetching...',
|
||||
success: (data) => `Data loaded: ${data.length} items`, // Access promise result
|
||||
error: (error) => `Failed to load data: ${error.message}`, // Access error details
|
||||
},
|
||||
});
|
||||
// Optionally hide notification manually if needed
|
||||
// myPromise.finally(() => uiNotificationService.hide(notificationId));
|
||||
```
|
||||
|
||||
### 3. `hide()` API Change
|
||||
|
||||
The `hide()` method no longer takes an options object. It only accepts the notification ID as a string argument.
|
||||
|
||||
**Old API:**
|
||||
|
||||
```js
|
||||
uiNotificationService.hide({ id: notificationId });
|
||||
```
|
||||
|
||||
**New API:**
|
||||
|
||||
```js
|
||||
uiNotificationService.hide(notificationId);
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Viewport Pane Tailwindcss class
|
||||
|
||||
Previously, when targeting the viewport pane to add custom CSS, you likely used `group-hover:visible` with the viewportPane having a `group` class.
|
||||
|
||||
The naming was confusing as we added more groups, so we renamed it to `group/pane`. Now you can apply `group-hover/pane` for better clarity.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Header Component
|
||||
|
||||
|
||||
Header Component has been refactored in the @ohif/ui-next package.
|
||||
|
||||
|
||||
**Before**
|
||||
|
||||
|
||||
```js
|
||||
function Header({
|
||||
children,
|
||||
menuOptions,
|
||||
isReturnEnabled,
|
||||
onClickReturnButton,
|
||||
isSticky,
|
||||
WhiteLabeling,
|
||||
showPatientInfo,
|
||||
servicesManager,
|
||||
Secondary,
|
||||
appConfig,
|
||||
...props
|
||||
}: withAppTypes): ReactNode
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```js
|
||||
function Header({
|
||||
children,
|
||||
menuOptions,
|
||||
isReturnEnabled,
|
||||
onClickReturnButton,
|
||||
isSticky,
|
||||
WhiteLabeling,
|
||||
PatientInfo,
|
||||
Secondary,
|
||||
...props
|
||||
}: HeaderProps): ReactNode
|
||||
```
|
||||
|
||||
The `PatientInfo` component is now preferred, and the `showPatientInfo` prop has been removed. The previous method depended on `servicesManager`, which was cumbersome because the UI shouldn't need to interact with `servicesManager`.
|
||||
|
||||
All the DropDown and Icons are now in the @ohif/ui-next package.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## ui, ui-next configs
|
||||
|
||||
We currently have two component libraries that we plan to merge in the future, so we need to maintain both configurations. If your styles aren't applying correctly, ensure you update both `platform/ui-next/tailwind.config.js` and `platform/ui/tailwind.config.js`.
|
||||
|
||||
|
||||
### addIcon from ui-next
|
||||
|
||||
if you add custom icons, you may need to add them using a new `addIcon` utility which adds the icon to both `ui` and `ui-next`.
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
---
|
||||
title: Refactoring
|
||||
summary: Migration guide for refactored components in OHIF 3.9, including the move of PanelSegmentation from cornerstone-dicom-seg extension to cornerstone extension, centralization of dialog utilities, and improved customization ID structure for better modularity.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Panel Segmentation
|
||||
|
||||
is now moved from `@ohif/extension-cornerstone-dicom-seg` to `@ohif/extension-cornerstone`.
|
||||
|
||||
|
||||
The cornerstone extension now provides the panelSegmentation feature, which was previously part of the cornerstone-dicom-seg extension. This change is logical as panelSegmentation handles more than just DICOM. It can process various formats, including custom formats from the backend and potentially NIFTI format in the future.
|
||||
|
||||
|
||||
Before in your modes you were using
|
||||
|
||||
```js
|
||||
'@ohif/extension-cornerstone-dicom-seg.panelModule.panelSegmentation',
|
||||
```
|
||||
|
||||
|
||||
Now you should use it via
|
||||
|
||||
|
||||
```js
|
||||
'@ohif/extension-cornerstone.panelModule.panelSegmentation',
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `callInputDialog` and `colorPickerDialog` and `showLabelAnnotationPopup`
|
||||
|
||||
Due to the excessive number of `callInputDialog` instances, we centralized them. You can now import them from `@ohif/extension-default`.
|
||||
|
||||
|
||||
```js
|
||||
import { showLabelAnnotationPopup, callInputDialog, colorPickerDialog } from '@ohif/extension-default';
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## disableEditing
|
||||
|
||||
The configuration has moved from appConfig to allow more precise control over component disabling. To disable editing for segmentation and measurements, add the following settings:
|
||||
|
||||
|
||||
**Before: **
|
||||
|
||||
```js
|
||||
customizationService.addModeCustomizations([
|
||||
{
|
||||
id: 'segmentation.panel',
|
||||
disableEditing: true,
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
**Now **
|
||||
|
||||
```js
|
||||
customizationService.addModeCustomizations([
|
||||
// To disable editing in the SegmentationTable
|
||||
{
|
||||
id: 'panelSegmentation.disableEditing',
|
||||
disableEditing: true,
|
||||
},
|
||||
// To disable editing in the MeasurementTable
|
||||
{
|
||||
id: 'panelMeasurement.disableEditing',
|
||||
disableEditing: true,
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Customization Ids
|
||||
|
||||
The primary reason for this migration is to improve modularity and maintainability in configuration management, as we plan to focus more on the customization service in the near future.
|
||||
|
||||
**Before**
|
||||
|
||||
```js
|
||||
customizationService.addModeCustomizations([
|
||||
{
|
||||
id: 'segmentation.panel',
|
||||
segmentationPanelMode: 'expanded',
|
||||
addSegment: false,
|
||||
onSegmentationAdd: () => {
|
||||
commandsManager.run('createNewLabelmapFromPT');
|
||||
},
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
|
||||
**Now**
|
||||
|
||||
```js
|
||||
customizationService.addModeCustomizations([
|
||||
{
|
||||
id: 'panelSegmentation.tableMode',
|
||||
mode: 'expanded',
|
||||
},
|
||||
{
|
||||
id: 'panelSegmentation.onSegmentationAdd',
|
||||
onSegmentationAdd: () => {
|
||||
commandsManager.run('createNewLabelmapFromPT');
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
```
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: Other Changes
|
||||
summary: Migration guide for additional changes in OHIF 3.9, covering external library loading with browserImport function, the pluginConfig.json format for dynamic imports, and improvements to viewport navigation using ViewReference methods.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
|
||||
## External Libraries
|
||||
Some libraries are loaded via dynamic import. You can provide a global function
|
||||
`browserImport` the allows loading of dynamic imports without affecting the
|
||||
webpack build. This import looks like:
|
||||
|
||||
```html
|
||||
<script>
|
||||
function browserImportFunction(moduleId) {
|
||||
return import(moduleId);
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
and belongs in the root html file for your application.
|
||||
You then need to remove `dependencies` on the external import, and add a reference
|
||||
to the external import in your `pluginConfig.json` file.
|
||||
|
||||
### Example plugin config for `dicom-microscopy-viewer`
|
||||
The example below imports the `dicom-microscopy-viewer` for use as an external
|
||||
dependency. The example is part of the default `pluginConfig.json` file.
|
||||
|
||||
```json
|
||||
"public": [
|
||||
{
|
||||
"directory": "./platform/public"
|
||||
},
|
||||
{
|
||||
"packageName": "dicom-microscopy-viewer",
|
||||
"importPath": "/dicom-microscopy-viewer/dicomMicroscopyViewer.min.js",
|
||||
"globalName": "dicomMicroscopyViewer",
|
||||
"directory": "./node_modules/dicom-microscopy-viewer/dist/dynamic-import"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
This defines two directory modules, whose contents are copied unchanged to the
|
||||
output build directory. It then defines the `dicom-microscopy-viewer` using
|
||||
the `packageName` element as being a module which is imported dynamically.
|
||||
Then, the import path passed into the browserImportFunction above is
|
||||
specified, and then how to access the import itself, via the `window.dicomMicroscopyViewer`
|
||||
global name reference.
|
||||
|
||||
### Referencing External Imports
|
||||
The appConfig either defines or has a default peerImport function which can be
|
||||
used to load references to the modules defined in the pluginConfig file. See
|
||||
the example in `init.tsx` for the cornerstone extension for how this is passed
|
||||
into CS3D for loading the whole slide imaging library.
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Use of ViewReference for navigation
|
||||
When navigating to measurements and storing/remembering navigation positions,
|
||||
the `viewport.getViewReference` is used to get a position, and `viewport.isReferenceViewable`
|
||||
used to check if a reference can be applied, and finally `viewport.setViewReference` to
|
||||
navigate to a view. Note that this changes the behaviour of navigation between
|
||||
MPR and Stack viewports, and also enables navigation of video and microscopy
|
||||
viewports in CS3D. This can cause some unexpected behaviour depending on how the
|
||||
frame of reference values are configured to allow for navigation.
|
||||
|
||||
The isReferenceViewable is used to determine when a view or measurement can be
|
||||
shown on a given view. For stack versus volume viewports, this can cause unexpected
|
||||
behaviour to be seen depending on how the view reference was fetched.
|
||||
|
||||
### `getViewReference` with `forFrameOfReference`
|
||||
When a view reference is fetched with the for frame of reference flag set to true,
|
||||
a reference will be returned which can be displayed on any viewport containing
|
||||
the same frame of reference and encompassing the given FOR and able to display the required
|
||||
orientation. Without this flag, a view reference is returned which will be
|
||||
displayed on a stack with the given image id, or a volume containing said image id
|
||||
or the specified volume.
|
||||
|
||||
### `isReferenceViewable` with navigation and/or orientation
|
||||
The is reference viewable will return false unless the given reference is directly
|
||||
viewable in the viewport as is. However, it can be passed various flags to determine
|
||||
whether the reference could be displayed if the viewport was modified in various ways,
|
||||
for example, by changing the position or orientation of the viewport. This allows
|
||||
checking for degrees of closeness so that the correct viewport can be chosen.
|
||||
|
||||
Note that this may result in displaying a measurement from one viewport on a completely
|
||||
different viewport, for example, showing a Probe tool from the stack viewport on
|
||||
an MPR view.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
id: 3p8-to-3p9
|
||||
title: 3.8 -> 3.9
|
||||
sidebar_position: 2
|
||||
summary: Migration guide for upgrading from OHIF 3.8 to 3.9, covering segmentation architecture changes, renamings, data sources, measurements, viewport action corners, state sync service, RT structure improvements, UI changes, and other refactorings.
|
||||
---
|
||||
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
|
||||
|
||||
## Migration Guide Sections
|
||||
|
||||
<DocCardList items={useCurrentSidebarCategory().items}/>
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
---
|
||||
title: Commands
|
||||
summary: Migration guide for commands in OHIF 3.10, covering the replacement of deleteMeasurement with removeMeasurement and setSourceViewportForReferenceLinesTool with the more generic setViewportForToolConfiguration command.
|
||||
---
|
||||
|
||||
|
||||
# Commands
|
||||
|
||||
## Measurements
|
||||
|
||||
* The `deleteMeasurement` command has been completely removed from the codebase It has been replaced by `removeMeasurement` command with enhanced functionality
|
||||
|
||||
1. Replace any usage of `deleteMeasurement` with `removeMeasurement` in your custom code
|
||||
|
||||
```diff
|
||||
- commandsManager.run('deleteMeasurement', { uid });
|
||||
+ commandsManager.run('removeMeasurement', { uid });
|
||||
```
|
||||
|
||||
|
||||
## Important Notes:
|
||||
|
||||
* This change is part of a broader refactoring of the measurement system to provide more consistent and powerful APIs
|
||||
* The new command structure follows a more consistent pattern throughout the codebase
|
||||
* If you were using `measurementServiceSource.remove(uid)` directly, you should now use `measurementService.remove(uid)` instead
|
||||
* The changes affect both UI components and any extensions that integrate with the measurement system
|
||||
* Removal functionality now works with both individual UIDs and arrays of UIDs for batch operations
|
||||
|
||||
|
||||
|
||||
## `setSourceViewportForReferenceLinesTool`
|
||||
|
||||
* `setSourceViewportForReferenceLinesTool` has been replaced by the more generic `setViewportForToolConfiguration`
|
||||
* The new API allows configuration of any tool, not just the ReferenceLinesTool
|
||||
* Tool name is now a required parameter, not hardcoded to ReferenceLinesTool
|
||||
|
||||
## Migration Steps:
|
||||
|
||||
1. Update command references from `setSourceViewportForReferenceLinesTool` to `setViewportForToolConfiguration`
|
||||
|
||||
```diff
|
||||
- {
|
||||
- commandName: 'setSourceViewportForReferenceLinesTool',
|
||||
- context: 'CORNERSTONE',
|
||||
- }
|
||||
|
||||
+ {
|
||||
+ commandName: 'setViewportForToolConfiguration',
|
||||
+ commandOptions: {
|
||||
+ toolName: 'ReferenceLines'
|
||||
+ },
|
||||
+ context: 'CORNERSTONE',
|
||||
+ }
|
||||
```
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
title: General
|
||||
summary: General migration changes from OHIF 3.9 to 3.10, including Node.js version update, HTML template modifications, bundled Google Fonts, docs updates, faster development builds with rsbuild, and webpack configuration changes for AI segmentation support.
|
||||
---
|
||||
|
||||
## Node.js Version Update
|
||||
|
||||
We have updated the recommended Node.js version from `18.16.1` to `20.9.0`. Please ensure your development and build environments are using Node.js `20.9.0` or later.
|
||||
|
||||
## HTML Template Update
|
||||
We have modified the `template.html` file so if you are using a custom template, you will need to update it.
|
||||
|
||||
Here are the key changes needed in the migration:
|
||||
|
||||
1. Added `window.PUBLIC_URL` declaration:
|
||||
```javascript
|
||||
window.PUBLIC_URL = '<%= PUBLIC_URL %>';
|
||||
```
|
||||
|
||||
Was added before the `<!-- EXTENSIONS -->` comment block.
|
||||
|
||||
## Bundled Google Fonts
|
||||
|
||||
Previously, OHIF relied on the Google Fonts API to load the required fonts. To improve privacy, performance, and offline availability, we now bundle the necessary font files as assets within the application. No explicit action is required for this change unless you were specifically overriding or manipulating the font loading process.
|
||||
|
||||
You **might** need to update your `module` rule in your webpack
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(woff|woff2|eot|ttf|otf)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## OHIF Docs
|
||||
|
||||
OHIF platform/docs is no longer part of the workspace.
|
||||
|
||||
- Builds are faster for 99.99% of users since only maintainers need to run the docs development.
|
||||
|
||||
If you need to run the docs website locally, you must install it first, as it is not installed by default.
|
||||
|
||||
Before:
|
||||
```bash
|
||||
yarn run dev
|
||||
```
|
||||
|
||||
After:
|
||||
```bash
|
||||
yarn install
|
||||
yarn run dev
|
||||
```
|
||||
|
||||
|
||||
## Experimental Fast Development Build (`dev:fast`)
|
||||
|
||||
We have introduced a new experimental command, `yarn run dev:fast`, which utilizes `rsbuild` and its Rust-based approach to significantly speed up development server start and hot module replacement times.
|
||||
|
||||
Here's a comparison of the performance improvements:
|
||||
|
||||
| Scenario | Load Time | Update Time |
|
||||
| -------- | ----------- | ----------- |
|
||||
| Before | ~12 seconds | ~5 seconds |
|
||||
| After | ~4 seconds | ~1 second |
|
||||
|
||||
**Note:** This command is currently experimental. While functional, it may not yet support all features or configurations of the standard `yarn run dev` command. We are continuing to develop and test this feature.
|
||||
|
||||
|
||||
## Webpack Configuration
|
||||
|
||||
To use our new Segmentation AI models, you'll need `onnxruntime-web`. If you're using a custom webpack configuration, make sure to update it with the new `copyPlugin` to copy the `onnxruntime-web` `dist` folder to your output directory.
|
||||
|
||||
|
||||
```javascript
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: '../../../node_modules/onnxruntime-web/dist',
|
||||
to: `${DIST_DIR}/ort`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
Also, if you're running the viewer from a sub-route, you'll need to update the `dicom-microscopy-viewer` package in the dev server, so it knows where to load the assets from.
|
||||
|
||||
|
||||
```javascript
|
||||
devServer: {
|
||||
proxy: {
|
||||
'/dicom-microscopy-viewer': {
|
||||
target: 'http://localhost:3000',
|
||||
pathRewrite: {
|
||||
'^/dicom-microscopy-viewer': `/${PUBLIC_URL}/dicom-microscopy-viewer`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
:::note
|
||||
Also, the `writePluginImportFile` function has been updated so that the dicom-microscopy-viewer package works correctly with the new webpack configuration. If you have a custom `writePluginImportFile` function, please update it to match.
|
||||
:::
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
title: Hotkeys
|
||||
summary: Migration guide for hotkeys management in OHIF 3.10, explaining the transition from defining hotkeys in mode factory to using the customizationService, with examples of replacing, adding, and modifying hotkey bindings.
|
||||
---
|
||||
|
||||
|
||||
## Key Changes:
|
||||
|
||||
* Hotkeys are no longer defined in mode factory via `hotkeys: [...hotkeys.defaults.hotkeyBindings]`
|
||||
* Hotkeys are now managed through the `customizationService` under the key `ohif.hotkeyBindings`
|
||||
* Default hotkeys are set automatically and can be customized using the customization service
|
||||
* User-defined hotkey preferences are now stored in a new format in localStorage
|
||||
* The `HotkeysManager` has undergone significant updates including better handling of defaults, key persistence, and cleanup
|
||||
|
||||
## Migration Steps:
|
||||
|
||||
### 1. Remove hotkeys array from mode factory definition
|
||||
|
||||
**Before:**
|
||||
```diff
|
||||
- function modeFactory({ modeConfiguration }) {
|
||||
- return {
|
||||
- id: 'basic',
|
||||
- // ... other configuration
|
||||
- hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
||||
- };
|
||||
- }
|
||||
```
|
||||
|
||||
**After:**
|
||||
```diff
|
||||
+ function modeFactory({ modeConfiguration }) {
|
||||
+ return {
|
||||
+ id: 'basic',
|
||||
+ // ... other configuration
|
||||
+ // No hotkeys array necessary
|
||||
+ };
|
||||
+ }
|
||||
```
|
||||
|
||||
|
||||
### 2. Set custom hotkeys using the customization service
|
||||
|
||||
There are several methods to modify hotkeys using the customization service:
|
||||
|
||||
#### a. Completely replace all hotkeys using `$set`:
|
||||
|
||||
```diff
|
||||
+ onModeEnter: function ({ servicesManager }) {
|
||||
+ const { customizationService } = servicesManager.services;
|
||||
+ customizationService.setCustomizations({
|
||||
+ 'ohif.hotkeyBindings': {
|
||||
+ $set: [
|
||||
+ {
|
||||
+ commandName: 'setToolActive',
|
||||
+ commandOptions: { toolName: 'Zoom' },
|
||||
+ label: 'Zoom',
|
||||
+ keys: ['z'],
|
||||
+ isEditable: true,
|
||||
+ },
|
||||
+ ],
|
||||
+ },
|
||||
+ });
|
||||
```
|
||||
|
||||
#### b. Add new hotkeys using `$push`:
|
||||
|
||||
```diff
|
||||
+ onModeEnter: function ({ servicesManager }) {
|
||||
+ const { customizationService } = servicesManager.services;
|
||||
+ customizationService.setCustomizations({
|
||||
+ 'ohif.hotkeyBindings': {
|
||||
+ $push: [
|
||||
+ {
|
||||
+ commandName: 'myCustomCommand',
|
||||
+ label: 'My Custom Function',
|
||||
+ keys: ['ctrl+m'],
|
||||
+ isEditable: true,
|
||||
+ },
|
||||
+ ],
|
||||
+ },
|
||||
+ });
|
||||
+}
|
||||
```
|
||||
|
||||
### 4. Update configuration file if you were setting window.config.hotkeys
|
||||
|
||||
If you were previously defining hotkeys in your window.config.js file, it was not really
|
||||
taken into account. So you can safely remove it now.
|
||||
|
||||
**Before:**
|
||||
```diff
|
||||
- window.config = {
|
||||
- // ...other config
|
||||
- hotkeys: [
|
||||
- {
|
||||
- commandName: 'incrementActiveViewport',
|
||||
- label: 'Next Viewport',
|
||||
- keys: ['right'],
|
||||
- },
|
||||
- // ...more hotkeys
|
||||
- ],
|
||||
- };
|
||||
```
|
||||
|
||||
**After:**
|
||||
```diff
|
||||
+ window.config = {
|
||||
+ // ...other config
|
||||
+ };
|
||||
```
|
||||
|
||||
### 5. Be aware that user preferences are now handled differently
|
||||
|
||||
The new system automatically handles user-preferred hotkey mappings:
|
||||
|
||||
- User hotkey preferences are stored in `localStorage` under the key `user-preferred-keys`
|
||||
- The format is a hash-based mapping rather than a full array of definitions
|
||||
- There's a migration utility that converts old preferences to the new format
|
||||
- You don't need to manually handle this, but be aware of it if you're accessing localStorage directly
|
||||
|
||||
|
||||
## Benefits of the Change
|
||||
|
||||
1. **Consistent API**: Hotkeys now follow the same customization pattern as other OHIF features
|
||||
2. **More flexible**: Easier to modify specific hotkeys without replacing the entire set
|
||||
3. **Better user preferences**: User customizations are better preserved and migrated
|
||||
4. **Runtime updates**: Hotkeys can be modified at runtime through the customization service
|
||||
5. **Improved cleanup**: Better lifecycle management of hotkey bindings
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: routerBaseName
|
||||
summary: Migration guide for router configuration in OHIF 3.10, covering the updated default behavior of routerBasename and its interaction with PUBLIC_URL, with scenario-based examples for both root and subpath hosting.
|
||||
---
|
||||
|
||||
|
||||
## Migration Guide: Router Configuration (`routerBasename` and `PUBLIC_URL`)
|
||||
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* **`routerBasename` Default Value:** The recommended default value for `routerBasename` in the configuration file (`window.config`) has changed from `'/'` to `null`.
|
||||
* **New Default Behavior:** If `routerBasename` is set to `null` (or is not defined) in the configuration, the application's base path will now automatically default to the value determined by `PUBLIC_URL`.
|
||||
* **Clarified Roles:**
|
||||
* `routerBasename`: Explicitly defines the base path for the application's routes (e.g., `/viewer`). If `null`, it defaults to `PUBLIC_URL`.
|
||||
* `PUBLIC_URL`: Primarily defines the URL prefix from which static assets (like JavaScript files, CSS, images) are loaded. It defaults to `/` if not set.
|
||||
|
||||
|
||||
:::info
|
||||
see the comprehensive guide [here](/deployment/custom-url-access)
|
||||
:::
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Review `routerBasename` Configuration:**
|
||||
Locate the `routerBasename` setting within your application configuration file (typically found in `platform/app/public/config/*.js`).
|
||||
|
||||
2. **Update `routerBasename` Based on Hosting Scenario:**
|
||||
|
||||
* **Scenario A: Hosting at the Root (`/`)**
|
||||
If your application is served from the root domain (e.g., `https://example.com/`), it's recommended to update `routerBasename` to `null`. This aligns the routing base with the default asset loading path (`PUBLIC_URL` which defaults to `/`).
|
||||
|
||||
*Example Diff:*
|
||||
```diff
|
||||
window.config = {
|
||||
- routerBasename: '/',
|
||||
+ routerBasename: null,
|
||||
// ... other config options
|
||||
showStudyList: true,
|
||||
dataSources: [ /* ... */ ],
|
||||
```
|
||||
*Explanation:* Setting `routerBasename: null` leverages the new default behavior. The router will use `/` as its base because `PUBLIC_URL` defaults to `/`.
|
||||
|
||||
* **Scenario B: Hosting at a Subpath (e.g., `/viewer/`)**
|
||||
If your application is served from a subpath (e.g., `https://example.com/viewer/`), you should ensure `routerBasename` is explicitly set to that path.
|
||||
|
||||
*Example (No Change Needed if Already Correct):*
|
||||
```diff
|
||||
window.config = {
|
||||
// No change needed if already set correctly for subpath hosting
|
||||
routerBasename: '/viewer',
|
||||
// ... other config options
|
||||
showStudyList: true,
|
||||
dataSources: [ /* ... */ ],
|
||||
```
|
||||
*Explanation:* Explicitly setting `routerBasename` ensures the application's internal routing works correctly under the `/viewer/` path.
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
title: Customization Service
|
||||
summary: Migration guide for OHIF's Customization Service from 3.9 to 3.10, covering unified customization getters, simplified registration, new commands, renamed customizations, and updated patterns for modifying UI components.
|
||||
---
|
||||
|
||||
# CustomizationService
|
||||
|
||||
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
|
||||
1. **Unified Customization Getter:**
|
||||
- The `getCustomization` method now uniformly retrieves customizations, prioritizing `global`, then `mode`, and finally `default` customizations.
|
||||
- The `defaultValue` parameter in `getCustomization` is no longer used for setting defaults. It simply returns if no customization is found.
|
||||
- The methods `getModeCustomization` and `getGlobalCustomization` are deprecated.
|
||||
|
||||
2. **Simplified Customization Registration:**
|
||||
- The `customizationType` property in customization definitions is renamed to `inheritsFrom`.
|
||||
- The `merge` property in customization definitions is removed. Instead, a customization is merged using the helper methods. The basic update commands are listed in the table below, and you can learn more about the helper methods [here](../../../platform/services/customization-service/customizationService.md).
|
||||
|
||||
| Command | Description | Example |
|
||||
| :------- | :---------------------------------------- | :------------------------------------------------ |
|
||||
| `$set` | Replace a value entirely | Replace a list or object |
|
||||
| `$push` | Append items to an array | Add to the end of a list |
|
||||
| `$unshift` | Prepend items to an array | Add to the start of a list |
|
||||
| `$splice` | Insert, remove, or replace at specific index | Modify specific indices in a list |
|
||||
| `$merge` | Update specific fields in an object | Change a subset of fields |
|
||||
| `$apply` | Compute the new value dynamically | Apply a function to transform values |
|
||||
| `$filter` | Find and update specific items in arrays | Target nested structures based on matching criteria |
|
||||
|
||||
|
||||
3. **New `$transform` command:**
|
||||
- If you were using the `transform` command, you should now use the `$transform` command. Just a simple rename to make it more consistent with the other commands.
|
||||
|
||||
|
||||
5. **Renamed `CornerstoneOverlay` customizations:**
|
||||
- The `cornerstoneOverlay` customizations (`cornerstoneOverlayTopLeft`, `cornerstoneOverlayTopRight`, `cornerstoneOverlayBottomLeft`, `cornerstoneOverlayBottomRight`) have been renamed to `viewportOverlay.topLeft`, `viewportOverlay.topRight`, `viewportOverlay.bottomLeft`, and `viewportOverlay.bottomRight`. See dedicated page for customizing viewport overlays [here](../../../platform/services/customization-service/viewportOverlay.md).
|
||||
|
||||
6. **Renamed `customRoutes`:**
|
||||
- The `customRoutes` customization is renamed to `routes.customRoutes`.
|
||||
|
||||
7. **`contextMenu` customization:**
|
||||
- The `contextMenu` customization now uses the `inheritsFrom` property to inherit from other context menus, previously it was called `customizationType`
|
||||
|
||||
8. **New `immutability-helper` dependency:**
|
||||
The `immutability-helper` library is now used for merging customizations. If you encounter an error related to it, you'll need to install it - though OHIF should really handle the installation for you, so this is pretty much just a heads up.
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Replace `getModeCustomization` and `getGlobalCustomization` with `getCustomization`:**
|
||||
|
||||
- **Before:**
|
||||
|
||||
```javascript
|
||||
const tools = customizationService.getModeCustomization(
|
||||
'cornerstone.overlayViewportTools'
|
||||
)?.tools;
|
||||
const globalValue = customizationService.getGlobalCustomization('someGlobalKey');
|
||||
```
|
||||
|
||||
- **After:**
|
||||
|
||||
```javascript
|
||||
const tools = customizationService.getCustomization('cornerstone.overlayViewportTools');
|
||||
const globalValue = customizationService.getCustomization('someGlobalKey');
|
||||
```
|
||||
|
||||
|
||||
:::note
|
||||
The returned value is the actual customization value, not an object that needs to be broken down.
|
||||
:::
|
||||
|
||||
2. **Update Customization Definitions:**
|
||||
- We've moved away from using random items in the customization definition, and now we use the `id` property to identify the customization as a value. Previously, it was referred to as `value`, `values`, and so on, but now an `id` is used to reference the customization. This approach really simplifies things - when you need to grab the customization, you can just use the `id` to get it, and you don't have to bother with destructuring the value from the object.
|
||||
|
||||
|
||||
|
||||
|
||||
**Example: Customizing a Panel**
|
||||
|
||||
**Before (v3.9):**
|
||||
|
||||
```javascript
|
||||
// the default value was hardcoded inside the panel itself - bad idea!
|
||||
// default was given in the panel itself
|
||||
|
||||
// PanelSegmentation.tsx
|
||||
|
||||
// Retrieve the onSegmentationAdd customization
|
||||
const { onSegmentationAdd } = customizationService.getCustomization(
|
||||
'PanelSegmentation.onSegmentationAdd',
|
||||
{
|
||||
id: 'segmentation.onSegmentationAdd',
|
||||
onSegmentationAdd: handlers.onSegmentationAdd,
|
||||
}
|
||||
);
|
||||
|
||||
// Retrieve the disableEditing customization
|
||||
const { disableEditing } = customizationService.getCustomization(
|
||||
'PanelSegmentation.disableEditing',
|
||||
{
|
||||
id: 'default.disableEditing',
|
||||
disableEditing: false,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
// mode was customizing it via
|
||||
customizationService.addModeCustomizations([
|
||||
{
|
||||
id: 'PanelSegmentation.tableMode',
|
||||
mode: 'expanded',
|
||||
},
|
||||
{
|
||||
id: 'PanelSegmentation.showAddSegment',
|
||||
showAddSegment: false,
|
||||
},
|
||||
]);
|
||||
|
||||
```
|
||||
|
||||
**After (v3.10):**
|
||||
|
||||
```javascript
|
||||
// cornerstone extension getCustomizationModule
|
||||
// centralized customization location for all extensions - good!
|
||||
function getCustomizationModule() {
|
||||
return [
|
||||
{
|
||||
name: 'default',
|
||||
value: {
|
||||
'panelSegmentation.disableEditing': false,
|
||||
'panelSegmentation.showAddSegment': true,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// inside panelSegmentation.tsx
|
||||
const disableEditing = customizationService.getCustomization('panelSegmentation.disableEditing');
|
||||
const showAddSegment = customizationService.getCustomization('panelSegmentation.showAddSegment');
|
||||
|
||||
|
||||
// mode can customize it via $ operators for mode customizations
|
||||
customizationService.setCustomizations({
|
||||
'panelSegmentation.disableEditing': { $set: true },
|
||||
'panelSegmentation.showAddSegment': { $set: false },
|
||||
});
|
||||
|
||||
|
||||
//or via configuration for global customizations
|
||||
window.config = {
|
||||
// rest of config
|
||||
customizationService: [
|
||||
{
|
||||
'panelSegmentation.disableEditing': {
|
||||
$set: true, // Disables editing of segmentations in the panel
|
||||
},
|
||||
},
|
||||
],
|
||||
// rest of config
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
|
||||
**Example: Updating a Customization**
|
||||
|
||||
Let's say you have a customization in v3.9 that adds a custom overlay item to the top-left corner:
|
||||
|
||||
**Before (v3.9):**
|
||||
|
||||
```javascript
|
||||
// In your mode's onModeEnter
|
||||
customizationService.addModeCustomizations([
|
||||
{
|
||||
id: 'cornerstoneOverlayTopLeft',
|
||||
items: [
|
||||
{
|
||||
id: 'myCustomOverlay',
|
||||
customizationType: 'ohif.overlayItem',
|
||||
attribute: 'PatientName',
|
||||
label: 'Patient:',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
**After (v3.10):**
|
||||
|
||||
```javascript
|
||||
// In your mode's onModeEnter or elsewhere
|
||||
customizationService.setCustomizations({
|
||||
'viewportOverlay.topLeft': {
|
||||
$push: [
|
||||
{
|
||||
id: 'myCustomOverlay',
|
||||
inheritsFrom: 'ohif.overlayItem',
|
||||
attribute: 'PatientName',
|
||||
label: 'Patient:',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
```
|
||||
**Example: Customizing viewport action Menu**
|
||||
|
||||
**Before (v3.9):**
|
||||
|
||||
```javascript
|
||||
// In your configuration for global customizations
|
||||
window.config = {
|
||||
// rest of config
|
||||
addWindowLevelActionMenu: true,
|
||||
};
|
||||
```
|
||||
|
||||
**After (v3.10):**
|
||||
|
||||
```javascript
|
||||
// you can now handle each action menu item (windowLevelActionMenu and segmentationOverlay) separately
|
||||
|
||||
// cornerstone extension getCustomizationModule
|
||||
function getCustomizationModule() {
|
||||
return [
|
||||
{
|
||||
name: 'default',
|
||||
value: {
|
||||
'viewportActionMenu.windowLevelActionMenu': {
|
||||
enabled: true,
|
||||
location: viewportActionCornersService.LOCATIONS.topRight,
|
||||
},
|
||||
'viewportActionMenu.segmentationOverlay': {
|
||||
enabled: true,
|
||||
location: viewportActionCornersService.LOCATIONS.topRight,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Accessing customizations within your component (e.g., OHIFCornerstoneViewport.tsx)
|
||||
const windowLevelActionMenu = customizationService.getCustomization('viewportActionMenu.windowLevelActionMenu');
|
||||
const segmentationOverlay = customizationService.getCustomization('viewportActionMenu.segmentationOverlay');
|
||||
|
||||
// Modifying customizations at runtime, for example, in your mode's onModeEnter
|
||||
customizationService.setCustomizations({
|
||||
'viewportActionMenu.windowLevelActionMenu': {
|
||||
$set: {
|
||||
enabled: false,
|
||||
location: viewportActionCornersService.LOCATIONS.bottomLeft,
|
||||
},
|
||||
},
|
||||
'viewportActionMenu.segmentationOverlay': {
|
||||
$set: {
|
||||
enabled: true,
|
||||
location: viewportActionCornersService.LOCATIONS.topLeft,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Alternatively, setting global customizations via configuration
|
||||
window.config = {
|
||||
// rest of config
|
||||
customizationService: [
|
||||
{
|
||||
'viewportActionMenu.windowLevelActionMenu': {
|
||||
$set: {
|
||||
enabled: false,
|
||||
location: 1,
|
||||
},
|
||||
},
|
||||
'viewportActionMenu.segmentationOverlay': {
|
||||
$set: {
|
||||
enabled: true,
|
||||
location: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
// rest of config
|
||||
};
|
||||
```
|
||||
|
||||
**Note:**
|
||||
|
||||
- The `customizationType` is replaced with `inheritsFrom`.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Renaming
|
||||
|
||||
To keep our customization system consistent, you should be aware of a few key renaming conventions. We now follow a straightforward naming convention for customizations: `scopeName.customizationItem`.
|
||||
|
||||
|
||||
|
||||
| Customization Key (Old) | Customization Key (New) | Description |
|
||||
| :------------------------------------------ | :------------------------------------------- | :-------------------------------------------------------------------------- |
|
||||
| `PanelMeasurement.disableEditing` | `panelMeasurement.disableEditing` | Disables editing measurements in the Measurement Panel and after SR hydration. |
|
||||
| `PanelSegmentation.CustomDropdownMenuContent` | `panelSegmentation.customDropdownMenuContent` | Custom content for the dropdown menu in the Segmentation Panel. |
|
||||
| `PanelSegmentation.disableEditing` | `panelSegmentation.disableEditing` | Disables editing segmentations in the Segmentation Panel. |
|
||||
| `PanelSegmentation.showAddSegment` | `panelSegmentation.showAddSegment` | Controls visibility of the "Add Segment" button in the Segmentation Panel. |
|
||||
| `PanelSegmentation.onSegmentationAdd` | `panelSegmentation.onSegmentationAdd` | Custom function to execute when a new segmentation is added. |
|
||||
| `PanelSegmentation.tableMode` | `panelSegmentation.tableMode` | Controls the table mode (collapsed/expanded) in the Segmentation Panel. |
|
||||
| `PanelSegmentation.readableText` | `panelSegmentation.readableText` | Custom readable text labels for the Segmentation Panel. |
|
||||
| `PanelStudyBrowser.studyMode` | `studyBrowser.studyMode` | Controls the study mode (all/primary/recent) in the Study Browser Panel. |
|
||||
| `customRoutes` | `routes.customRoutes` | Defines custom routes for the application. |
|
||||
| `cornerstoneOverlayTopLeft` | `viewportOverlay.topLeft` | Custom overlay items for the top-left corner of the viewport. |
|
||||
| `cornerstoneOverlayTopRight` | `viewportOverlay.topRight` | Custom overlay items for the top-right corner of the viewport. |
|
||||
| `cornerstoneOverlayBottomLeft` | `viewportOverlay.bottomLeft` | Custom overlay items for the bottom-left corner of the viewport. |
|
||||
| `cornerstoneOverlayBottomRight` | `viewportOverlay.bottomRight` | Custom overlay items for the bottom-right corner of the viewport. |
|
||||
| (New) | `viewportActionMenu.windowLevelActionMenu` | Controls the display and the location of the window level action menu in the viewport. |
|
||||
| (New) | `viewportActionMenu.segmentationOverlay` | Controls the display and the location of segmentation overlays in the viewport. |
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: Introduction
|
||||
position: 1
|
||||
summary: Introduction to UI changes in OHIF 3.10, covering the migration of the image viewer to the @ohif/ui-next library, a complete rewrite of UI components offering extensibility, accessibility, and a modern design, with guidance on updating custom panels.
|
||||
---
|
||||
|
||||
|
||||
## Introduction
|
||||
|
||||
The OHIF Viewer has two main parts: the worklist and the image viewer.
|
||||
|
||||
In version 3.10, we successfully migrated the image viewer to the `@ohif/ui-next` library. This is a complete rewrite of each component, offering extensibility, accessibility, and a modern look and feel.
|
||||
|
||||
The worklist is still using the old `@ohif/ui` library, but it will be migrated to `@ohif/ui-next` in a future release.
|
||||
|
||||
## Migration Guide
|
||||
|
||||
You'll generally need to update your custom panels to use the new `@ohif/ui-next` components.
|
||||
|
||||
The task is to find the direct mapping of the components you're using in your custom panels.
|
||||
|
||||
This guide will cover the migration for them.
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Tests
|
||||
summary: Migration guide for test updates in OHIF 3.10, covering changes to how tools are identified and tested, including the transition from class-based to data-attribute-based selection in Cypress tests.
|
||||
---
|
||||
|
||||
|
||||
## 1. ToolButton `data-active` Attribute
|
||||
|
||||
- **Previous**: Checked for a `bg-primary-light` class to determine if a tool was active.
|
||||
- **Now**: Check for the HTML attribute `data-active="true"`.
|
||||
|
||||
### Example
|
||||
|
||||
```diff
|
||||
- cy.get('@wwwcButton').should('have.class', 'bg-primary-light');
|
||||
+ cy.get('@wwwcButton').should('have.attr', 'data-active', 'true');
|
||||
```
|
||||
|
||||
## 2. Additional Data Attributes
|
||||
|
||||
- Each tool button now includes:
|
||||
- `data-tool="<toolId>"`
|
||||
- `data-active="<true|false>"`
|
||||
|
||||
This makes it easier to identify and assert on specific tools in the DOM.
|
||||
|
||||
### Example
|
||||
|
||||
```diff
|
||||
- <span data-cy={id}>
|
||||
+ <span
|
||||
+ data-cy={id}
|
||||
+ data-tool={id}
|
||||
+ data-active={isActive}
|
||||
+ >
|
||||
```
|
||||
|
||||
## 3. MPR Button Class Change
|
||||
|
||||
If you were targeting the `ohif-disabled` class, you need to update your tests to target the `cursor-not-allowed` class.
|
||||
|
||||
- **Previous**: `ohif-disabled`
|
||||
- **Now**: `cursor-not-allowed`
|
||||
|
||||
### Example
|
||||
|
||||
```diff
|
||||
- cy.get('[data-cy="MPR"]').should('have.class', 'ohif-disabled');
|
||||
+ cy.get('[data-cy="MPR"]').should('have.class', 'cursor-not-allowed');
|
||||
```
|
||||
|
||||
## 4. Removal of Stack Scroll Alias
|
||||
|
||||
- The `[data-cy="StackScroll"]` element is no longer reliably in the DOM at study load.
|
||||
- If needed, reintroduce or conditionally assert its presence when appropriate.
|
||||
|
||||
```diff
|
||||
- cy.get('[data-cy="StackScroll"]').as('stackScrollBtn');
|
||||
+ // Removed due to absence in DOM at study load
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
1. **Replace** all checks for `bg-primary-light` with `data-active="true"`.
|
||||
2. **Use** `data-tool` and `data-active` attributes for more robust DOM selection and assertions.
|
||||
3. **Update** MPR button checks to `cursor-not-allowed`.
|
||||
4. **Remove** the `[data-cy="StackScroll"]` alias (or only use it when the element is present).
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
---
|
||||
title: Colors
|
||||
summary: Migration guide for OHIF 3.10's new color system, explaining the transition from custom color names to a semantic color palette using CSS variables, with detailed mapping of old color classes to new Tailwind equivalents.
|
||||
---
|
||||
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* **New Color System:** Migration from custom color names (e.g., `aqua-pale`, `common-bright`) to a semantic color palette using CSS variables (e.g., `--primary`, `--secondary`, `--muted-foreground`). Tailwind classes like `text-primary`, `bg-secondary`, `text-muted-foreground` should now be used.
|
||||
* **Deprecated Color Classes:** Custom color classes like `text-aqua-pale` and `text-common-bright` have been removed and need replacement.
|
||||
* **Simplified State Classes:** Explicit hover/active state classes like `bg-primary-main`, `hover:bg-primary-light`, `active:text-primary-light` seem to be replaced by simpler base classes (e.g., `bg-primary`) where Tailwind's state variants (`hover:`, `active:`) modify the base color, or these states are handled by component variants (e.g., in a Button component).
|
||||
* **Component Abstraction:** Some styling, especially for interactive elements like buttons, has been abstracted into components (e.g., `ViewportActionButton`, UI library buttons) which use predefined variants (`default`, `secondary`, `ghost`) instead of manual style combinations.
|
||||
|
||||
:::note
|
||||
You can look at the set of colors in the [Color System](/colors-and-type)
|
||||
:::
|
||||
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Identify Deprecated Color Classes:**
|
||||
Search your codebase for the old custom color classes. The most common ones identified in the diff are:
|
||||
* `text-aqua-pale`
|
||||
* `text-common-bright`
|
||||
* `text-primary-active`
|
||||
* `bg-primary-main`
|
||||
* `hover:bg-primary-light`
|
||||
* `hover:text-black` (when used with primary hover states)
|
||||
* Potentially others using similar custom names.
|
||||
|
||||
2. **Replace with New Semantic Colors:**
|
||||
Update the deprecated classes with their likely semantic equivalents from the new system. Use the table below as a guide. **Note:** The exact replacement might depend on the specific context and desired visual outcome. Inspect the element in the browser after changes to ensure it matches the intended design.
|
||||
|
||||
| Old Class | Likely New Class(es) | Notes |
|
||||
| :------------------------ | :-------------------------------------------------------- | :-------------------------------------------------------------------- |
|
||||
| `text-aqua-pale` | `text-muted-foreground` | Used for less prominent text, now uses the muted foreground color. |
|
||||
| `text-common-bright` | `text-foreground` or `text-primary-foreground` | Likely the default bright text color. |
|
||||
| `text-primary-active` | `text-primary` or `text-highlight` | Simplified to the base primary color or potentially a highlight color. |
|
||||
| `bg-primary-main` | `bg-primary` | Simplified to the base primary background color. |
|
||||
| `text-white` (on dark bg) | `text-foreground` or `text-primary-foreground` | Use the standard foreground color for the theme. |
|
||||
| `bg-black` (for elements) | `bg-background`, `bg-popover`, `bg-card`, or `bg-muted` | Use semantic background colors depending on the element's role. |
|
||||
|
||||
3. **Update State Variants and Interactions:**
|
||||
Classes managing hover, active, or focus states have likely been simplified or moved into component variants.
|
||||
|
||||
* **Remove Explicit Hover/Active Styles:** Search for combinations like `hover:bg-primary-light`, `hover:text-black`, `active:text-primary-light` and remove them if the element now uses a base class like `bg-primary` or component variants. Tailwind's built-in state modifiers (`hover:`, `active:`) might handle this automatically with the new base colors, or component variants encapsulate these states.
|
||||
* **Use Component Variants:** If the element is now a component from a UI library (like `Button` from `@ohif/ui-next`), use its variants (`variant="default"`, `variant="secondary"`, `variant="ghost"`) instead of manual style combinations.
|
||||
|
||||
*Example Diff:*
|
||||
```diff
|
||||
- <div className="bg-primary-main hover:bg-primary-light text-white hover:text-black rounded p-2">
|
||||
- Action Button
|
||||
- </div>
|
||||
|
||||
+ <Button variant="default">
|
||||
+ Action Button
|
||||
+ </Button>
|
||||
```
|
||||
|
||||
*Example Diff:*
|
||||
```diff
|
||||
// Before (in _getStatusComponent.tsx)
|
||||
- <div
|
||||
- className="bg-primary-main hover:bg-primary-light ml-1 cursor-pointer rounded px-1.5 hover:text-black"
|
||||
- onMouseUp={onStatusClick}
|
||||
- >
|
||||
- {loadStr}
|
||||
- </div>
|
||||
|
||||
// After (in OHIFCornerstoneRTViewport.tsx using the abstracted component)
|
||||
+ <ViewportActionButton onInteraction={onStatusClick}>
|
||||
+ {loadStr}
|
||||
+ </ViewportActionButton>
|
||||
```
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
---
|
||||
title: Icons
|
||||
summary: Migration guide for OHIF 3.10's Icon component updates, covering the transition from @ohif/ui to @ohif/ui-next with new PascalCase naming conventions, legacy fallback options, and a comprehensive renaming table for all icons.
|
||||
---
|
||||
|
||||
## Migration Guide: Icon Component Updates
|
||||
|
||||
### General Overview
|
||||
|
||||
This migration involves changes to how icons are used within the OHIF platform. The core change is the move to a new icon component library, `@ohif/ui-next`, which provides more flexibility and a more consistent naming convention for icons.
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
1. **New Icon Library:** The primary change is the shift from using `<Icon>` from `@ohif/ui` to using the new `Icons` component from `@ohif/ui-next`.
|
||||
2. **`AbcDef` Naming Convention:** The new library uses a `AbcDef` (PascalCase) naming convention for the icons. For instance, `status-alert` is now `StatusAlert`.
|
||||
3. **Legacy Fallback:** To ease the transition, a legacy fallback has been provided using `Icons.ByName`. This allows you to continue using the old `name="status-alert"` format but is not the recommended way moving forward.
|
||||
4. **Direct Icon Component Access:** The recommended approach is to use `Icons.StatusAlert` instead of `<Icons.ByName name="status-alert"/>` this way will make code more clear and readable.
|
||||
|
||||
### Migration Strategies
|
||||
|
||||
You have two ways to approach the migration:
|
||||
|
||||
1. **Recommended Approach (Gradual Adoption):**
|
||||
* Start by updating your codebase to use the `Icons.Method` for the new icon naming convention.
|
||||
* For example, replace `<Icon name="status-alert" />` with `<Icons.StatusAlert />`.
|
||||
* This ensures your code is aligned with the new standard and provides optimal compatibility in the future.
|
||||
* This method can be rolled out in phases.
|
||||
|
||||
2. **Legacy Fallback Approach (Temporary):**
|
||||
* If a full migration is not immediately feasible, you can use the legacy fallback temporarily:
|
||||
* Replace `<Icon name="status-alert" />` with `<Icons.ByName name="status-alert" />`.
|
||||
* This option allows you to complete the migration with minimal disruption to the old code
|
||||
* However, it is highly recommended to move towards the `Icons.Method` approach to take advantage of all the new library offers and have a cleaner code base.
|
||||
|
||||
**Recommendation:** We strongly recommend using the *Recommended Approach* for a more maintainable and consistent codebase going forward.
|
||||
|
||||
### Specific Changes (Code Examples)
|
||||
|
||||
Here are some specific examples based on the diff you provided, illustrating both the legacy fallback and recommended approach:
|
||||
|
||||
**Example 1: Status Icons in `_getStatusComponent.tsx`**
|
||||
|
||||
**Old Code (`@ohif/ui`):**
|
||||
|
||||
```jsx
|
||||
import { Icon, Tooltip } from '@ohif/ui';
|
||||
|
||||
// ...
|
||||
case true:
|
||||
StatusIcon = () => <Icon name="status-alert" />;
|
||||
break;
|
||||
case false:
|
||||
StatusIcon = () => (
|
||||
<Icon
|
||||
className="text-aqua-pale"
|
||||
name="status-untracked"
|
||||
/>
|
||||
);
|
||||
break;
|
||||
//...
|
||||
|
||||
```
|
||||
|
||||
**Legacy Fallback Approach (`Icons.ByName`):**
|
||||
|
||||
```jsx
|
||||
import { Tooltip } from '@ohif/ui';
|
||||
import { Icons } from '@ohif/ui-next';
|
||||
|
||||
// ...
|
||||
case true:
|
||||
StatusIcon = () => <Icons.ByName name="status-alert" />;
|
||||
break;
|
||||
case false:
|
||||
StatusIcon = () => (
|
||||
<Icons.ByName
|
||||
className="text-aqua-pale"
|
||||
name="status-untracked"
|
||||
/>
|
||||
);
|
||||
break;
|
||||
//...
|
||||
```
|
||||
|
||||
**Recommended Approach (`Icons.StatusAlert`, `Icons.StatusUntracked`):**
|
||||
|
||||
```jsx
|
||||
import { Tooltip } from '@ohif/ui';
|
||||
import { Icons } from '@ohif/ui-next';
|
||||
|
||||
// ...
|
||||
case true:
|
||||
StatusIcon = () => <Icons.StatusAlert />;
|
||||
break;
|
||||
case false:
|
||||
StatusIcon = () => (
|
||||
<Icons.StatusUntracked
|
||||
className="text-aqua-pale"
|
||||
/>
|
||||
);
|
||||
break;
|
||||
//...
|
||||
```
|
||||
|
||||
|
||||
**Example 5: Icon usage in `WorkList.tsx`**
|
||||
|
||||
**Old Code (`@ohif/ui`):**
|
||||
|
||||
```jsx
|
||||
<Icon
|
||||
name="group-layers"
|
||||
```
|
||||
**Recommended Approach (`Icons.GroupLayers`):**
|
||||
|
||||
```jsx
|
||||
<Icons.GroupLayers
|
||||
```
|
||||
```jsx
|
||||
<Icons.ByName
|
||||
className="!h-[20px] !w-[20px] text-black"
|
||||
name={isValidMode ? 'launch-arrow' : 'launch-info'}
|
||||
/>
|
||||
```
|
||||
**Recommended Approach (`Icons.LaunchArrow`, `Icons.LaunchInfo`):**
|
||||
|
||||
```jsx
|
||||
isValidMode ? (
|
||||
<Icons.LaunchArrow className="!h-[20px] !w-[20px] text-black" />
|
||||
) : (
|
||||
<Icons.LaunchInfo className="!h-[20px] !w-[20px] text-black" />
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### Creating New Custom Icons
|
||||
|
||||
This section explains how to migrate your custom icons from the old SVG import method to the new React component-based system in `@ohif/ui-next`. The new approach improves consistency, allows for better tree-shaking, and provides type safety.
|
||||
|
||||
The process involves converting your existing SVG files into React components and then registering them.
|
||||
|
||||
#### 1. Convert SVG to a React Component
|
||||
|
||||
First, take your raw SVG file and convert it into a `.tsx` React functional component.
|
||||
|
||||
**Before: Raw SVG File**
|
||||
|
||||
Previously, you might have had a file like `Baseline.svg`:
|
||||
|
||||
```xml
|
||||
// Baseline.svg
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
|
||||
< some svg content>
|
||||
</svg>
|
||||
```
|
||||
|
||||
**After: React Icon Component**
|
||||
|
||||
Now, create a `.tsx` file that exports a React component. Note the following changes:
|
||||
- SVG attributes like `text-anchor` and `stroke-width` are converted to camel case (i.e. `textAnchor`, `strokeWidth`).
|
||||
- The component accepts `IconProps` and spreads them onto the root `<svg>` element.
|
||||
|
||||
```typescript
|
||||
// Baseline.tsx
|
||||
import React from 'react';
|
||||
import type { IconProps } from '../types';
|
||||
|
||||
export const Baseline = (props: IconProps) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" {...props}>
|
||||
< some svg content>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default Baseline;
|
||||
```
|
||||
|
||||
#### 2. Register the New Icon
|
||||
|
||||
After creating the component, you must register it with the `Icons` service from `@ohif/ui-next`. This is typically done in a centralized location where you initialize your UI components (for example, an extension's preRegistration is a good spot for this). You'll need a unique name for the icon, which can be managed with an enum for consistency.
|
||||
|
||||
```typescript
|
||||
// Example: in your setup/initialization code
|
||||
import { Icons, IconNameEnum } from '@ohif/ui-next';
|
||||
import Baseline from './sources/Baseline'; // Import your new icon component
|
||||
|
||||
// Add the icon to the registry
|
||||
Icons.addIcon(IconNameEnum.BASELINE, Baseline);
|
||||
```
|
||||
|
||||
By following these steps, your custom icon will be available for use throughout the application just like any of the default icons.
|
||||
|
||||
|
||||
|
||||
|
||||
### Detailed Renaming Table
|
||||
|
||||
| Old Icon Name | New Icon Component Name | Example Usage (`Icons.`) | Notes |
|
||||
| :------------------------------ | :------------------------------------------ | :--------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `status-alert` | `StatusAlert` | `Icons.StatusAlert` | |
|
||||
| `status-untracked` | `StatusUntracked` | `Icons.StatusUntracked` | |
|
||||
| `status-locked` | `StatusLocked` | `Icons.StatusLocked` | |
|
||||
| `icon-transferring` | `IconTransferring` | `Icons.IconTransferring` | |
|
||||
| `icon-alert-small` | `Alert` | `Icons.Alert` | |
|
||||
| `icon-alert-outline` | `AlertOutline` | `Icons.AlertOutline` | |
|
||||
| `icon-status-alert` | `Alert` | `Icons.Alert` | |
|
||||
| `action-new-dialog` | `ActionNewDialog` | `Icons.ActionNewDialog` | |
|
||||
| `VolumeRendering` | `VolumeRendering` | `Icons.VolumeRendering` | |
|
||||
| `chevron-left` | `ChevronClosed` | `Icons.ChevronClosed` | Use when arrow direction needs to point left |
|
||||
| `chevron-down` | `ChevronOpen` | `Icons.ChevronOpen` | Use when arrow direction needs to point down |
|
||||
| `launch-arrow` | `LaunchArrow` | `Icons.LaunchArrow` | |
|
||||
| `launch-info` | `LaunchInfo` | `Icons.LaunchInfo` | |
|
||||
| `group-layers` | `GroupLayers` | `Icons.GroupLayers` | |
|
||||
| `icon-upload` | `Upload` | `Icons.Upload` | |
|
||||
| `icon-search` | `Search` | `Icons.Search` | |
|
||||
| `icon-clear-field` | `Clear` | `Icons.Clear` | |
|
||||
| `icon-add` | `Add` | `Icons.Add` | |
|
||||
| `icon-close` | `Close` | `Icons.Close` | |
|
||||
| `icon-pause` | `Pause` | `Icons.Pause` | |
|
||||
| `icon-play` | `Play` | `Icons.Play` | |
|
||||
| `icon-multiple-patients` | `MultiplePatients` | `Icons.MultiplePatients` | |
|
||||
| `icon-settings` | `Settings` | `Icons.Settings` | |
|
||||
| `icon-more-menu` | `More` | `Icons.More` | |
|
||||
| `content-prev` | `ContentPrev` | `Icons.ContentPrev` | |
|
||||
| `content-next` | `ContentNext` | `Icons.ContentNext` | |
|
||||
| `checkbox-checked` | `CheckBoxChecked` | `Icons.CheckBoxChecked` | |
|
||||
| `checkbox-unchecked` | `CheckBoxUnchecked` | `Icons.CheckBoxUnchecked` | |
|
||||
| `checkbox-default` | `CheckBoxUnchecked` | `Icons.CheckBoxUnchecked` | |
|
||||
|`checkbox-active`| `CheckBoxChecked`| `Icons.CheckBoxChecked`| |
|
||||
| `sorting-active-up` | `SortingAscending` | `Icons.SortingAscending` | |
|
||||
| `sorting-active-down` | `SortingDescending` | `Icons.SortingDescending` | |
|
||||
| `sorting` | `Sorting` | `Icons.Sorting` | |
|
||||
|`link` | `Link` | `Icons.Link` | |
|
||||
|`unlink` | `Link` | `Icons.Link` | |
|
||||
|`info-action` | `Info` | `Icons.Info` | |
|
||||
|`database` | `Database`| `Icons.Database`| |
|
||||
|`tool-3d-rotate`| `Tool3DRotate`| `Icons.Tool3DRotate`| |
|
||||
|`tool-angle`| `ToolAngle`| `Icons.ToolAngle`| |
|
||||
|`tool-annotate`| `ToolAnnotate`| `Icons.ToolAnnotate`| |
|
||||
|`tool-bidirectional`| `ToolBidirectional`| `Icons.ToolBidirectional`| |
|
||||
|`tool-calibration`| `ToolCalibrate`| `Icons.ToolCalibrate`| |
|
||||
|`tool-capture`| `ToolCapture`| `Icons.ToolCapture`| |
|
||||
|`tool-cine`| `ToolCine`| `Icons.ToolCine`| |
|
||||
|`tool-circle`| `ToolCircle`| `Icons.ToolCircle`| |
|
||||
|`tool-cobb-angle`| `ToolCobbAngle`| `Icons.ToolCobbAngle`| |
|
||||
|`tool-create-threshold`| `ToolCreateThreshold` | `Icons.ToolCreateThreshold` | |
|
||||
|`tool-crosshair`| `ToolCrosshair`| `Icons.ToolCrosshair`| |
|
||||
|`dicom-tag-browser`| `ToolDicomTagBrowser` | `Icons.ToolDicomTagBrowser` | |
|
||||
|`tool-flip-horizontal`| `ToolFlipHorizontal` | `Icons.ToolFlipHorizontal` | |
|
||||
|`tool-freehand-polygon`| `ToolFreehandPolygon`| `Icons.ToolFreehandPolygon`| |
|
||||
|`tool-freehand-roi`| `ToolFreehandRoi` | `Icons.ToolFreehandRoi`| |
|
||||
|`tool-freehand`| `ToolFreehand`| `Icons.ToolFreehand`| |
|
||||
|`tool-fusion-color`| `ToolFusionColor`| `Icons.ToolFusionColor`| |
|
||||
|`tool-invert`| `ToolInvert`| `Icons.ToolInvert`| |
|
||||
|`tool-layout-default`| `ToolLayoutDefault`| `Icons.ToolLayoutDefault`| |
|
||||
|`tool-length`| `ToolLength`| `Icons.ToolLength`| |
|
||||
|`tool-magnetic-roi`| `ToolMagneticRoi` | `Icons.ToolMagneticRoi`| |
|
||||
|`tool-magnify`| `ToolMagnify`| `Icons.ToolMagnify`| |
|
||||
|`tool-measure-ellipse`| `ToolMeasureEllipse`| `Icons.ToolMeasureEllipse`| |
|
||||
|`tool-more-menu`| `ToolMoreMenu`| `Icons.ToolMoreMenu`| |
|
||||
|`tool-move`| `ToolMove`| `Icons.ToolMove`| |
|
||||
|`tool-polygon`| `ToolPolygon`| `Icons.ToolPolygon`| |
|
||||
|`tool-quick-magnify`| `ToolQuickMagnify` | `Icons.ToolQuickMagnify` | |
|
||||
|`tool-rectangle`| `ToolRectangle` | `Icons.ToolRectangle` | |
|
||||
|`tool-referenceLines`| `ToolReferenceLines`| `Icons.ToolReferenceLines`| |
|
||||
|`tool-reset`| `ToolReset`| `Icons.ToolReset`| |
|
||||
|`tool-rotate-right`| `ToolRotateRight`| `Icons.ToolRotateRight`| |
|
||||
|`tool-seg-brush`| `ToolSegBrush`| `Icons.ToolSegBrush`| |
|
||||
|`tool-seg-eraser`| `ToolSegEraser`| `Icons.ToolSegEraser`| |
|
||||
|`tool-seg-shape`| `ToolSegShape` | `Icons.ToolSegShape`| |
|
||||
|`tool-seg-threshold`| `ToolSegThreshold` | `Icons.ToolSegThreshold` | |
|
||||
|`tool-spline-roi`| `ToolSplineRoi`| `Icons.ToolSplineRoi`| |
|
||||
|`tool-stack-image-sync`| `ToolStackImageSync`| `Icons.ToolStackImageSync`| |
|
||||
|`tool-stack-scroll`| `ToolStackScroll` | `Icons.ToolStackScroll`| |
|
||||
|`tool-toggle-dicom-overlay`| `ToolToggleDicomOverlay`| `Icons.ToolToggleDicomOverlay`| |
|
||||
|`tool-ultrasound-bidirectional`| `ToolUltrasoundBidirectional`| `Icons.ToolUltrasoundBidirectional`| |
|
||||
|`tool-window-level`| `ToolWindowLevel`| `Icons.ToolWindowLevel`| |
|
||||
|`tool-window-region`| `ToolWindowRegion`| `Icons.ToolWindowRegion`| |
|
||||
|`tool-zoom` | `ToolZoom` | `Icons.ToolZoom`| |
|
||||
| `tool-layout` | `ToolLayout` | `Icons.ToolLayout` | |
|
||||
|`icon-tool-eraser`| `ToolEraser` | `Icons.ToolEraser`| |
|
||||
|`icon-tool-brush`| `ToolBrush`| `Icons.ToolBrush`| |
|
||||
|`icon-tool-threshold`| `ToolThreshold` | `Icons.ToolThreshold` | |
|
||||
|`icon-tool-shape`| `ToolShape`| `Icons.ToolShape` | |
|
||||
|`icon-color-lut`| `IconColorLUT` | `Icons.IconColorLUT` | |
|
||||
| `viewport-window-level`|`ViewportWindowLevel`|`Icons.ViewportWindowLevel`| |
|
||||
|`notifications-info`| `NotificationInfo`| `Icons.NotificationInfo`| |
|
||||
|`layout-advanced-3d-four-up` | `LayoutAdvanced3DFourUp` | `Icons.LayoutAdvanced3DFourUp` | |
|
||||
|`layout-advanced-3d-main` | `LayoutAdvanced3DMain` | `Icons.LayoutAdvanced3DMain` | |
|
||||
|`layout-advanced-3d-only` | `LayoutAdvanced3DOnly` | `Icons.LayoutAdvanced3DOnly`| |
|
||||
|`layout-advanced-3d-primary` | `LayoutAdvanced3DPrimary` | `Icons.LayoutAdvanced3DPrimary` | |
|
||||
|`layout-advanced-axial-primary` |`LayoutAdvancedAxialPrimary`| `Icons.LayoutAdvancedAxialPrimary` | |
|
||||
|`layout-advanced-mpr`| `LayoutAdvancedMPR` | `Icons.LayoutAdvancedMPR` | |
|
||||
|`layout-common-1x1` | `LayoutCommon1x1` | `Icons.LayoutCommon1x1` | |
|
||||
|`layout-common-1x2` | `LayoutCommon1x2`|`Icons.LayoutCommon1x2`| |
|
||||
|`layout-common-2x2` | `LayoutCommon2x2`|`Icons.LayoutCommon2x2` | |
|
||||
|`layout-common-2x3` | `LayoutCommon2x3`| `Icons.LayoutCommon2x3`| |
|
||||
|`illustration-investigational-use`|`InvestigationalUse`|`Icons.InvestigationalUse`| |
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
---
|
||||
title: Button
|
||||
summary: Migration guide for Button components in OHIF 3.10, explaining the transition from @ohif/ui to @ohif/ui-next, the replacement of ButtonEnums with string-based variants, and changes to IconButton, ButtonGroup, and styling approach.
|
||||
---
|
||||
|
||||
## Key Changes:
|
||||
|
||||
* **Component Library:** The primary `Button` component likely now resides in `@ohif/ui-next` instead of `@ohif/ui`. Imports need to be updated.
|
||||
* **`ButtonEnums` Deprecated:** The `ButtonEnums.type` (e.g., `ButtonEnums.type.primary`) used for button styling is deprecated. Styling is now primarily controlled by the `variant` prop using string literals (`'default'`, `'secondary'`, `'ghost'`, `'link'`).
|
||||
* **Styling Approach:** Manual Tailwind CSS classes for styling (colors, hover states, sizing) are largely replaced by the `variant` and `size` props on the new `Button` component. Semantic color names are used internally.
|
||||
* **`IconButton` Replacement:** The pattern of using a dedicated `IconButton` component is often replaced by using `<Button variant="ghost" size="icon">` and embedding an icon component (like `<Icons.ByName name="..." />`) within it.
|
||||
* **`ButtonGroup` Deprecated:** The `ButtonGroup` component is deprecated and replaced by the `Tabs`, `TabsList`, and `TabsTrigger` components from `@ohif/ui-next` for creating selectable groups.
|
||||
* **Specific Action Buttons:** In certain contexts (like viewport actions or footers), generic buttons or styled `div` elements might be replaced by more specific components like `ViewportActionButton` or composite components like `FooterAction`.
|
||||
* **Color System:** Custom color classes (e.g., `text-primary-active`, `bg-primary-main`) are replaced by a new semantic color system (e.g., `text-primary`, `bg-primary`, `text-muted-foreground`). Variants often handle color states (hover, active) automatically.
|
||||
|
||||
## Migration Steps:
|
||||
|
||||
1. **Update Imports:**
|
||||
Replace imports for `Button` and related enums from `@ohif/ui` with the new `Button` component, likely from `@ohif/ui-next`.
|
||||
|
||||
```diff
|
||||
- import { Button, ButtonEnums, IconButton } from '@ohif/ui';
|
||||
+ import { Button, Icons } from '@ohif/ui-next';
|
||||
```
|
||||
|
||||
3. **Migrate Manual Styling to `variant` and `size` Props:**
|
||||
Remove custom Tailwind CSS classes for basic button appearance, hover states, and sizing. Use the `variant` (`'default'`, `'secondary'`, `'ghost'`, `'link'`) and `size` (`'sm'`, `'default'`, `'lg'`, `'icon'`) props instead.
|
||||
|
||||
*Example (`DynamicVolumeControls.tsx` change):*
|
||||
```diff
|
||||
- <Button
|
||||
- className="mt-2 !h-[26px] !w-[115px] self-start !p-0"
|
||||
- onClick={() => { onGenerate(computeViewMode); }}
|
||||
- >
|
||||
+ <Button
|
||||
+ variant="default"
|
||||
+ size="sm"
|
||||
+ className="mt-2 h-[26px] w-[115px] self-start p-0" // Keep only necessary layout/positioning classes
|
||||
+ onClick={handleGenerate}
|
||||
+ >
|
||||
Generate
|
||||
</Button>
|
||||
```
|
||||
|
||||
5. **Replace `IconButton`:**
|
||||
Update instances of `<IconButton>` to use `<Button variant="ghost" size="icon">`. Place the icon component from `@ohif/ui-next` (e.g., `<Icons.ByName name="icon-name" />`) inside the button.
|
||||
|
||||
*Example (`DynamicVolumeControls.tsx` change):*
|
||||
```diff
|
||||
- <IconButton
|
||||
- className="bg-customblue-30 h-[26px] w-[58px] rounded-[4px]"
|
||||
- onClick={() => onPlayPauseChange(!isPlaying)}
|
||||
- >
|
||||
- <Icon
|
||||
- name={getPlayPauseIconName()}
|
||||
- className="active:text-primary-light hover:bg-customblue-300 h-[24px] w-[24px] cursor-pointer text-white"
|
||||
- />
|
||||
- </IconButton>
|
||||
+ <Button
|
||||
+ id="play-pause-button"
|
||||
+ variant="secondary" // Or "ghost" depending on final desired style
|
||||
+ size="default" // Or "icon" if only icon is needed
|
||||
+ className="w-[58px]" // Keep specific width if necessary
|
||||
+ onClick={() => {
|
||||
+ if (typeof onPlayPauseChange === 'function') {
|
||||
+ onPlayPauseChange(!isPlaying);
|
||||
+ }
|
||||
+ }}
|
||||
+ >
|
||||
+ <Icons.ByName
|
||||
+ name={getPlayPauseIconName()}
|
||||
+ className="text-foreground h-[24px] w-[24px]" // Use semantic colors
|
||||
+ />
|
||||
+ </Button>
|
||||
```
|
||||
|
||||
6. **Replace `ButtonGroup` with `Tabs`:**
|
||||
Refactor sections using `ButtonGroup` to use the `Tabs`, `TabsList`, and `TabsTrigger` components. Manage the selected state using the `value` and `onValueChange` props of the `Tabs` component.
|
||||
|
||||
*Example (`DynamicVolumeControls.tsx` change):*
|
||||
```diff
|
||||
- <ButtonGroup className="mt-2 w-full">
|
||||
- <button className="w-1/2" onClick={() => setComputedView(false)}>4D</button>
|
||||
- <button className="w-1/2" onClick={() => setComputedView(true)}>Computed</button>
|
||||
- </ButtonGroup>
|
||||
|
||||
+ <Tabs
|
||||
+ value={computedView ? 'computed' : '4d'}
|
||||
+ onValueChange={value => setComputedView(value === 'computed')}
|
||||
+ className="my-2 w-full"
|
||||
+ >
|
||||
+ <TabsList className="w-full">
|
||||
+ <TabsTrigger value="4d" className="w-1/2">4D</TabsTrigger>
|
||||
+ <TabsTrigger value="computed" className="w-1/2">Computed</TabsTrigger>
|
||||
+ </TabsList>
|
||||
+ </Tabs>
|
||||
```
|
||||
|
||||
7. **Identify Specific Component Replacements:**
|
||||
Review areas where styled `div` elements were used as buttons. Replace them with appropriate components like `<Button>` or domain-specific ones if available (e.g., `ViewportActionButton`).
|
||||
|
||||
*Example (`_getStatusComponent.tsx` change):*
|
||||
```diff
|
||||
- <div
|
||||
- className="bg-primary-main hover:bg-primary-light ml-1 cursor-pointer rounded px-1.5 hover:text-black"
|
||||
- onMouseUp={onStatusClick}
|
||||
- >
|
||||
- {loadStr}
|
||||
- </div>
|
||||
+ <ViewportActionButton onInteraction={onStatusClick}>{loadStr}</ViewportActionButton>
|
||||
```
|
||||
|
||||
*Example (`VolumeRenderingPresetsContent.tsx` change):*
|
||||
```diff
|
||||
- <Button
|
||||
- name="Cancel"
|
||||
- size={ButtonEnums.size.medium}
|
||||
- type={ButtonEnums.type.secondary}
|
||||
- onClick={onClose}
|
||||
- > Cancel </Button>
|
||||
+ <FooterAction>
|
||||
+ <FooterAction.Right>
|
||||
+ <FooterAction.Secondary onClick={hide}>Cancel</FooterAction.Secondary>
|
||||
+ </FooterAction.Right>
|
||||
+ </FooterAction>
|
||||
```
|
||||
+379
@@ -0,0 +1,379 @@
|
||||
---
|
||||
title: Input
|
||||
summary: Migration guide for input components in OHIF 3.10, covering the transition from Input, InputNumber, InputRange, InputDoubleRange and others to the new Numeric component system and updated input patterns in @ohif/ui-next.
|
||||
---
|
||||
|
||||
|
||||
# Migration Guide: Input Components to @ohif/ui-next
|
||||
|
||||
This guide explains how to migrate from the existing `Input`, `InputNumber`, `InputRange`, `InputDoubleRange`, `InputFilterText`, `InputGroup`, `InputLabelWrapper`, and `InputText` components to their new equivalents or patterns using `@ohif/ui-next`, including the `Numeric` meta component for numeric inputs.
|
||||
|
||||
|
||||
|
||||
|
||||
## Why Migrate?
|
||||
|
||||
See the full list of components in the [Numeric Component Showcase](/components-list#numeric)
|
||||
|
||||
|
||||
The old components relied heavily on props, making them complex and difficult to maintain and apply custom styles. The new `Numeric` component provides a structured approach with a context-based API, reducing prop clutter and improving reusability.
|
||||
|
||||
The `Numeric` component offers several advantages:
|
||||
- **Versatile Modes**: It supports basic number input (`Numeric.NumberInput`), stepper controls (`Numeric.NumberStepper`), single range sliders (`Numeric.SingleRange`), and double range sliders (`Numeric.DoubleRange`).
|
||||
- **Flexible Layout**: You have full control over the layout using standard CSS classes (`className`) on the container and its subcomponents like `Numeric.Label`, `Numeric.NumberInput`, etc., allowing for various arrangements (e.g., flex, grid).
|
||||
- **Enhanced Customization**: Easily customize the appearance and behavior, such as showing/hiding associated number inputs for sliders, displaying the current value within the label (`showValue`), and integrating icons.
|
||||
- **State Management**: Supports both controlled and uncontrolled component states.
|
||||
|
||||
|
||||
|
||||
|
||||
## `Input type="number"` > `Numeric.NumberInput`
|
||||
|
||||
### Basic Usage
|
||||
|
||||
**Old Usage:**
|
||||
|
||||
```tsx
|
||||
<Input
|
||||
id="example"
|
||||
label="Enter a number"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
type="number"
|
||||
/>
|
||||
```
|
||||
|
||||
**New Usage:**
|
||||
|
||||
```tsx
|
||||
<Numeric.Container mode="number" value={value} onChange={setValue}>
|
||||
<Numeric.Label>Enter a number</Numeric.Label>
|
||||
<Numeric.NumberInput />
|
||||
</Numeric.Container>
|
||||
```
|
||||
|
||||
|
||||
|
||||
### `Input` with Custom Classes
|
||||
|
||||
#### **Old Usage (with containerClassName, labelClassName, and className)**
|
||||
|
||||
In the old implementation, we manually applied `containerClassName`, `labelClassName`, and `className` to style the `Input` component:
|
||||
|
||||
```tsx
|
||||
<Input
|
||||
id="example"
|
||||
label="Enter a number"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
type="number"
|
||||
containerClassName="flex flex-col space-y-2"
|
||||
labelClassName="text-gray-500 text-sm"
|
||||
className="border rounded p-2"
|
||||
/>
|
||||
```
|
||||
|
||||
|
||||
**New Usage (Migrating to `Numeric.NumberInput`)**
|
||||
|
||||
With `Numeric`, you should wrap everything inside `Numeric.Container`, and you can directly apply class names to its subcomponents:
|
||||
|
||||
```tsx
|
||||
<Numeric.Container mode="number" value={value} onChange={setValue} className="flex flex-col space-y-2">
|
||||
<Numeric.Label className="text-gray-500 text-sm">Enter a number</Numeric.Label>
|
||||
<Numeric.NumberInput className="border rounded p-2" />
|
||||
</Numeric.Container>
|
||||
```
|
||||
|
||||
|
||||
## `Input` / `InputText` (General) > `@ohif/ui-next Input + Label`
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* The base `Input` component from `@ohif/ui` is replaced by the `Input` component from `@ohif/ui-next`.
|
||||
* Styling props like `labelClassName`, `containerClassName` are removed. Use standard `className` on the `Input` component and its container elements.
|
||||
* Labels provided via the `label` prop are removed. Use the separate `Label` component from `@ohif/ui-next` alongside the `Input`.
|
||||
* Layout is handled by standard HTML/Tailwind (Flexbox, Grid).
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Update Import:** Ensure you are importing `Input` and `Label` from `@ohif/ui-next`.
|
||||
2. **Replace Label Prop:** If you used the `label` prop, add a separate `<Label>` component before the `<Input>`.
|
||||
3. **Handle Layout:** Wrap the `<Label>` and `<Input>` in a container `div` and use layout utilities (e.g., `flex`, `items-center`, `space-x-2`, `flex-col`) to position them correctly.
|
||||
4. **Transfer Styling:** Migrate styles from `className`, `labelClassName`, and `containerClassName` to the `className` prop of the new `Input`, `Label`, and container `div` as appropriate.
|
||||
|
||||
*Example Diff (Conceptual - derived from PanelPetSUV):*
|
||||
|
||||
```diff
|
||||
- <Input
|
||||
- containerClassName={'flex flex-row justify-between items-center'}
|
||||
- label={'Weight'}
|
||||
- labelChildren={<span className="text-aqua-pale"> kg</span>}
|
||||
- labelClassName="text-[13px] text-white"
|
||||
- className="h-[26px] w-[117px]"
|
||||
- value={metadata.PatientWeight || ''}
|
||||
- onChange={handleWeightChange}
|
||||
- />
|
||||
|
||||
+ <div className="flex flex-row items-center space-x-4"> {/* Replaced containerClassName */}
|
||||
+ <Label className="min-w-32 flex-shrink-0 text-[13px] text-white"> {/* Replaced labelClassName */}
|
||||
+ Weight
|
||||
+ <span className="text-muted-foreground"> kg</span> {/* Replaced labelChildren */}
|
||||
+ </Label>
|
||||
+ <Input
|
||||
+ className="h-7 flex-1 h-[26px] w-[117px]" {/* Merged input className */}
|
||||
+ value={metadata.PatientWeight || ''}
|
||||
+ onChange={handleWeightChange}
|
||||
+ />
|
||||
+ </div>
|
||||
```
|
||||
|
||||
|
||||
## `InputNumber` > `Numeric.NumberStepper`
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* The `InputNumber` component is replaced by the `Numeric` component system using `mode="stepper"`.
|
||||
* Styling props like `sizeClassName`, `arrowsDirection`, and `labelPosition` are removed. Layout and styling are now controlled via standard `className` and parent container layouts (e.g., Flexbox).
|
||||
* Props like `value`, `onChange`, `minValue`, `maxValue`, and `step` are now typically set on the `Numeric.Container`.
|
||||
* Labels are handled by the separate `Numeric.Label` subcomponent.
|
||||
* Stepper controls are provided by the `Numeric.NumberStepper` subcomponent, which takes a `direction` prop (`horizontal` or `vertical`).
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Replace Component:** Replace `<InputNumber ... />` with `<Numeric.Container mode="stepper" ... >`.
|
||||
2. **Transfer Props:** Move `value`, `onChange`, `minValue` (as `min`), `maxValue` (as `max`), and `step` props to the `Numeric.Container`.
|
||||
3. **Add Subcomponents:**
|
||||
* Inside `Numeric.Container`, add `<Numeric.NumberStepper />`. Set its `direction` prop (`horizontal` or `vertical`) based on the old `arrowsDirection`. Apply sizing classes directly using `className`.
|
||||
* Add a `<Numeric.Label>` component for the label text.
|
||||
4. **Handle Layout:** Wrap the `Numeric.Container` or arrange its children using standard layout techniques (like Flexbox) to achieve the desired positioning (equivalent to the old `labelPosition`). Apply styling classes as needed.
|
||||
|
||||
*Example Diff:*
|
||||
|
||||
```diff
|
||||
- <InputNumber
|
||||
- value={currentFrameIndex}
|
||||
- onChange={onFrameChange}
|
||||
- minValue={0}
|
||||
- maxValue={framesLength - 1}
|
||||
- label="Frame"
|
||||
- sizeClassName="w-[58px] h-[28px]"
|
||||
- arrowsDirection="horizontal"
|
||||
- labelPosition="bottom"
|
||||
- />
|
||||
|
||||
+ <Numeric.Container
|
||||
+ mode="stepper"
|
||||
+ value={currentDimensionGroupNumber || 1}
|
||||
+ onChange={onDimensionGroupChange || (() => {})}
|
||||
+ min={1}
|
||||
+ max={numDimensionGroups || 1}
|
||||
+ step={1}
|
||||
+ >
|
||||
+ <div className="flex flex-col items-center">
|
||||
+ <Numeric.NumberStepper
|
||||
+ className="h-[28px] w-[58px]"
|
||||
+ direction="horizontal"
|
||||
+ />
|
||||
+ <Numeric.Label className="text-muted-foreground mt-1 text-sm">Frame</Numeric.Label>
|
||||
+ </div>
|
||||
+ </Numeric.Container>
|
||||
```
|
||||
|
||||
|
||||
## `InputRange` > `Numeric.SingleRange`
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* `InputRange` is replaced by the `Numeric` component system using `mode="singleRange"`.
|
||||
* Props like `value`, `onChange`, `minValue` (`min`), `maxValue` (`max`), and `step` are set on the `Numeric.Container`.
|
||||
* The slider element itself is rendered using `<Numeric.SingleRange />`.
|
||||
* The `showLabel` prop is replaced by explicitly adding a `<Numeric.Label>` subcomponent. The label text is passed as children to `Numeric.Label`. You can optionally show the current value(s) within the label using the `showValue` prop on `Numeric.Label`.
|
||||
* The `allowNumberEdit` prop is replaced by the `showNumberInput` prop on `<Numeric.SingleRange />`.
|
||||
* Layout props like `labelPosition` are removed; use standard CSS/Tailwind for layout.
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Replace Component:** Replace `<InputRange ... />` with `<Numeric.Container mode="singleRange" ... >`.
|
||||
2. **Transfer Props:** Move `value`, `onChange`, `minValue` (as `min`), `maxValue` (as `max`), and `step` to the `Numeric.Container`.
|
||||
3. **Add Subcomponents:**
|
||||
* Inside, add `<Numeric.SingleRange />`.
|
||||
* Use the `showNumberInput` prop on the range subcomponent if number editing was previously enabled (`allowNumberEdit={true}`).
|
||||
* If `showLabel` was true, add a `<Numeric.Label>` component. Pass the label text as children. Use the `showValue` prop on the label if you want to display the numeric value alongside the text.
|
||||
4. **Handle Layout:** Arrange the `Numeric.Label` and the Range subcomponent using standard layout techniques (Flexbox, Grid) as needed. Apply styling directly using `className`.
|
||||
|
||||
*Example Diff (Conceptual):*
|
||||
|
||||
```diff
|
||||
- <InputRange
|
||||
- value={opacity}
|
||||
- onChange={setOpacity}
|
||||
- minValue={0}
|
||||
- maxValue={100}
|
||||
- step={1}
|
||||
- showLabel={true}
|
||||
- label="Opacity"
|
||||
- allowNumberEdit={true}
|
||||
- />
|
||||
|
||||
+ <Numeric.Container
|
||||
+ mode="singleRange"
|
||||
+ value={opacity}
|
||||
+ onChange={setOpacity}
|
||||
+ min={0}
|
||||
+ max={100}
|
||||
+ step={1}
|
||||
+ >
|
||||
+ <div className="flex items-center space-x-2"> {/* Example layout */}
|
||||
+ <Numeric.Label showValue>Opacity</Numeric.Label>
|
||||
+ <Numeric.SingleRange showNumberInput />
|
||||
+ </div>
|
||||
+ </Numeric.Container>
|
||||
```
|
||||
|
||||
|
||||
## `InputDoubleRange` > `Numeric.DoubleRange`
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* `InputDoubleRange` is replaced by the `Numeric` component system using `mode="doubleRange"`.
|
||||
* Props like `values`, `onChange`, `minValue` (`min`), `maxValue` (`max`), and `step` are set on the `Numeric.Container`.
|
||||
* The slider element itself is rendered using `<Numeric.DoubleRange />`.
|
||||
* The `showLabel` prop is replaced by explicitly adding a `<Numeric.Label>` subcomponent. You can optionally show the current values within the label using the `showValue` prop on `Numeric.Label`.
|
||||
* Editing numbers is controlled by the `showNumberInputs` (plural) prop on `<Numeric.DoubleRange />`.
|
||||
* Layout props like `labelPosition` are removed; use standard CSS/Tailwind for layout.
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Replace Component:** Replace `<InputDoubleRange ... />` with `<Numeric.Container mode="doubleRange" ... >`.
|
||||
2. **Transfer Props:** Move `values`, `onChange`, `minValue` (as `min`), `maxValue` (as `max`), and `step` to the `Numeric.Container`.
|
||||
3. **Add Subcomponents:**
|
||||
* Inside, add `<Numeric.DoubleRange />`.
|
||||
* Use the `showNumberInputs` prop on the range subcomponent if number editing is desired.
|
||||
* If `showLabel` was true, add a `<Numeric.Label>` component. Pass the label text as children. Use the `showValue` prop on the label if you want to display the numeric values alongside the text.
|
||||
4. **Handle Layout:** Arrange the `Numeric.Label` and the Range subcomponent using standard layout techniques (Flexbox, Grid) as needed.
|
||||
|
||||
*Example Diff:*
|
||||
|
||||
```diff
|
||||
- <InputDoubleRange
|
||||
- values={rangeValues}
|
||||
- onChange={handleSliderChange}
|
||||
- minValue={1}
|
||||
- maxValue={numDimensionGroups || 1}
|
||||
- showLabel={false} // Assuming label wasn't shown, or handled separately
|
||||
- step={1}
|
||||
- // Assuming number edit might have been implicitly enabled or desired
|
||||
- />
|
||||
|
||||
+ <Numeric.Container
|
||||
+ mode="doubleRange"
|
||||
+ min={1}
|
||||
+ max={numDimensionGroups || 1}
|
||||
+ values={rangeValues || [1, numDimensionGroups || 1]}
|
||||
+ onChange={onDoubleRangeChange || (() => {})}
|
||||
+ >
|
||||
+ {/* Label could be added here if needed */}
|
||||
+ {/* <Numeric.Label>Range</Numeric.Label> */}
|
||||
+ <Numeric.DoubleRange showNumberInputs />
|
||||
+ </Numeric.Container>
|
||||
```
|
||||
|
||||
|
||||
## InputFilterText > InputFilter
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* `InputFilterText` is replaced by the more composable `InputFilter` component from `@ohif/ui-next`.
|
||||
* `InputFilter` uses subcomponents (`InputFilter.SearchIcon`, `InputFilter.Input`, `InputFilter.ClearButton`) which are included by default but can be customized.
|
||||
* The `onDebounceChange` prop is replaced by a standard `onChange` prop on the main `InputFilter` component, which handles debouncing internally (configurable via `debounceTime`).
|
||||
* Props like `placeholder` and `value` are passed to the `InputFilter.Input` subcomponent (or directly to `InputFilter` for simplicity if using the default structure).
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Replace Component:** Replace `<InputFilterText ... />` with `<InputFilter ... >`.
|
||||
2. **Transfer Props:**
|
||||
* Move `placeholder` to the `InputFilter` component or its `InputFilter.Input` subcomponent.
|
||||
* Handle `value` using controlled state if necessary, passing it to `InputFilter`.
|
||||
3. **Update Handler:** Replace the `onDebounceChange` handler with the `onChange` prop on the `InputFilter` component.
|
||||
4. **Styling:** Apply necessary classes for layout and positioning, especially padding on the input (e.g., `pl-9 pr-9`) to accommodate the default icon and clear button if using the defaults.
|
||||
|
||||
*Example Diff:*
|
||||
|
||||
```diff
|
||||
- <InputFilterText
|
||||
- value={searchValue}
|
||||
- onDebounceChange={handleSearchChange}
|
||||
- placeholder={'Search all'}
|
||||
- />
|
||||
|
||||
+ <InputFilter
|
||||
+ value={searchValue}
|
||||
+ onChange={setFilterValue} /* Direct state update or debounced handler */
|
||||
+ placeholder="Search all"
|
||||
+ >
|
||||
+ {/* Using default structure which includes Icon, Input, ClearButton */}
|
||||
+ {/* Example customization: */}
|
||||
+ {/* <InputFilter.SearchIcon /> */}
|
||||
+ {/* <InputFilter.Input placeholder="Search all" className="pl-9 pr-9" /> */}
|
||||
+ {/* <InputFilter.ClearButton /> */}
|
||||
+ </InputFilter>
|
||||
```
|
||||
|
||||
## InputGroup / InputLabelWrapper > Composition
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* These wrapper components (`InputGroup`, `InputLabelWrapper`) are deprecated.
|
||||
* Functionality (grouping label and input, optional sorting indicators) is now achieved through composition using standard layout techniques (Flexbox/Grid) and the base `@ohif/ui-next` components (`Label`, `Input`, `Icons`).
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Remove Wrapper:** Delete the `<InputGroup>` or `<InputLabelWrapper>` tags.
|
||||
2. **Create Container:** Use a standard `div` as the container.
|
||||
3. **Add Label and Input:** Place the `<Label>` and the corresponding input/control component (e.g., `<Input>`, `<Select>`, custom component) inside the `div`.
|
||||
4. **Apply Layout:** Use Tailwind classes (`flex`, `grid`, `space-x-*`, `items-center`, etc.) on the container `div` to arrange the label and input as needed.
|
||||
5. **Add Sorting Icons (if applicable):** If migrating from `InputLabelWrapper` with `isSortable`, manually add the appropriate `<Icons.ByName name="sorting-..." />` component next to the label text within the `<Label>` component's children. Import `Icons` from `@ohif/ui-next`.
|
||||
6. **Apply Styling:** Add necessary styling classes directly to the `Label`, input component, and container `div`.
|
||||
|
||||
*Example Diff (Conceptual - derived from InputLabelWrapper usage):*
|
||||
|
||||
```diff
|
||||
- <InputLabelWrapper
|
||||
- label="Patient Name"
|
||||
- isSortable={true}
|
||||
- sortDirection={sortDir}
|
||||
- onLabelClick={toggleSort}
|
||||
- >
|
||||
- <Input value={patientName} onChange={setPatientName} />
|
||||
- </InputLabelWrapper>
|
||||
|
||||
+ <div className="flex flex-col space-y-1"> {/* Example layout */}
|
||||
+ <Label
|
||||
+ onClick={toggleSort}
|
||||
+ className="flex cursor-pointer items-center"
|
||||
+ >
|
||||
+ Patient Name
|
||||
+ {sortDir === 'ascending' && <Icons.ByName name="sorting-ascending" className="ml-1 h-4 w-4" />}
|
||||
+ {sortDir === 'descending' && <Icons.ByName name="sorting-descending" className="ml-1 h-4 w-4" />}
|
||||
+ {sortDir === 'none' && <Icons.ByName name="sorting" className="ml-1 h-4 w-4" />}
|
||||
+ </Label>
|
||||
+ <Input value={patientName} onChange={setPatientName} />
|
||||
+ </div>
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Summary of Changes
|
||||
|
||||
| Old Component | New Component/Pattern Equivalent | Notes |
|
||||
|-----------------------|----------------------------------------------------|----------------------------------------------------------------------------|
|
||||
| `<Input type="number">` | `<Numeric.NumberInput>` | Use `Numeric.Container` with `mode="number"` |
|
||||
| `<Input>` / `<InputText>` | `@ohif/ui-next <Input>` + `<Label>` | Use standard `<div>` and CSS/Tailwind for layout |
|
||||
| `<InputNumber>` | `<Numeric.NumberStepper>` | Use `Numeric.Container` with `mode="stepper"` |
|
||||
| `<InputRange>` | `<Numeric.SingleRange>` | Use `Numeric.Container` with `mode="singleRange"` |
|
||||
| `<InputDoubleRange>` | `<Numeric.DoubleRange>` | Use `Numeric.Container` with `mode="doubleRange"` |
|
||||
| `<InputFilterText>` | `@ohif/ui-next <InputFilter>` | Composable component with internal debouncing |
|
||||
| `<InputGroup>` | Composition (`div`, `<Label>`, `<Input>`, etc.) | Replaced by standard layout techniques |
|
||||
| `<InputLabelWrapper>` | Composition (`div`, `<Label>`, `<Input>`, `<Icons>`) | Replaced by standard layout techniques; manually add sort icons if needed |
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: Tooltip
|
||||
summary: Migration guide for Tooltip components in OHIF 3.10, explaining the new composable structure with TooltipTrigger and TooltipContent, and replacement of TooltipClipboard with the Clipboard component.
|
||||
---
|
||||
|
||||
## Tooltip Updates
|
||||
|
||||
### Changes:
|
||||
- Updated Tooltip structure to use `Tooltip`, `TooltipTrigger`, and `TooltipContent`.
|
||||
- Removed deprecated `TooltipClipboard` and inline `content`/`position` properties.
|
||||
|
||||
### Migration Steps:
|
||||
1. Replace imports:
|
||||
```tsx
|
||||
// Before
|
||||
import { Tooltip } from '@ohif/ui';
|
||||
import { TooltipClipboard } from '@ohif/ui';
|
||||
|
||||
// After
|
||||
import { Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
|
||||
```
|
||||
|
||||
2. Update Tooltip usage:
|
||||
```tsx
|
||||
// Before
|
||||
<Tooltip content={<div>Tooltip Message</div>} position="bottom-left">
|
||||
<Component />
|
||||
</Tooltip>
|
||||
|
||||
// After
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Component />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
Tooltip Message
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
```
|
||||
|
||||
|
||||
3. TooltipClipboard Replacement:
|
||||
The `TooltipClipboard` component has been removed. Instead, use the `Clipboard` component inside `TooltipContent` for copying text functionality.
|
||||
|
||||
#### Before:
|
||||
```tsx
|
||||
<TooltipClipboard>{text}</TooltipClipboard>
|
||||
```
|
||||
|
||||
#### After:
|
||||
```tsx
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="cursor-pointer truncate">{text}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
{text}
|
||||
<Clipboard>{text}</Clipboard>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
```
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
---
|
||||
title: Select
|
||||
summary: Migration guide for Select components in OHIF 3.10, explaining the transition from @ohif/ui to @ohif/ui-next, with details on the new Select.Root, Select.Trigger, Select.Content, and Select.Item structure replacing the old dropdown approach.
|
||||
---
|
||||
|
||||
|
||||
|
||||
This guide outlines the steps needed to migrate from the previous `<Select>` component (likely from `@ohif/ui`) to the new compound `<Select>` component provided by `@ohif/ui-next`.
|
||||
|
||||
## Key Changes
|
||||
|
||||
* **Deprecated Component:** The previous standalone `<Select>` component is deprecated.
|
||||
* **New Compound Component:** The new implementation uses a compound component pattern, requiring multiple specific sub-components (`Select`, `SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`).
|
||||
* **Option Definition:** Options are no longer passed as a single `options` prop. Instead, each option is rendered as an individual `<SelectItem>` component within `<SelectContent>`.
|
||||
* **Placeholder:** The `placeholder` prop is now applied to the `<SelectValue>` sub-component.
|
||||
* **Value Handling:** The `value` and `onValueChange` props are now managed by the root `<Select>` component. Note the change from `onChange` to `onValueChange`.
|
||||
|
||||
## Migration Steps
|
||||
|
||||
1. **Update Imports:**
|
||||
Replace the import for the old `Select` component with imports for the new compound components from `@ohif/ui-next`.
|
||||
|
||||
```diff
|
||||
- import { Select } from '@ohif/ui';
|
||||
+ import {
|
||||
+ Select,
|
||||
+ SelectContent,
|
||||
+ SelectItem,
|
||||
+ SelectTrigger,
|
||||
+ SelectValue,
|
||||
+ } from '@ohif/ui-next';
|
||||
|
||||
```
|
||||
|
||||
2. **Adapt Component Structure:**
|
||||
Replace the single `<Select>` tag with the new compound structure. Map your existing `options` array to individual `<SelectItem>` components.
|
||||
|
||||
*Example Diff:*
|
||||
|
||||
```diff
|
||||
- <Select
|
||||
- label={t('Strategy')}
|
||||
- closeMenuOnSelect={true}
|
||||
- className="border-primary-main mr-2 bg-black text-white"
|
||||
- options={options}
|
||||
- placeholder={options.find(option => option.value === config.strategy).placeHolder}
|
||||
- value={config.strategy}
|
||||
- onChange={({ value }) => {
|
||||
- dispatch({
|
||||
- type: 'setStrategy',
|
||||
- payload: {
|
||||
- strategy: value,
|
||||
- },
|
||||
- });
|
||||
- }}
|
||||
- />
|
||||
|
||||
+ <Select
|
||||
+ value={config.strategy}
|
||||
+ onValueChange={value => {
|
||||
+ dispatch({
|
||||
+ type: 'setStrategy',
|
||||
+ payload: {
|
||||
+ strategy: value,
|
||||
+ },
|
||||
+ });
|
||||
+ }}
|
||||
+ >
|
||||
+ <SelectTrigger className="w-full">
|
||||
+ <SelectValue
|
||||
+ placeholder={options.find(option => option.value === config.strategy)?.placeHolder}
|
||||
+ />
|
||||
+ </SelectTrigger>
|
||||
+ <SelectContent className="">
|
||||
+ {options.map(option => (
|
||||
+ <SelectItem
|
||||
+ key={option.value}
|
||||
+ value={option.value}
|
||||
+ >
|
||||
+ {option.label}
|
||||
+ </SelectItem>
|
||||
+ ))}
|
||||
+ </SelectContent>
|
||||
+ </Select>
|
||||
```
|
||||
|
||||
**Explanation:**
|
||||
* The main logic container is now the root `<Select>` component, which takes the `value` and the `onValueChange` handler (note: `onValueChange` directly receives the *value*, not an event object).
|
||||
* `<SelectTrigger>` wraps the element that opens the dropdown (often styled like the previous select input).
|
||||
* `<SelectValue>` displays the currently selected value or the `placeholder` text if no value is selected.
|
||||
* `<SelectContent>` contains the list of options.
|
||||
* Each option is rendered using `<SelectItem>`, where the `value` prop corresponds to the option's value and the children (`{option.label}`) represent the text displayed for that option.
|
||||
* Props like `closeMenuOnSelect` are generally handled by default in the new component.
|
||||
|
||||
3. **Adjust Styling:**
|
||||
The internal structure and default styling have changed. Remove or update previous CSS class names (`className`) applied to the old component and apply new Tailwind/CSS classes to the appropriate sub-components (`Select`, `SelectTrigger`, `SelectContent`, `SelectItem`) as needed to match your desired appearance. Note that `border-primary-main` and `bg-black` might no longer be necessary or applied differently with the new component's structure and variants.
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: Switch
|
||||
summary: Migration guide for Switch component in OHIF 3.10, covering the transition from custom Toggle and CinePlayPauseButton components to the standardized Switch component, with details on prop changes and usage patterns.
|
||||
---
|
||||
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* **Component Renaming & Import Path:** The component `SwitchButton` from `@ohif/ui` has been replaced by `Switch` in `@ohif/ui-next`. You will need to update your import statements accordingly.
|
||||
* **Removal of `label` Prop:** The integrated `label` prop has been removed. Labels should now be implemented externally using standard HTML elements (like `<span>` or `<label>`) or the `<Label>` component from `@ohif/ui-next`. Layout between the label and the `Switch` needs to be handled explicitly, typically using Flexbox utility classes.
|
||||
* **Event Handler Prop Renamed:** The `onChange` event handler prop has been replaced with `onCheckedChange`. The new prop provides the updated boolean `checked` state directly as its argument.
|
||||
* **Styling and Layout:** The new `Switch` component relies on standard `className` prop and Tailwind utility classes for styling and layout adjustments, rather than internal props or structures.
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Update Import Statement:**
|
||||
Change the import from `@ohif/ui` to `@ohif/ui-next` and rename the component.
|
||||
|
||||
```diff
|
||||
- import { SwitchButton } from '@ohif/ui';
|
||||
+ import { Switch } from '@ohif/ui-next';
|
||||
+ import { Label } from '@ohif/ui-next'; // Optional: If using the Label component
|
||||
```
|
||||
|
||||
2. **Replace Component Usage and Handle Label Externally:**
|
||||
Replace the `<SwitchButton>` tag with `<Switch>`. Remove the `label` prop and add an external element for the label. Use layout utilities (like `flex`) to position the label relative to the switch.
|
||||
|
||||
*Example Diff:*
|
||||
```diff
|
||||
- <SwitchButton
|
||||
- label="Enable Feature"
|
||||
- checked={isFeatureEnabled}
|
||||
- onChange={handleToggle}
|
||||
- />
|
||||
|
||||
+ <div className="flex items-center space-x-2">
|
||||
+ <Switch
|
||||
+ id="feature-toggle" // It's good practice to add an id
|
||||
+ checked={isFeatureEnabled}
|
||||
+ onCheckedChange={handleToggle}
|
||||
+ />
|
||||
+ <Label htmlFor="feature-toggle">Enable Feature</Label> {/* Or use a <span> */}
|
||||
+ </div>
|
||||
```
|
||||
*Explanation:* The `label` prop is gone. A `<div>` with `flex` is used to arrange the new `<Switch>` and an associated `<Label>`. The `htmlFor` attribute on the `<Label>` links it to the `<Switch>` via its `id` for accessibility.
|
||||
|
||||
3. **Update Event Handler Prop:**
|
||||
Rename the `onChange` prop to `onCheckedChange`. Ensure your handler function correctly receives the new boolean state as its argument.
|
||||
|
||||
*Example Diff (within the component usage):*
|
||||
```diff
|
||||
- onChange={checked => setIsEnabled(checked)}
|
||||
+ onCheckedChange={checked => setIsEnabled(checked)}
|
||||
```
|
||||
*Explanation:* The prop name changes from `onChange` to `onCheckedChange`. The callback function signature, receiving the boolean `checked` state, remains a common pattern and is directly supported by `onCheckedChange`. If your previous `onChange` did *not* receive the checked state directly (e.g., it just toggled existing state), you might need to adjust your handler logic slightly, but `onCheckedChange` directly provides the new state.
|
||||
+434
@@ -0,0 +1,434 @@
|
||||
---
|
||||
title: Toolbar
|
||||
summary: Migration guide for toolbar components in OHIF 3.10, covering the new uiType values (ohif.toolButton and ohif.toolButtonList), section-based definitions replacing nested structures, ToolBox updates, and changes to tool option handlers.
|
||||
---
|
||||
|
||||
# Toolbar
|
||||
|
||||
## New Toolbar uiType
|
||||
|
||||
We have two new toolbar button types: `ohif.toolButtonList` and `ohif.toolButton`, which are intended to replace the `ohif.radioGroup` and `ohif.splitButton` types.
|
||||
|
||||
Note that these are backward compatible, so if you are not ready to pick up the new ui types (which are more flexible and powerful), you can continue using the old types.
|
||||
|
||||
|
||||
```js
|
||||
// Old type
|
||||
{
|
||||
uiType: 'ohif.radioGroup',
|
||||
}
|
||||
|
||||
// New type
|
||||
{
|
||||
uiType: 'ohif.toolButton',
|
||||
}
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```js
|
||||
// Old type
|
||||
{
|
||||
uiType: 'ohif.splitButton',
|
||||
}
|
||||
|
||||
// New type
|
||||
{
|
||||
uiType: 'ohif.toolButtonList',
|
||||
}
|
||||
```
|
||||
|
||||
The `ohif.buttonGroup` and `ohif.radioGroup` types used in the Toolbox have been replaced with `ohif.toolBoxButtonGroup` and `ohif.toolBoxButton` to reflect their usage in the Toolbox, which has distinct styling.
|
||||
|
||||
```js
|
||||
// Old type
|
||||
{
|
||||
uiType: 'ohif.buttonGroup',
|
||||
}
|
||||
|
||||
// New type
|
||||
{
|
||||
uiType: 'ohif.toolBoxButtonGroup',
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```js
|
||||
// Old type
|
||||
{
|
||||
uiType: 'ohif.radioGroup',
|
||||
}
|
||||
|
||||
// New type
|
||||
{
|
||||
uiType: 'ohif.toolBoxButton',
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## getToolbarModule
|
||||
|
||||
|
||||
The `getToolbarModule` function previously returned `disabled`, `disabledText`, and `className` as part of its evaluation process for the button state. These properties will still be returned, but common class names are now handled internally by the new UI button components, including `ToolButton`, `ToolButtonList`, `Toolbox`, and `ToolBoxGroup`. You can override the `className` if you need to.
|
||||
|
||||
|
||||
## Tool Definitions: Moving to Section-Based Definitions
|
||||
|
||||
This migration represents a significant step toward a more extension-based toolbar system. We've moved away from the nested primary/items structure in favor of a flatter, more composable section-based approach.
|
||||
|
||||
### Deprecated: Nested Toolbar Structures
|
||||
|
||||
```diff
|
||||
- {
|
||||
- id: 'MeasurementTools',
|
||||
- uiType: 'ohif.toolButtonList',
|
||||
- props: {
|
||||
- groupId: 'MeasurementTools',
|
||||
- evaluate: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList',
|
||||
- primary: createButton({
|
||||
- id: 'Length',
|
||||
- icon: 'tool-length',
|
||||
- label: 'Length',
|
||||
- tooltip: 'Length Tool',
|
||||
- commands: setToolActiveToolbar,
|
||||
- evaluate: 'evaluate.cornerstoneTool',
|
||||
- }),
|
||||
- secondary: {
|
||||
- icon: 'chevron-down',
|
||||
- tooltip: 'More Measure Tools',
|
||||
- },
|
||||
- items: [
|
||||
- createButton({ ... }),
|
||||
- createButton({ ... }),
|
||||
- // More nested buttons
|
||||
- ],
|
||||
- },
|
||||
- }
|
||||
```
|
||||
|
||||
### New Approach: Section-Based Definitions
|
||||
|
||||
```diff
|
||||
+ // 1. Define the toolbar section container
|
||||
+ {
|
||||
+ id: 'MeasurementTools',
|
||||
+ uiType: 'ohif.toolButtonList',
|
||||
+ props: {
|
||||
+ buttonSection: 'measurementSection',
|
||||
+ groupId: 'MeasurementTools',
|
||||
+ },
|
||||
+ },
|
||||
+ // 2. Register individual buttons separately
|
||||
+ {
|
||||
+ id: 'Length',
|
||||
+ uiType: 'ohif.toolButton',
|
||||
+ props: {
|
||||
+ icon: 'tool-length',
|
||||
+ label: 'Length',
|
||||
+ tooltip: 'Length Tool',
|
||||
+ commands: setToolActiveToolbar,
|
||||
+ evaluate: 'evaluate.cornerstoneTool',
|
||||
+ },
|
||||
+ },
|
||||
+ {
|
||||
+ id: 'Bidirectional',
|
||||
+ uiType: 'ohif.toolButton',
|
||||
+ props: {
|
||||
+ icon: 'tool-bidirectional',
|
||||
+ label: 'Bidirectional',
|
||||
+ tooltip: 'Bidirectional Tool',
|
||||
+ commands: setToolActiveToolbar,
|
||||
+ evaluate: 'evaluate.cornerstoneTool',
|
||||
+ },
|
||||
+ },
|
||||
```
|
||||
|
||||
and then in your mode you can compose the section and associate buttons
|
||||
|
||||
```diff
|
||||
+ // 3. In your mode, create the section and associate buttons
|
||||
+ toolbarService.createButtonSection('primary', [
|
||||
+ 'MeasurementTools',
|
||||
+ 'Pan',
|
||||
+ 'Zoom',
|
||||
+ ]);
|
||||
+
|
||||
+
|
||||
+ toolbarService.createButtonSection('measurementSection', [
|
||||
+ 'Length',
|
||||
+ 'Bidirectional',
|
||||
+ 'ArrowAnnotate',
|
||||
+ 'EllipticalROI',
|
||||
+ ]);
|
||||
```
|
||||
|
||||
:::note
|
||||
The `measurementSection` is defined in the tool button configuration of the `MeasurementTools` button.
|
||||
:::
|
||||
|
||||
|
||||
### Group Evaluators Deprecated
|
||||
|
||||
Group evaluator functions like `evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList` are now deprecated. Instead, the `uiType` component itself is responsible for grouping and displaying the buttons from a section as needed. This allows for more flexible UI implementations that aren't tied to specific evaluation logic.
|
||||
|
||||
|
||||
|
||||
## ToolBox
|
||||
|
||||
Previously, the segmentation toolbox was not using an `evaluator` property. This is now taken into account
|
||||
|
||||
|
||||
### evaluators in Toolbox
|
||||
|
||||
```js
|
||||
// old
|
||||
{
|
||||
id: 'BrushTools',
|
||||
uiType: 'ohif.buttonGroup',
|
||||
props: {
|
||||
groupId: 'BrushTools',
|
||||
}
|
||||
}
|
||||
|
||||
// now
|
||||
{
|
||||
id: 'BrushTools',
|
||||
uiType: 'ohif.buttonGroup',
|
||||
props: {
|
||||
groupId: 'BrushTools',
|
||||
evaluate: 'evaluate.cornerstone.hasSegmentation',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Replace Toolbox imports from ui-next with extension-default
|
||||
|
||||
```diff
|
||||
- import { Toolbox } from '@ohif/ui-next';
|
||||
+ import { Toolbox } from '@ohif/extension-default';
|
||||
```
|
||||
|
||||
|
||||
Ensure you're importing the Toolbox component from the correct location:
|
||||
|
||||
```javascript
|
||||
// New import pattern
|
||||
import { Toolbox } from '@ohif/extension-default';
|
||||
|
||||
// Usage remains similar
|
||||
<Toolbox
|
||||
servicesManager={servicesManager}
|
||||
buttonSectionId="segmentation"
|
||||
title="Segmentation Tools"
|
||||
/>
|
||||
```
|
||||
|
||||
### Stacked Sections in Toolbox
|
||||
|
||||
The new Toolbox component supports stacked sections, which allows for more complex UI organization. Instead of flat button groups, you can now create deep hierarchies of tool sections and subsections.
|
||||
|
||||
|
||||
Previously you were able to have something like this
|
||||
|
||||
```js
|
||||
// old
|
||||
// buttons for BrushTools were a giant group of buttons
|
||||
const buttons = {
|
||||
id: 'BrushTools',
|
||||
uiType: 'ohif.toolBoxButtonGroup',
|
||||
props: {
|
||||
groupId: 'BrushTools',
|
||||
evaluate: 'evaluate.cornerstone.hasSegmentation',
|
||||
items: [
|
||||
{
|
||||
id: 'Brush',
|
||||
icon: 'icon-tool-brush',
|
||||
label: 'Brush',
|
||||
evaluate: {
|
||||
// ...
|
||||
},
|
||||
options: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'Eraser',
|
||||
icon: 'icon-tool-eraser',
|
||||
label: 'Eraser',
|
||||
evaluate: {
|
||||
// ...
|
||||
},
|
||||
options: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'Threshold',
|
||||
icon: 'icon-tool-threshold',
|
||||
label: 'Threshold Tool',
|
||||
evaluate: {
|
||||
// ...
|
||||
},
|
||||
options: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'Shapes',
|
||||
uiType: 'ohif.toolBoxButton',
|
||||
props: {
|
||||
id: 'Shapes',
|
||||
icon: 'icon-tool-shape',
|
||||
label: 'Shapes',
|
||||
evaluate: {
|
||||
// ...
|
||||
},
|
||||
options: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
toolbarService.addButtons(buttons);
|
||||
|
||||
toolbarService.createButtonSection('segmentationToolbox', ['BrushTools', 'Shapes']);
|
||||
```
|
||||
|
||||
But now you should have at least one section defined in your toolbar buttons
|
||||
|
||||
|
||||
```js
|
||||
// separate flat definitions for each button and each section
|
||||
const buttons = [
|
||||
{
|
||||
id: 'Brush',
|
||||
uiType: 'ohif.toolButton',
|
||||
props: {
|
||||
icon: 'icon-tool-brush',
|
||||
label: 'Brush',
|
||||
evaluate: {
|
||||
// ...
|
||||
},
|
||||
options: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'Eraser',
|
||||
uiType: 'ohif.toolButton',
|
||||
props: {
|
||||
icon: 'icon-tool-eraser',
|
||||
label: 'Eraser',
|
||||
evaluate: {
|
||||
// ...
|
||||
},
|
||||
options: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'Threshold',
|
||||
uiType: 'ohif.toolButton',
|
||||
props: {
|
||||
icon: 'icon-tool-threshold',
|
||||
label: 'Threshold Tool',
|
||||
evaluate: {
|
||||
// ...
|
||||
},
|
||||
options: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'Shapes',
|
||||
uiType: 'ohif.toolBoxButton',
|
||||
props: {
|
||||
icon: 'icon-tool-shape',
|
||||
label: 'Shapes',
|
||||
evaluate: {
|
||||
name: 'evaluate.cornerstone.segmentation',
|
||||
toolNames: ['CircleScissor', 'SphereScissor', 'RectangleScissor'],
|
||||
disabledText: 'Create new segmentation to enable shapes tool.',
|
||||
},
|
||||
options: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
},
|
||||
// Sections
|
||||
{
|
||||
id: 'SegmentationTools',
|
||||
uiType: 'ohif.toolBoxButton',
|
||||
props: {
|
||||
groupId: 'SegmentationTools',
|
||||
buttonSection: 'segmentationToolboxToolsSection',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'BrushTools',
|
||||
uiType: 'ohif.toolBoxButtonGroup',
|
||||
props: {
|
||||
groupId: 'BrushTools',
|
||||
buttonSection: 'brushToolsSection',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
toolbarService.addButtons(buttons);
|
||||
```
|
||||
|
||||
and then
|
||||
|
||||
```js
|
||||
// Step 2: Create the section hierarchy
|
||||
// Top level toolbox section
|
||||
toolbarService.createButtonSection('segmentationToolbox', ['SegmentationTools']);
|
||||
|
||||
// Next level - subsections within the toolbox
|
||||
toolbarService.createButtonSection('segmentationToolboxToolsSection', ['BrushTools', 'Shapes']);
|
||||
|
||||
// Lowest level - buttons within a subsection
|
||||
toolbarService.createButtonSection('brushToolsSection', ['Brush', 'Eraser', 'Threshold']);
|
||||
```
|
||||
|
||||
|
||||
### Remove ToolboxProvider from composition root
|
||||
|
||||
If you have the ToolboxProvider in your application composition, remove it:
|
||||
|
||||
```diff
|
||||
// In App.tsx or similar
|
||||
const appComposition = [
|
||||
[ThemeWrapperNext],
|
||||
[ThemeWrapper],
|
||||
[SystemContextProvider, { commandsManager, extensionManager, hotkeysManager, servicesManager }],
|
||||
- [ToolboxProvider],
|
||||
[ViewportGridProvider, { service: viewportGridService }],
|
||||
// Other providers...
|
||||
];
|
||||
```
|
||||
|
||||
we now keep the state for toolbar inside the ToolbarService itself
|
||||
|
||||
### 3. Update tool option handlers to use onChange instead of commands
|
||||
|
||||
```diff
|
||||
- <RowSegmentedControl
|
||||
- key={option.id}
|
||||
- option={option}
|
||||
- />
|
||||
|
||||
+ <RowSegmentedControl
|
||||
+ key={option.id}
|
||||
+ option={option}
|
||||
+ onChange={option.onChange}
|
||||
+ />
|
||||
```
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
---
|
||||
title: Segmentation Table
|
||||
summary: Migration guide for the refactored SegmentationTable component in OHIF 3.10, covering changes to the context system, compound component pattern adoption, replacement of SelectorHeader, and introduction of segment statistics features.
|
||||
---
|
||||
|
||||
|
||||
# SegmentationTable Migration Guide
|
||||
|
||||
This guide will help you migrate your code to use the refactored SegmentationTable component.
|
||||
|
||||
## Key Changes
|
||||
|
||||
* **Context System Refactoring**: The context system has been completely redesigned with dedicated providers for different aspects of the segmentation UI
|
||||
* **Compound Component Pattern**: Components now follow a more structured compound component pattern with clearer parent-child relationships
|
||||
* **Header Components Changed**: The `SegmentationTable.SelectorHeader` has been replaced with specialized header components
|
||||
* **New Features**: Added segment statistics, hover cards, and better customization options
|
||||
* **Better Customization**: Added support for custom dropdown menus and segment statistics headers
|
||||
|
||||
## Migration Steps
|
||||
|
||||
|
||||
|
||||
### 1. Migrate from SelectorHeader to New Header Components
|
||||
|
||||
The `SegmentationTable.SelectorHeader` has been removed. Use the new Collapsed pattern instead:
|
||||
|
||||
```diff
|
||||
- <SegmentationTable.SelectorHeader>
|
||||
- <CustomDropdownMenuContent />
|
||||
- </SegmentationTable.SelectorHeader>
|
||||
|
||||
+ <SegmentationTable.Collapsed>
|
||||
+ <SegmentationTable.Collapsed.Header>
|
||||
+ <SegmentationTable.Collapsed.DropdownMenu>
|
||||
+ <CustomDropdownMenuContent />
|
||||
+ </SegmentationTable.Collapsed.DropdownMenu>
|
||||
+ <SegmentationTable.Collapsed.Selector />
|
||||
+ <SegmentationTable.Collapsed.Info />
|
||||
+ </SegmentationTable.Collapsed.Header>
|
||||
+ <SegmentationTable.Collapsed.Content>
|
||||
+ {/* Content here */}
|
||||
+ </SegmentationTable.Collapsed.Content>
|
||||
+ </SegmentationTable.Collapsed>
|
||||
```
|
||||
|
||||
### 2. Update Component Hierarchy for Expanded View
|
||||
|
||||
The expanded view structure has also changed:
|
||||
|
||||
```diff
|
||||
- <SegmentationTable.Expanded>
|
||||
- <SegmentationTable.Header>
|
||||
- <CustomDropdownMenuContent />
|
||||
- </SegmentationTable.Header>
|
||||
- <SegmentationTable.Segments />
|
||||
- </SegmentationTable.Expanded>
|
||||
|
||||
+ <SegmentationTable.Expanded>
|
||||
+ <SegmentationTable.Expanded.Header>
|
||||
+ <SegmentationTable.Expanded.DropdownMenu>
|
||||
+ <CustomDropdownMenuContent />
|
||||
+ </SegmentationTable.Expanded.DropdownMenu>
|
||||
+ <SegmentationTable.Expanded.Label />
|
||||
+ <SegmentationTable.Expanded.Info />
|
||||
+ </SegmentationTable.Expanded.Header>
|
||||
+ <SegmentationTable.Expanded.Content>
|
||||
+ <SegmentationTable.AddSegmentRow />
|
||||
+ <SegmentationTable.Segments />
|
||||
+ </SegmentationTable.Expanded.Content>
|
||||
+ </SegmentationTable.Expanded>
|
||||
```
|
||||
|
||||
### 3. Using the New Segment Statistics Component
|
||||
|
||||
The new `SegmentStatistics` component provides a way to display segment statistics:
|
||||
|
||||
```diff
|
||||
+ <SegmentationTable.Segments>
|
||||
+ <SegmentationTable.SegmentStatistics.Header>
|
||||
+ <CustomSegmentStatisticsHeader />
|
||||
+ </SegmentationTable.SegmentStatistics.Header>
|
||||
+ <SegmentationTable.SegmentStatistics.Body />
|
||||
+ </SegmentationTable.Segments>
|
||||
```
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: Tours and Onboarding
|
||||
summary: Migration guide for Tours and Onboarding features in OHIF 3.10, explaining the transition from defining tours directly in window.config.tours to using the customization service, enabling mode-specific tours and better organization.
|
||||
---
|
||||
|
||||
## Migration Guide: Tours
|
||||
|
||||
* Tours are no longer defined directly in `window.config.tours` but through the customization service under the key `ohif.tours`
|
||||
* The `waitForElement` utility function has been moved from the config file to a dedicated customization file
|
||||
* The structure of tour definitions (steps, options, etc.) remains largely the same
|
||||
|
||||
## Migration Steps:
|
||||
|
||||
|
||||
|
||||
### 1. Update any direct references to window.config.tours
|
||||
|
||||
If you have any code that directly references window.config.tours, update it to use the customization service:
|
||||
|
||||
```diff
|
||||
- const tours = window.config.tours;
|
||||
+ const tours = customizationService.getCustomization('ohif.tours');
|
||||
```
|
||||
|
||||
### 2. Use config update patterns for configuring tours
|
||||
|
||||
**Before:**
|
||||
```diff
|
||||
- window.config = {
|
||||
- tours: [
|
||||
- {
|
||||
- id: 'basicViewerTour',
|
||||
- route: '/viewer',
|
||||
- steps: [
|
||||
- // tour steps...
|
||||
- ],
|
||||
- tourOptions: {
|
||||
- // tour options...
|
||||
- },
|
||||
- },
|
||||
- ],
|
||||
- };
|
||||
```
|
||||
|
||||
**After:**
|
||||
```javascript
|
||||
window.config = {
|
||||
customizationService: {
|
||||
'ohif.tours': {
|
||||
$set: [
|
||||
{
|
||||
id: 'basicViewerTour',
|
||||
route: '/viewer',
|
||||
steps: [
|
||||
// Your tour steps
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
## Benefits of the Change
|
||||
|
||||
4. **Mode-specific Tours**: now you can have different tours for different modes
|
||||
+403
@@ -0,0 +1,403 @@
|
||||
---
|
||||
title: uiDialogService
|
||||
summary: Migration guide for uiDialogService in OHIF 3.10, covering the transition from create/dismiss to show/hide methods, changes to dialog definition structure, and updates to utility functions like callInputDialog and colorPickerDialog.
|
||||
---
|
||||
|
||||
|
||||
## DialogService
|
||||
|
||||
This guide details the migration steps for the `uiDialogService` API changes, based on the provided diff. The most significant change is a shift from methods like `.create()` and `.dismiss()` to `.show()` and `.hide()`, alongside structural changes in how dialogs are defined and rendered. The changes aim for a more streamlined and flexible dialog management, leveraging React context for state management.
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* **`uiDialogService.create()` and `uiDialogService.dismiss()` are deprecated.** They have been replaced with `uiDialogService.show()` and `uiDialogService.hide()`. This change
|
||||
makes it consistent with the `uiModalService` and `uiNotificationService` APIs.
|
||||
* **`content` property now expects a React Component Type**, not an instance. Props for the content component are passed via `contentProps`.
|
||||
* **The `dialogId` is now consistently passed as `id` within the options** to `uiDialogService.show()`.
|
||||
|
||||
|
||||
### Props Kept same as before
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `id` | Still required, but we don't return it from the `show` method anymore. |
|
||||
| `content` | This is now expected to be a *React component type* (a function or class that returns JSX) |
|
||||
| `contentProps` | This continues to be the way to pass data *to* your custom dialog component. However, several specific props that *used* to be passed here (like `onClose`, `actions`) are no longer valid. |
|
||||
| `isDraggable` | Controls whether the dialog can be moved by dragging. |
|
||||
| `defaultPosition` | Allows you to specify an initial `{ x, y }` position for the dialog. |
|
||||
| `title` | The title text to display in the dialog header. |
|
||||
| `showOverlay` | default true - if the dialog is draggable the overlay is not shown by default |
|
||||
|
||||
|
||||
### Removed Props:
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `centralize` | Dialogs are now centered by default via CSS if you don't want center you pass defaultPosition |
|
||||
| `preservePosition` | Work in progress and will be available in future |
|
||||
| `contentDimensions` | Removed - should be specified directly in dialogs |
|
||||
| `onStart` | Removed |
|
||||
| `onDrag` | Removed |
|
||||
| `onStop` | Removed |
|
||||
| `onClickOutside` | Removed - if you want to close the dialog on click outside, you can use the `shouldCloseOnOverlayClick` prop |
|
||||
|
||||
|
||||
### Renamed Props:
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `containerDimensions` | renamed to `containerClassName` |
|
||||
|
||||
|
||||
|
||||
|
||||
### New Props:
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `unstyled` | A boolean prop to render the dialog without the default styling. It is used for context menu dialogs |
|
||||
| `shouldCloseOnEsc` | Default off for dialogs - Controls whether pressing the Escape key will close the dialog. |
|
||||
| `shouldCloseOnOverlayClick` | Default off for dialogs - Controls whether clicking the overlay background will close the dialog. |
|
||||
|
||||
|
||||
### Frequently Asked Questions
|
||||
|
||||
**Q: Why did my dialog's background color change?**
|
||||
|
||||
A: This can happen if you were previously setting the background color on the dialog's content directly. With the new API, the dialog's content is wrapped in a container. You should now pass any background or text color classes using the `containerClassName` property.
|
||||
|
||||
For example:
|
||||
```diff
|
||||
- containerClassName: 'w-[70%] max-w-[900px]',
|
||||
+ containerClassName: 'w-[70%] max-w-[900px] bg-primary-dark text-foreground',
|
||||
```
|
||||
|
||||
**Q: How do I create a dialog without the default container, like for a context menu?**
|
||||
|
||||
A: If you need to render dialog content without the standard dialog container (e.g., for a context menu), you can use the `unstyled: true` prop. This will render your component without the default dialog wrapper, giving you full control over its appearance.
|
||||
|
||||
```javascript
|
||||
uiDialogService.show({
|
||||
id: 'context-menu',
|
||||
content: MyContextMenuComponent,
|
||||
contentProps: { /* ... */ },
|
||||
unstyled: true,
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Replace `.create()` with `.show()`:**
|
||||
|
||||
```diff
|
||||
- const dialogId = uiDialogService.create({
|
||||
- id: 'my-dialog',
|
||||
- content: MyDialogComponent,
|
||||
- contentProps: { prop1: 'value1' },
|
||||
- // ... other options ...
|
||||
- });
|
||||
|
||||
+ uiDialogService.show({
|
||||
+ id: 'my-dialog',
|
||||
+ content: MyDialogComponent,
|
||||
+ contentProps: { prop1: 'value1' },
|
||||
+ // ... other options ...
|
||||
+ });
|
||||
```
|
||||
|
||||
2. Rename `containerDimensions` to `containerClassName`
|
||||
|
||||
```diff
|
||||
- containerDimensions: 'w-[70%] max-w-[900px]',
|
||||
+ containerClassName: 'w-[70%] max-w-[900px]',
|
||||
```
|
||||
|
||||
3. **Replace `.dismiss({ id: dialogId })` with `.hide(dialogId)`:**
|
||||
|
||||
```diff
|
||||
- uiDialogService.dismiss({ id: dialogId });
|
||||
|
||||
+ uiDialogService.hide(dialogId);
|
||||
```
|
||||
|
||||
4. **Replace `dismissAll` with `hideAll`**
|
||||
```diff
|
||||
- uiDialogService.dismissAll();
|
||||
+ uiDialogService.hideAll();
|
||||
```
|
||||
5. **Update Dialog Content:**
|
||||
|
||||
* Ensure your dialog content is defined as a React component (functional or class-based).
|
||||
* Pass props to the component via `contentProps`.
|
||||
* You don't need to pass in `onClose` or `hide` as they are now handled passed in automatically.
|
||||
|
||||
```javascript
|
||||
// Example: MyDialogComponent.tsx
|
||||
function MyDialogComponent({ prop1, hide }) {
|
||||
return (
|
||||
<div>
|
||||
<p>Value of prop1: {prop1}</p>
|
||||
<button onClick={hide}>Close</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
6. **Update Footer Action Buttons:**
|
||||
|
||||
Previously, footer buttons might have been implemented using generic `<Button>` components. The new approach uses a dedicated `<FooterAction>` component for better structure and consistency.
|
||||
|
||||
```diff
|
||||
- <Button
|
||||
- name="Cancel"
|
||||
- size={ButtonEnums.size.medium}
|
||||
- type={ButtonEnums.type.secondary}
|
||||
- onClick={onClose}
|
||||
- > Cancel </Button>
|
||||
+ <FooterAction>
|
||||
+ <FooterAction.Right>
|
||||
+ <FooterAction.Secondary onClick={hide}>Cancel</FooterAction.Secondary>
|
||||
+ </FooterAction.Right>
|
||||
+ </FooterAction>
|
||||
```
|
||||
|
||||
|
||||
**Example: Migrating a Custom Dialog with Actions**
|
||||
|
||||
This example shows how to migrate a dialog that used the generic `Dialog` component with a `body` function and an `actions` array to the new component-based pattern.
|
||||
|
||||
**Before: Using a Generic `Dialog` with `contentProps`**
|
||||
|
||||
Previously, you might have constructed a dialog by passing a title, a body-rendering function, and an actions array directly into `contentProps`. This approach mixed content, presentation, and logic in the `create` call.
|
||||
|
||||
```javascript
|
||||
// Before
|
||||
const dialogId = 'my-complex-dialog';
|
||||
const showCompletionDialog = (successMessage) => {
|
||||
const dismiss = () => uiDialogService.dismiss({ id: dialogId });
|
||||
uiDialogService.create({
|
||||
id: dialogId,
|
||||
centralize: true,
|
||||
isDraggable: false,
|
||||
content: Dialog, // A generic Dialog component
|
||||
contentProps: {
|
||||
title: 'Action Completed',
|
||||
noCloseButton: true,
|
||||
onClose: dismiss,
|
||||
onSubmit: dismiss,
|
||||
actions: [
|
||||
{ id: 'proceed', text: 'Proceed', type: 'primary' },
|
||||
],
|
||||
body: () => (
|
||||
<div className="text-secondary-light">{successMessage}</div>
|
||||
),
|
||||
},
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
**After: Using a Dedicated Component**
|
||||
|
||||
The new pattern involves creating a dedicated React component for your dialog's content. This component encapsulates its own layout, logic, and actions, leading to cleaner and more maintainable code.
|
||||
|
||||
**1. Create a dedicated component for your dialog's content.**
|
||||
|
||||
This component receives `hide` and any custom data via props. It manages its own UI, including the footer buttons, and handles the logic for what happens when a user interacts with it.
|
||||
|
||||
```javascript
|
||||
// After: MyCompletionDialog.tsx
|
||||
function MyCompletionDialog({ hide, successMessage }) {
|
||||
const closeAndProceed = () => {
|
||||
hide();
|
||||
// You can now handle any post-dialog logic here,
|
||||
// such as navigating to a different page.
|
||||
// navigate('/next-page');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="text-body-text">{successMessage}</div>
|
||||
<FooterAction>
|
||||
<FooterAction.Right>
|
||||
<FooterAction.Primary onClick={closeAndProceed}>
|
||||
Proceed
|
||||
</FooterAction.Primary>
|
||||
</FooterAction.Right>
|
||||
</FooterAction>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**2. Call `uiDialogService.show` with the new component.**
|
||||
|
||||
The call to show the dialog is now much simpler. You pass the component itself to the `content` property and any necessary data through `contentProps`.
|
||||
|
||||
```javascript
|
||||
// After: Calling the service
|
||||
const showCompletionDialog = (successMessage) => {
|
||||
uiDialogService.show({
|
||||
id: 'my-complex-dialog',
|
||||
title: 'Action Completed',
|
||||
content: MyCompletionDialog,
|
||||
contentProps: {
|
||||
successMessage,
|
||||
},
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Components
|
||||
|
||||
### CreateReportDialogPrompt
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* **Function Signature Update:** The function now accepts an object with `servicesManager`, `extensionManager`, `title`(optional).
|
||||
* **Return Value Structure:** The function now returns an object containing `value` (the report name), `dataSourceName` (the selected data source, if applicable), and `action` (indicating the user's choice).
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Update Function Call:**
|
||||
|
||||
Previously, the function was called with separate arguments. You should now pass an object:
|
||||
|
||||
```diff
|
||||
- const promptResult = await createReportDialogPrompt(uiDialogService, {
|
||||
- extensionManager,
|
||||
- });
|
||||
|
||||
+ const promptResult = await createReportDialogPrompt({
|
||||
+ servicesManager,
|
||||
+ extensionManager,
|
||||
+ title: 'Store Segmentation', // Optional title
|
||||
+ });
|
||||
|
||||
```
|
||||
|
||||
### promptSaveReport
|
||||
|
||||
Not changed, just a javascript to typescript migration.
|
||||
|
||||
### callLabelAutocompleteDialog
|
||||
|
||||
`callLabelAutocompleteDialog` is deprecated and has been replaced by `callInputDialogAutoComplete`. This new function simplifies the asynchronous handling of user input by using `uiDialogService.show()` and returning a promise.
|
||||
|
||||
|
||||
|
||||
### showLabelAnnotationPopup
|
||||
|
||||
`showLabelAnnotationPopup` has been replaced with `callInputDialogAutoComplete`. This update also uses `uiDialogService.show()` and promises, and it removes the callback function.
|
||||
|
||||
- The function now expects an object with `measurement`, `uiDialogService`, `labelConfig`, and `renderContent`.
|
||||
|
||||
```diff
|
||||
- const value = await showLabelAnnotationPopup(
|
||||
- measurement,
|
||||
- servicesManager.services.uiDialogService,
|
||||
- labelConfig,
|
||||
- renderContent
|
||||
- );
|
||||
+ const value = await callInputDialogAutoComplete({
|
||||
+ measurement,
|
||||
+ uiDialogService,
|
||||
+ labelConfig,
|
||||
+ renderContent,
|
||||
+ });
|
||||
```
|
||||
|
||||
### callInputDialog
|
||||
|
||||
- expects an objects now and returns the value of the input which you can then use for actions
|
||||
|
||||
|
||||
```diff
|
||||
|
||||
- callInputDialog(
|
||||
- uiDialogService,
|
||||
- {
|
||||
- text: '',
|
||||
- label: `${length}`,
|
||||
- },
|
||||
- (value, id) => {
|
||||
- if (id === 'save') {
|
||||
- adjustCalibration(Number.parseFloat(value));
|
||||
- resolve(true);
|
||||
- } else {
|
||||
- reject('cancel');
|
||||
- }
|
||||
- },
|
||||
- false,
|
||||
- {
|
||||
- dialogTitle: 'Calibration',
|
||||
- inputLabel: 'Actual Physical distance (mm)',
|
||||
- validateFunc: val => {
|
||||
- const v = Number.parseFloat(val);
|
||||
- return !isNaN(v) && v !== 0.0;
|
||||
- },
|
||||
- }
|
||||
- );
|
||||
+ callInputDialog({
|
||||
+ uiDialogService,
|
||||
+ title: 'Calibration',
|
||||
+ placeholder: 'Actual Physical distance (mm)',
|
||||
+ defaultValue: `${length}`,
|
||||
+ }).then(newValue => {
|
||||
+ adjustCalibration(Number.parseFloat(newValue));
|
||||
+ resolve(true);
|
||||
+ });
|
||||
```
|
||||
|
||||
or another one
|
||||
|
||||
```diff
|
||||
- callInputDialog(
|
||||
- uiDialogService,
|
||||
- { text: '', label: 'Enter description' },
|
||||
- (value, action) => {
|
||||
- if (action === 'save') {
|
||||
- saveFunction(value);
|
||||
- }
|
||||
- }
|
||||
- );
|
||||
+ callInputDialog({
|
||||
+ uiDialogService,
|
||||
+ title: 'Enter description of the Series',
|
||||
+ defaultValue: '',
|
||||
+ }).then(value => {
|
||||
+ saveFunction(value);
|
||||
+ });
|
||||
```
|
||||
|
||||
|
||||
### colorPickerDialog
|
||||
|
||||
Instead of calling `colorPickerDialog(uiDialogService, rgbaColor, callback)`, use `uiDialogService.show()` with `ColorPickerDialog` as the content.
|
||||
|
||||
|
||||
```diff
|
||||
- colorPickerDialog(uiDialogService, rgbaColor, (newRgbaColor, actionId) => {
|
||||
- if (actionId === 'cancel') {
|
||||
- return;
|
||||
- }
|
||||
- const color = [newRgbaColor.r, newRgbaColor.g, newRgbaColor.b, newRgbaColor.a * 255.0];
|
||||
- segmentationService.setSegmentColor(viewportId, segmentationId, segmentIndex, color);
|
||||
- });
|
||||
|
||||
// after
|
||||
|
||||
+ uiDialogService.show({
|
||||
+ content: ColorPickerDialog,
|
||||
+ title: 'Segment Color',
|
||||
+ contentProps: {
|
||||
+ value: rgbaColor,
|
||||
+ onSave: newRgbaColor => {
|
||||
+ const color = [newRgbaColor.r, newRgbaColor.g, newRgbaColor.b, newRgbaColor.a * 255.0];
|
||||
+ segmentationService.setSegmentColor(viewportId, segmentationId, segmentIndex, color);
|
||||
+ },
|
||||
+ },
|
||||
+ });
|
||||
```
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
---
|
||||
title: uiModalService
|
||||
summary: Migration guide for the uiModalService in OHIF 3.10, covering props that remain unchanged, renamed props (containerDimensions to containerClassName), removed props (movable, isOpen, contentDimensions), and automatic handling of modal closing.
|
||||
---
|
||||
|
||||
|
||||
## ModalService
|
||||
|
||||
|
||||
### Props Kept same as before
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `content` | This is now expected to be a *React component type* (a function or class that returns JSX) |
|
||||
| `contentProps` | This continues to be the way to pass data *to* your custom dialog component. However, several specific props that *used* to be passed here (like `onClose`, `actions`) are no longer valid. |
|
||||
| `title` | The title text to display in the dialog header. |
|
||||
| `shouldCloseOnEsc` | Allows closing the modal when the escape key is pressed. |
|
||||
| `shouldCloseOnOverlayClick` | Allows closing the modal when the overlay is clicked. |
|
||||
|
||||
### Renamed Props:
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `containerDimensions` | renamed to `containerClassName` |
|
||||
|
||||
|
||||
|
||||
### Removed Props:
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `movable` | It's removed because modals shouldn't be movable. If you need to move a dialog, use `uidDialogService` and `dialogs` instead. |
|
||||
| `isOpen` | always assumed `true` when `show` is called. |
|
||||
| `contentDimensions` | Removed, it is now component's responsibility to set the size for the content |
|
||||
| `customClassName` | renamed to `className` |
|
||||
| `closeButton` | The component now manages modal closing internally. If you need a close button, you can add one, perhaps by checking out the `FooterActions` component. |
|
||||
|
||||
|
||||
### Frequently Asked Questions
|
||||
|
||||
**Q: Why did my dialog's background color change?**
|
||||
|
||||
A: This can happen if you were previously setting the background color on the dialog's content directly. With the new API, the dialog's content is wrapped in a container. You should now pass any background or text color classes using the `containerClassName` property.
|
||||
|
||||
For example:
|
||||
```diff
|
||||
- containerClassName: 'w-[70%] max-w-[900px]',
|
||||
+ containerClassName: 'w-[70%] max-w-[900px] bg-primary-dark text-foreground',
|
||||
```
|
||||
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
|
||||
### Rename of `containerDimensions` to `containerClassName` and removal of `contentDimensions`
|
||||
|
||||
|
||||
Before
|
||||
|
||||
```js
|
||||
uiModalService.show({
|
||||
title: 'Download High-Quality Image',
|
||||
content: CornerstoneViewportDownloadForm,
|
||||
contentProps: {
|
||||
activeViewportId,
|
||||
},
|
||||
containerDimensions: 'w-[70%] max-w-[900px]',
|
||||
contentDimensions: 'h-[493px] w-[460px] pl-[12px] pr-[12px]',
|
||||
});
|
||||
```
|
||||
|
||||
After: the component is responsible for setting the size
|
||||
|
||||
```js
|
||||
function CornerstoneViewportDownloadForm({ activeViewportId }) {
|
||||
return (
|
||||
<div className="h-[493px] w-[460px] pl-[12px] pr-[12px]">
|
||||
<h2 className="text-lg font-bold">Download Image</h2>
|
||||
<p>Viewport ID: {activeViewportId}</p>
|
||||
<button className="mt-4 bg-blue-500 text-white p-2 rounded">Download</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show the modal
|
||||
uiModalService.show({
|
||||
title: 'Download High-Quality Image',
|
||||
content: CornerstoneViewportDownloadForm,
|
||||
contentProps: { activeViewportId },
|
||||
containerClassName: 'w-[70%] max-w-[900px]',
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### onClose
|
||||
Previously, you had to pass in the `onClose` as `hide` function automatically added to the component.
|
||||
|
||||
```diff
|
||||
- uiModalService.show({
|
||||
- title: 'Untrack Series',
|
||||
- content: UntrackSeriesModal,
|
||||
- contentProps: { onConfirm },
|
||||
- onClose: () => uiModalService.hide(),
|
||||
- });
|
||||
|
||||
+ uiModalService.show({
|
||||
+ title: 'Untrack Series',
|
||||
+ content: UntrackSeriesModal,
|
||||
+ contentProps: {
|
||||
+ onConfirm,
|
||||
+ hide, // passed in automatically in the background
|
||||
+ },
|
||||
+ });
|
||||
```
|
||||
|
||||
6. **Update Footer Action Buttons:**
|
||||
|
||||
Previously, footer buttons might have been implemented using generic `<Button>` components. The new approach uses a dedicated `<FooterAction>` component for better structure and consistency.
|
||||
|
||||
```diff
|
||||
- <Button
|
||||
- name="Cancel"
|
||||
- size={ButtonEnums.size.medium}
|
||||
- type={ButtonEnums.type.secondary}
|
||||
- onClick={onClose}
|
||||
- > Cancel </Button>
|
||||
+ <FooterAction>
|
||||
+ <FooterAction.Right>
|
||||
+ <FooterAction.Secondary onClick={hide}>Cancel</FooterAction.Secondary>
|
||||
+ </FooterAction.Right>
|
||||
+ </FooterAction>
|
||||
```
|
||||
|
||||
**Example: Simple Alert Modal**
|
||||
|
||||
This example shows how to migrate a simple alert modal.
|
||||
|
||||
*Before:*
|
||||
|
||||
```js
|
||||
uiModalService.show({
|
||||
title: 'Untrack Series',
|
||||
content: UntrackSeriesModal,
|
||||
contentProps: {
|
||||
onConfirm,
|
||||
hide, // passed in automatically in the background
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
*After:*
|
||||
|
||||
```js
|
||||
function UntrackSeriesModal({ onConfirm, hide }) {
|
||||
return (
|
||||
<div>
|
||||
{/* Modal content */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show the modal
|
||||
uiModalService.show({
|
||||
title: 'Untrack Series',
|
||||
content: UntrackSeriesModal,
|
||||
contentProps: {
|
||||
onConfirm,
|
||||
hide, // passed in automatically in the background
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
sidebar_label: 3.9 -> 3.10
|
||||
title: Migration Guide from 3.9 to 3.10 beta
|
||||
summary: Migration guide for upgrading from OHIF 3.9 to 3.10 beta, covering general changes, customization service improvements, UI component upgrades, command handling, hotkey updates, routing changes, and testing strategies.
|
||||
---
|
||||
|
||||
# Migration Guide
|
||||
|
||||
|
||||
## General
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Migration Guides",
|
||||
"position": 11
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
sidebar_label: 3.7 -> 3.8
|
||||
title: Migration Guide from 3.7 to 3.8
|
||||
summary: Migration guide for upgrading from OHIF 3.7 to 3.8, covering changes to toolbar button definitions, active tool handling, button evaluators, tool listeners, leftPanel/rightPanel naming, URL parameters, UI components, and refactoring of enums.
|
||||
---
|
||||
|
||||
# Migration Guide
|
||||
|
||||
There are two main things that need to be taken care of.
|
||||
|
||||
|
||||
## New Toolbar Button definitions
|
||||
|
||||
### Update Active Tool Handling
|
||||
The concept of `activeTool` and its associated getter and setter has been removed. The active tool should now be derived from the toolGroup and the viewport.
|
||||
|
||||
|
||||
**Action Needed**
|
||||
|
||||
Remove any code that sets the default tool using `toolbarService.setDefaultTool()` and activates the tool using
|
||||
`toolbarService.recordInteraction()`. For example, the following code should be removed:
|
||||
|
||||
```javascript
|
||||
let unsubscribe;
|
||||
toolbarService.setDefaultTool({
|
||||
groupId: "WindowLevel",
|
||||
itemId: "WindowLevel",
|
||||
interactionType: "tool",
|
||||
commands: [
|
||||
{
|
||||
commandName: "setToolActive",
|
||||
commandOptions: {
|
||||
toolName: "WindowLevel",
|
||||
},
|
||||
context: "CORNERSTONE",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const activateTool = () => {
|
||||
toolbarService.recordInteraction(toolbarService.getDefaultTool());
|
||||
|
||||
unsubscribe();
|
||||
};
|
||||
|
||||
({ unsubscribe } = toolGroupService.subscribe(
|
||||
toolGroupService.EVENTS.VIEWPORT_ADDED,
|
||||
activateTool
|
||||
));
|
||||
```
|
||||
|
||||
|
||||
|
||||
Instead, focus on defining the buttons and their placement in the toolbar using `toolbarService.addButtons()` and `toolbarService.createButtonSection()`. For example:
|
||||
|
||||
```javascript
|
||||
toolbarService.addButtons([...toolbarButtons, ...moreTools]);
|
||||
toolbarService.createButtonSection("primary", [
|
||||
"MeasurementTools",
|
||||
"Zoom",
|
||||
"WindowLevel",
|
||||
"Pan",
|
||||
"Capture",
|
||||
"Layout",
|
||||
"MPR",
|
||||
"Crosshairs",
|
||||
"MoreTools",
|
||||
]);
|
||||
```
|
||||
|
||||
|
||||
### Update Button Definitions
|
||||
The concept of button types (toggle, action, tool) has been removed. Buttons are now defined using a simplified object-based definition.
|
||||
|
||||
**Action Needed**
|
||||
|
||||
Update your button definitions to use the new object-based format and remove the `type` property. Use the `uiType` property for the top-level UI type definition. For example:
|
||||
|
||||
```javascript
|
||||
// Old Implementation
|
||||
{
|
||||
id: 'Capture',
|
||||
type: 'ohif.action',
|
||||
props: {
|
||||
icon: 'tool-capture',
|
||||
label: 'Capture',
|
||||
type: 'action',
|
||||
commands: [
|
||||
{
|
||||
commandName: 'showDownloadViewportModal',
|
||||
commandOptions: {},
|
||||
context: 'CORNERSTONE',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
is now
|
||||
|
||||
```javascript
|
||||
// New Implementation
|
||||
{
|
||||
id: 'Capture',
|
||||
uiType: 'ohif.radioGroup',
|
||||
props: {
|
||||
icon: 'tool-capture',
|
||||
label: 'Capture',
|
||||
commands: [
|
||||
{
|
||||
commandName: 'showDownloadViewportModal',
|
||||
context: 'CORNERSTONE',
|
||||
},
|
||||
],
|
||||
evaluate: 'evaluate.action',
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
### Add Evaluators to Button Definitions
|
||||
Introduce the evaluate property in your button definitions to determine the state of the button based on the app context.
|
||||
|
||||
**Action Needed**
|
||||
|
||||
Add the appropriate `evaluate` property to each button definition. For example:
|
||||
- Use `evaluate.cornerstoneTool` if the button should be highlighted only when it is the active primary tool (left mouse).
|
||||
- Use `evaluate.cornerstoneTool.toggle` if the tool is a toggle tool (like reference lines or image overlay).
|
||||
|
||||
Refer to the `modes/longitudinal/src/toolbarButtons.ts` file for examples of using the `evaluate` property.
|
||||
|
||||
Additional Resources
|
||||
|
||||
- For more information on the new toolbar module and its usage, refer to the [Toolbar documentation](../platform/extensions/modules/toolbar.md).
|
||||
- Consult the updated button definitions in `modes/longitudinal/src/toolbarButtons.ts` for examples of the new object-based button definition format and the usage of evaluators.
|
||||
|
||||
### Tool listeners
|
||||
|
||||
Some tools can be configured to listen to events to trigger, for example
|
||||
|
||||
```ts
|
||||
createButton({
|
||||
id: 'ReferenceLines',
|
||||
icon: 'tool-referenceLines',
|
||||
label: 'Reference Lines',
|
||||
tooltip: 'Show Reference Lines',
|
||||
commands: 'toggleEnabledDisabledToolbar',
|
||||
listeners: {
|
||||
[ViewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]:
|
||||
ReferenceLinesListeners,
|
||||
[ViewportGridService.EVENTS.VIEWPORTS_READY]:
|
||||
ReferenceLinesListeners,
|
||||
},
|
||||
evaluate: 'evaluate.cornerstoneTool.toggle',
|
||||
}),
|
||||
```
|
||||
|
||||
If you have a custom viewport component, and you are overriding the ```onElementEnabled``` handler, than ensure to call ```viewportGridService.setViewportIsReady(viewportId, true)``` in your own handler so that eventually the ```VIEWPORTS_READY``` event fires as expected, if you are not modifying the handler, then an existing handler that is automatically passed down via the props will call that for you, it is passed down from ```ViewportGrid.tsx```
|
||||
|
||||
```ts
|
||||
<ViewportComponent
|
||||
displaySets={displaySets}
|
||||
viewportLabel={viewports.size > 1 ? viewportLabel : ''}
|
||||
viewportId={viewportId}
|
||||
dataSource={dataSource}
|
||||
viewportOptions={viewportOptions}
|
||||
displaySetOptions={displaySetOptions}
|
||||
needsRerendering={displaySetsNeedsRerendering}
|
||||
isHangingProtocolLayout={isHangingProtocolLayout}
|
||||
onElementEnabled={() => {
|
||||
viewportGridService.setViewportIsReady(viewportId, true);
|
||||
}}
|
||||
/>
|
||||
|
||||
```
|
||||
|
||||
## Toolbar Service
|
||||
|
||||
toolbarService.init is not a function.
|
||||
|
||||
**Action Needed**
|
||||
remove the call to toolbarService.init() from your codebase.
|
||||
|
||||
|
||||
|
||||
## leftPanelDefaultClosed and rightPanelDefaultClosed
|
||||
|
||||
Now they are renamed to `leftPanelClosed` and `rightPanelClosed` respectively.
|
||||
|
||||
|
||||
## StudyInstanceUID in the URL param
|
||||
|
||||
Previously there were two params that you could choose: seriesInstanceUID and seriesInstanceUIDs, they have been replaced with seriesInstanceUIDs so even if you would like to filter one series use ``seriesInstanceUIDs`
|
||||
|
||||
|
||||
## UI
|
||||
|
||||
### Header
|
||||
Header in @ohif/ui now needs servicesManager and appConfig as input.
|
||||
|
||||
|
||||
### Panels
|
||||
Left and right panel lists are no longer injected into the LayoutTemplate, and have been moved to a PanelService where you have to fetch them from.
|
||||
|
||||
If you're using the main layout, you're fine. However, if you have a custom layout, you'll need to update it. To get the panels, see the
|
||||
|
||||
`extensions/default/src/ViewerLayout/index.tsx`
|
||||
|
||||
|
||||
|
||||
|
||||
## Refactoring
|
||||
|
||||
- TimingEnum (and I guess all enums exported from OHIF core have now moved from Types to Enums export).
|
||||
@@ -0,0 +1,998 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
sidebar_label: 2.x -> 3.5
|
||||
title: Migration Guide from 2.x to 3.5
|
||||
summary: Comprehensive migration guide for upgrading from OHIF 2.x to 3.5, covering architectural changes, UI improvements, extensions, modes, cornerstone3D integration, configuration changes, and build process updates.
|
||||
---
|
||||
|
||||
# Migration Guide
|
||||
|
||||
On this page, we will provide a guide to migrating from OHIF v2 to v3. Please note
|
||||
that this document is a work in progress and will be updated as we move forward.
|
||||
This document is not meant to be used as a migration recipe but as a migration overview.
|
||||
|
||||
|
||||
# Introduction
|
||||
|
||||
## Importance of Migration
|
||||
|
||||
- Enhanced UX: the new design and UI of OHIF v3 provides a more intuitive and user-friendly experience.
|
||||
OHIF v3 adds an improved side panels and toolbar, and a new layout system that lets you customize
|
||||
the layout of your application.
|
||||
- Improved Performance: OHIF v3 leverages the new Cornerstone3D rendering and tooling libraries, which
|
||||
significantly improve performance and provide a more robust and stable foundation for your application
|
||||
for rendering and interacting with medical images. Some of the new advanced features in Cornerstone3D
|
||||
include: OffScreen Rendering and GPU Acceleration for all viewports, streaming of the volume data, 3D annotations and measurements,
|
||||
sharing tool states between viewports and more.
|
||||
- Improved Customizability: With addition of Modes and Extensions, OHIF v3 provides a more modular
|
||||
and customizable framework for building medical imaging applications, this will let you
|
||||
focus on your use case and not worry about the underlying infrastructure and also have less worry
|
||||
to keep up to date with the latest changes.
|
||||
- Community driven Modes: OHIF v3 provides a gallery of modes that you can use as a starting point
|
||||
for your application. These
|
||||
- Future-Proofing: By migrating to v3, you align your application with the latest advancements in the OHIF framework, ensuring ongoing support, updates, and access to new features.
|
||||
- Community Support: OHIF v3 benefits from an active community of developers and contributors who provide valuable support, bug fixes, and continuous improvements.
|
||||
|
||||
## Migration Timeframe
|
||||
|
||||
The duration of the migration process can vary depending on factors such as the complexity of custom changes made in v2, familiarity with v3's architecture, and the size of the codebase.
|
||||
If you don't have any custom changes in v2, the migration process should be relatively straightforward. If you have custom changes, you will need to update them to work with the new architecture and new
|
||||
rendering and tooling engines.
|
||||
|
||||
|
||||
## Complexity and Pain Points
|
||||
|
||||
Certain scenarios can make the migration process more complex and potentially introduce pain points:
|
||||
|
||||
- Extensive Customizations: If your v2 implementation includes extensive custom changes and overrides, adapting those customizations to the new structure and APIs of v3 may require additional effort and careful refactoring.
|
||||
- UI Customizations: Since in OHIF v3 we moved our component library to Tailwind CSS
|
||||
if you have any custom UI components, you will need to migrate them to Tailwind CSS too, and this might be a bit time consuming.
|
||||
- Hardware requirements: Since Cornerstone3D uses WebGL for rendering volumeViewport (although it has
|
||||
a CPU rendering fallback), you need to make sure that your target hardware supports WebGL. You can check
|
||||
if your hardware supports WebGL [here](https://get.webgl.org/). Also regarding the GPU requirements, you can check the tier of your GPU [here](https://pmndrs.github.io/detect-gpu/), if it is tier 1 and above, you
|
||||
should be good to go.
|
||||
|
||||
## Summary of Changes
|
||||
|
||||
OHIF v3 is a major re-architecture of the OHIF v2 to make it more modular and
|
||||
easier to maintain. The main differences are:
|
||||
|
||||
- platform/viewer (@ohif/viewer) has been renamed to platform/app (@ohif/app) (explanation below)
|
||||
- Extensions are available to be used by modes on request, but are still injected as module components.
|
||||
- To use the modules provided by the extensions, you need to write a [Mode](../platform/modes/index.md). Modes
|
||||
are configuration objects that will be used by the viewer to load the modules. This lets users to be able to use common extensions with different configurations, and enhances the customizability of the viewer.
|
||||
- App configuration structure is different, mainly the `servers` is renamed to `dataSources`.
|
||||
- Apps can be customized significantly more than previously by providing configuration code int he customizationModule section.
|
||||
- The viewer UI is completely re-written in Tailwind CSS for better maintainability, although it is a WIP but
|
||||
already provides a better user experience.
|
||||
- cornerstone-core and cornerstone-tools are removed and OHIF v3 is using the new Cornerstone3D rendering library and tools. Moving to Cornerstone3D has enabled us to provide a more robust and stable foundation
|
||||
for 3D rendering and 3D annotations and measurements. In addition, Cornerstone3D provides APIs to load
|
||||
and stream data into a volume which has huge performance benefits.
|
||||
- A new CLI tool to help you create extensions and modes (more [here](../development/ohif-cli.md))
|
||||
- redux store has been removed and replaced with a simpler state management system via React Context API.
|
||||
|
||||
New significant additions that might be useful for you that weren't available in OHIF v2:
|
||||
- [OHIF CLI](../development/ohif-cli.md)
|
||||
- [New Rendering Engine and Toolings](https://www.cornerstonejs.org/)
|
||||
- [Modes](../platform/modes/index.md)
|
||||
- [Mode Gallery](https://ohif.org/modes)
|
||||
- [Layouts](../platform/extensions/modules/layout-template.md)
|
||||
- [Data Sources](../platform/extensions/modules/data-source.md)
|
||||
- [Hanging Protocols](../platform/services/data/HangingProtocolService.md)
|
||||
- [URL Params](../configuration/url.md)
|
||||
|
||||
## Platform/viewer (@ohif/viewer) -> platform/app (@ohif/app)
|
||||
|
||||
|
||||
To ensure proper versioning of OHIF v3, we have made a decision to rename the platform/viewer to platform/app. Previously, the platform/viewer package followed software engineering versioning (currently at v4.12.51). However, going forward, we aim to align the versioning of platform/app with the product version (e.g., v3.4.0, v3.5.0, etc.).
|
||||
|
||||
Since the platform/viewer (@ohif/viewer) is already at v4.12.51, we opted to rename it as platform/app to enable versioning in accordance with the product versioning approach. If you were utilizing any exports from @ohif/viewer, please update them to use @ohif/app instead.
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
:::tip
|
||||
There are various configurations available to customize the viewer. Each configuration is represented by a custom-tailored object that should be used with the viewer to work effectively with a specific server. Here are some examples of configuration files found in the platform/app/public/config directory. Some server-specific configurations that you should be aware are: `supportsWildcard`, `bulkDataURI`, `omitQuotationForMultipartRequest`, `staticWado` (Read more about them [here](../configuration/configurationFiles.md)).
|
||||
|
||||
- default.js: This is our default configuration designed for our main server, which uses a Static WADO datasource hosted on Amazon S3.
|
||||
- local_orthanc.js: Use this configuration when working with our local Orthanc server.
|
||||
- local_dcm4chee.js: This configuration is intended for our local dcm4chee server.
|
||||
- netlify.js: This configuration is the same as default.js and is used for deployment on Netlify.
|
||||
- google.js: Use this configuration to run the viewer against the Google Health API.
|
||||
:::
|
||||
|
||||
OHIF v3 has a new configuration structure. The main difference is that the `servers` is renamed to `dataSources` and the configuration is now asynchronous. Datasources are more abstract and
|
||||
far more capable than servers. Read more about dataSources [here](../platform/extensions/modules/data-source.md).
|
||||
|
||||
- `StudyPrefetcher` is only available in OHIF v3.9 beta and will be available in the next stable 3.9 release.
|
||||
- The `servers` object has been replaced with a `dataSources` array containing objects representing different data sources.
|
||||
- The cornerstoneExtensionConfig property has been removed, you should use `customizationService` instead (you can read more [here](../platform/services/customization-service/customizationService.md))
|
||||
- The maxConcurrentMetadataRequests property has been removed in favor of `maxNumRequests`
|
||||
- The hotkeys array has been updated with different command names and options, and some keys have been removed.
|
||||
- New properties have been added, including `maxNumberOfWebWorkers`, `omitQuotationForMultipartRequest`, `showWarningMessageForCrossOrigin`, `showCPUFallbackMessage`, `showLoadingIndicator`, `strictZSpacingForVolumeViewport`.
|
||||
- you should see if `supportsWildcard` is supported in your server, some servers don't support it and you need to make it false.
|
||||
|
||||
## Modes
|
||||
|
||||
As mentioned briefly above, modes are configuration objects that will be used by the viewer to load extensions.
|
||||
This lets users to be able to use common extensions with different configurations. So as OHIF developers can focus on creating extensions while
|
||||
you as the user can focus on creating modes having your own use case and configuration/initialization logic in mind.
|
||||
|
||||
Separating the configuration from the extensions also makes it so that you can
|
||||
have multiple modes in a single application each focusing on certain tasks. For example, you can have a mode for segmentation which uses specific panels and tools which you don't need
|
||||
for a mode that will be used for reading (read more about modes [here](../platform/modes/index.md))
|
||||
|
||||
:::info
|
||||
Previously, the viewer was designed around registered extensions. If you had a specific use case, you had to duplicate the viewer code and incorporate your customizations through extensions. However, with the introduction of a new layer of abstraction called Modes, you no longer need to fork the viewer.
|
||||
|
||||
Modes provide a flexible approach where you can create your own mode and utilize the necessary extensions within that mode. This eliminates the need for duplicating the viewer codebase.
|
||||
|
||||
Furthermore, Modes offer the advantage of having multiple applications within a single viewer. For instance, you can have a mode dedicated to segmentation tasks and another mode focused on reading. Each mode can have its own unique configuration, initialization logic, layout, tools, and hanging protocols. This ensures a cleaner user interface in the viewer and an improved user experience overall.
|
||||
:::
|
||||
|
||||
Upon entering a mode, the Viewer will register its declared extensions and load them. And you
|
||||
can specify which modules you need from each extension in the mode configuration. For instance
|
||||
|
||||
```js
|
||||
|
||||
const ohif = {
|
||||
layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout',
|
||||
sopClassHandler: '@ohif/extension-default.sopClassHandlerModule.stack',
|
||||
measurements: '@ohif/extension-default.panelModule.measure',
|
||||
thumbnailList: '@ohif/extension-default.panelModule.seriesList',
|
||||
};
|
||||
|
||||
const cs3d = {
|
||||
viewport: '@ohif/extension-cornerstone.viewportModule.cornerstone',
|
||||
};
|
||||
|
||||
const tmtv = {
|
||||
hangingProtocol: '@ohif/extension-tmtv.hangingProtocolModule.ptCT',
|
||||
petSUV: '@ohif/extension-tmtv.panelModule.petSUV',
|
||||
ROIThresholdPanel: '@ohif/extension-tmtv.panelModule.ROIThresholdSeg',
|
||||
};
|
||||
|
||||
function modeFactory({ modeConfiguration }) {
|
||||
routes: [
|
||||
{
|
||||
path: 'tmtv',
|
||||
layoutTemplate: ({ location, servicesManager }) => {
|
||||
return {
|
||||
id: ohif.layout,
|
||||
props: {
|
||||
// leftPanels: [ohif.thumbnailList],
|
||||
rightPanels: [tmtv.ROIThresholdPanel, tmtv.petSUV],
|
||||
viewports: [
|
||||
{
|
||||
namespace: cs3d.viewport,
|
||||
displaySetsToDisplay: [ohif.sopClassHandler],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, we are using the `tmtv` mode which is a mode for reading PET/CT scans
|
||||
and as you can see we are specifying the layout, the panels and the viewports that we need
|
||||
for this mode. The `tmtv` mode is using the `cs3d` extension for rendering and the `ohif` extension. As you see you can reference the modules from the extensions using the `namespace` via strings. So for instance, if you need to use the `viewportModule` from the `@ohif/extension-cornerstone` you can use `@ohif/extension-cornerstone.viewportModule.cornerstone` as the namespace.
|
||||
|
||||
:::tip
|
||||
`ExtensionManager` will register and load the modules from the extensions and make them available to the viewer by their namespaces.
|
||||
:::
|
||||
|
||||
Below you can see a screen shot from the demo showcasing 3 modes for the opened study.
|
||||
|
||||

|
||||
|
||||
:::tip
|
||||
How do I decide certain thing should go inside a mode or extension, Here are some considerations to help you make the decision:
|
||||
|
||||
- **Functionality Scope**: If the functionality is specific to a particular use case or task within your viewer, it is often best suited to be included within a mode. Modes allow you to create customized configurations, layouts, panels, tools, and other components specific to a particular task or workflow. This includes which tool to be active by default, which panels to be displayed, and which layout to be used.
|
||||
|
||||
- **Reusability**: If the functionality can be used across multiple modes, it is better to implement it as an extension. Extensions provide a modular approach where you can encapsulate and share functionality across different modes. For instance, if you have a custom panel that you want to use in multiple modes, you can implement it as an extension and include it in
|
||||
the mode configuration.
|
||||
|
||||
- **Complexity**: If the functionality requires significant customizations, complex logic, or extensive modifications to the viewer's core behavior, it might be better suited as an extension.
|
||||
|
||||
- **New Service**: If you are writing a new service, it is preferable to implement it as an extension. Services are used to provide a common interface for interacting with external systems and data sources.
|
||||
There is a new way to register new services which are extendible by other extensions.
|
||||
|
||||
Remember that there is no strict rule for deciding between modes and extensions. It's a matter of understanding the specific requirements of your application.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
## Routes
|
||||
|
||||
In OHIF v2 a study was loaded and mounted on `/viewer/:studyInstanceUID` route. In OHIF v3
|
||||
we have reworked the route registration to enable more sophisticated routing. Now, Modes are tied to specific routes in the viewer, and multiple modes/routes can be present within a single application, making "routes" configuration the most important part of mode configuration.
|
||||
|
||||
- Routes with a dataSourceName: `{mode.id}/{dataSourceName}`
|
||||
- Routes without a dataSourceName: `{mode.id}` which uses the default dataSourceName
|
||||
|
||||
This makes a mode flexible enough to be able to connect to multiple datasources
|
||||
without rebuild of the app for use cases such as reading from one PACS and
|
||||
writing to another.
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Can I register a custom route to OHIF v3?
|
||||
</summary>
|
||||
|
||||
Yes, you can take advantage of the customizationService and register your own routes.
|
||||
see [custom routes](../platform/services/customization-service/customRoutes.md)
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## DICOM Endpoints
|
||||
|
||||
In OHIF v3 there is a new end point that your DICOM server should be able to respond to
|
||||
`WADO-RS GET studies/{studyInstanceUid}/series`
|
||||
|
||||
This is used in the viewer for fetching the series list for a study to use for the hanging protocol.
|
||||
|
||||
## LifeCycle Hooks
|
||||
|
||||
OHIF v2 had `preRegistration` hook for extensions for initialization. In OHIF v3 you have
|
||||
even more control using `onModeEnter` and `onModeExit` hooks on the extensions and on the modes.
|
||||
|
||||
- `preRegistration`: is called before the extension is registered to the viewer. So very early in the lifecycle of the viewer.
|
||||
- `onModeEnter` is called when the mode is entered (component on the route is mounted, e.g., when you click on the mode to enter it)
|
||||
- `onModeExit` is called when the mode is exited (component on the route is unmounted, e.g., when you navigate back to the worklist)
|
||||
|
||||
## Extensions
|
||||
|
||||
Since extensions in OHIF v2 were the main way of customizing the viewer, we will spend some time
|
||||
below to explain how you can migrate your extensions to OHIF v3.
|
||||
|
||||
### Default Extension
|
||||
|
||||
Lots of common functionalities in the platform/core has been moved inside
|
||||
the `@ohif/extension-default` extension. This extension is loaded by default
|
||||
in the viewer and it provides the following functionalities:
|
||||
|
||||
- common datasources such as DICOMWeb, DICOMLocal, and DICOMJSON datasource.
|
||||
- default measurement panel and panel study browser
|
||||
- common toolbar button layouts
|
||||
- common hanging protocol configurations
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
how can I integrate to my google health api? is there support for that?
|
||||
</summary>
|
||||
|
||||
You can right now, take a look into our google configuration that we use for our QA located at
|
||||
`config/google.js`. Also we have some exciting UI changes coming up for the next release
|
||||
that will make it easier to integrate with google health api.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Is there any recommendation for PACS integration
|
||||
</summary>
|
||||
|
||||
You can take a look at open source PACS such as dcm4chee or orthanc. We have support for them. Also
|
||||
we have a new static wado datasource that you can use to take benefit of new deduplicated metadata
|
||||
and caching features.
|
||||
|
||||
</details>
|
||||
|
||||
### Cornerstone Extension
|
||||
|
||||
In OHIF v2, the Cornerstone extension provided modules like Cornerstone ViewportModule, ToolbarModule, and CommandsModule for controlling viewport actions.
|
||||
It relied on `react-cornerstone-viewport` for rendering viewports, `cornerstone-tools` for tools, and `cornerstone-core` for core functionalities.
|
||||
|
||||
However, in OHIF v3, there have been significant changes. The rendering and tooling logic has been migrated to a new library called [`Cornerstone3D`](https://github.com/cornerstonejs/cornerstone3D-beta/). This means that all viewport rendering and tool functionalities are now handled by Cornerstone3D.
|
||||
|
||||
Additionally, in OHIF v3, the native support for 3D functionalities previously provided by the `vtk` extension has been integrated into Cornerstone3D. As a result, any vtkjs logic is encapsulated on CS3D. Things now are much more cleaner and simpler.
|
||||
|
||||
To migrate from OHIF v2 to OHIF v3:
|
||||
|
||||
#### Loading
|
||||
|
||||
Previously we used `cornerstone-wado-image-loader` for loading images. However, we have fully switched the a new
|
||||
library called `@cornerstonejs/dicom-image-loader` which is a fork of `cornerstone-wado-image-loader` with typescript support and bug fixes.
|
||||
We have deprecated `cornerstone-wado-image-loader` and you should also switch to `@cornerstonejs/dicom-image-loader` as well.
|
||||
The process is very simple, you can follow this [PR](https://github.com/OHIF/Viewers/pull/3339) to see how we have migrated.
|
||||
|
||||
There is also a new loader and package `@cornerstonejs/streaming-image-volume-loader`, which provides streaming of the image data
|
||||
into a volume using web workers and web assembly. You can look into the cornerstone documentation and read more about the
|
||||
volumeViewport and volumeLoader.
|
||||
|
||||
|
||||
#### Rendering
|
||||
|
||||
The significant difference between cornerstone-core and cornerstone3D is that cornerstone3D fully utilizes
|
||||
[vtk.js](https://kitware.github.io/vtk-js/) for rendering, however in cornerstone-core we used a mix of webGL and vtk
|
||||
for rendering. While you don't need to do a migration for this, you should be aware that the rendering is now fully performed in the
|
||||
world coordinate system and the image is placed in the world coordinate system using the `imagePositionPatient` and `imageOrientationPatient`
|
||||
attributes of the image. This means that you can now share the tool states between multiple viewports and you can also
|
||||
use the same tool states for 2D and 3D viewports.
|
||||
|
||||
:::tip
|
||||
|
||||
In OHIF v3, we have removed the OHIF's vtk extension and migrated all the 3D functionalities to Cornerstone3D.
|
||||
|
||||
Also you need to remove any dependencies on `react-cornerstone-viewport`, `cornerstone-tools`, and `cornerstone-core`.
|
||||
:::
|
||||
|
||||
#### Tools
|
||||
|
||||
If you don't have any custom tools, you most likely won't need to make any changes as have tried
|
||||
to migrate all the tools from `cornerstone-tools` to Cornerstone3D (except `ROIWindowLevel` which is work in progress right now).
|
||||
|
||||
Cornerstone3D has moved the coordinate system of tools to the world coordinate system enabling sharing
|
||||
tool states between multiple viewports, and as a result the toolData is now stored in the world coordinate system as well.
|
||||
So to migrate your tools, you will need to update your toolData to be stored in the world coordinate system. You can look
|
||||
into the simplest tool for instance LengthTool in both `cornerstone-tools` and `cornerstone3D` to see the difference.
|
||||
|
||||
|
||||
|
||||
By following these steps, you can leverage the improved rendering and tooling capabilities of Cornerstone3D and eliminate the need for the old ohif's vtk extension in OHIF v3.
|
||||
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Is there any name standard for modes and extensions?
|
||||
</summary>
|
||||
|
||||
No naming standard, you can have your organization name as a prefix for your modes and extensions as we
|
||||
do for ohif (`@ohif/extension-*` and `@ohif/mode-*`).
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
What happens if I have create a mode with same name as existing one
|
||||
</summary>
|
||||
|
||||
You shouldn't. Modes are configuration objects that you can simply. There is no real use case
|
||||
for creating a mode with same name as existing one. If you do so, the last one will override the previous one
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
How to remove an "core" extension/mode?
|
||||
</summary>
|
||||
|
||||
You can use the OHIF cli tool to add/remove/link and unlink extensions and modes. You can find more information
|
||||
about the cli tool [here](../development/ohif-cli.md)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
If I have vtkjs implementation how can I port it? Should I create a specific extension for that?
|
||||
</summary>
|
||||
|
||||
Cornerstone3D has support for some vtk.js actor and mappers including imageData, polyData and volume. If you have another
|
||||
implementation of vtk.js actor or mapper, you might be able to use `viewport.addActor` to include it in the rendering
|
||||
pipeline, but depending on the implementation and how much it interfere with the cornerstone3D rendering pipeline, you might
|
||||
not get the expected result.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
### DICOM Segmentation & DICOM RT
|
||||
|
||||
In OHIF v3, the equivalent extensions for RT and SEG exists with similar logic, but with various improvements such as
|
||||
enhanced ui/ux for segmentation panel, faster loading and interaction, and better support for multiple viewports,
|
||||
animations for jump to segment, volumetric rendering, and more. Additionally, OHIF v3 introduces new functionalities with the SEG Viewport and RT Viewport.
|
||||
|
||||
:::tip
|
||||
|
||||
In OHIF v3, Segmentation objects
|
||||
are loading using the frame of reference by default which means that if there are two viewports that are using the same frame of reference,
|
||||
if you load a segmentation (labelmap or RT) which lives in the same frame of reference, it will be loaded in both viewports.
|
||||
:::
|
||||
|
||||
When loading a series that contains SEG (Segmentation) or RT (RT Structure Set) data, the viewport will automatically
|
||||
switch to the corresponding SEG or RT viewport. The user will then be prompted to decide whether to load the segmentation
|
||||
or RT structure set into the viewer. This new feature addresses a common use case in which there are multiple segmentation
|
||||
series in a study, and the user only wants to load specific ones. In OHIF v3, the Segmentations are all loaded
|
||||
as 3D volumes and as a result a volume viewport is used to display them. (Stack Segmentation in Cornerstone3D is still a
|
||||
work in progress.)
|
||||
|
||||
In OHIF v2, the user had to load all the segmentation series and then manually delete the ones they didn't want to see.
|
||||
However, in OHIF v3, the user has more control. The temporary SEG or RT viewport does not immediately load (hydrate)
|
||||
the segmentation or RT structure set. Instead, the user can decide which ones to load, reducing unnecessary
|
||||
loading and providing a more efficient workflow.
|
||||
|
||||
This enhancement in OHIF v3 allows users to selectively load specific segmentations or RT structure sets,
|
||||
improving the usability and efficiency of the viewer when working with multiple SEG or RT series.
|
||||
|
||||
|
||||
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Can I load one seg in one viewport and another in another viewport?
|
||||
</summary>
|
||||
|
||||
If there is another viewport in the grid that is using the same frame of reference, the segmentation will be loaded in that viewport as well.
|
||||
|
||||
However, since we split the concept of `load` (`hydration`) and `preview`, you can use the preview (not load), which
|
||||
makes sure the SEG is contained within the viewport, but it is not hydrated so you cannot edit it.
|
||||
|
||||
In future however, we will add more controls over, hiding the segmentation in other viewports via UI, however, you can
|
||||
right now do it via code.
|
||||
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Does it support nifti?
|
||||
</summary>
|
||||
|
||||
Nifit support for both image and segmentation is coming soon. We are working on it.
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
### DICOM SR
|
||||
|
||||
In OHIF v2, DICOM SR functionality was integrated into the Cornerstone extension. However, in OHIF v3, DICOM SR is now a separate extension. The DICOM SR extension in OHIF v3 retains the same loading and hydrating logic using dcmjs adapters. Additionally, it introduces a new type of viewport called the SR Viewport, which is used to display SR data.
|
||||
|
||||
Similar to the temporary SEG and RT viewports, when a SR display set is selected in OHIF v3, the user is prompted to decide whether to load the SR data into the viewer and initiate the tracking. The SR viewport allows the user to switch between different measurements within the SR instance by utilizing the arrow buttons located at the top of the viewport.
|
||||
|
||||
:::tip
|
||||
This separation of DICOM SR into its own extension in OHIF v3 provides a dedicated viewport type for SR data and offers enhanced functionality for interacting with SR measurements within the viewer.
|
||||
:::
|
||||
|
||||
|
||||
### DICOM Tag Browser
|
||||
|
||||
In OHIF v2, the DICOM Tag Browser was a separate extension that provided a dedicated user interface for exploring DICOM tags. However, in OHIF v3, we have integrated the DICOM Tag Browser functionality into the `default` extension.
|
||||
|
||||
The DICOM Tag Browser is a powerful tool for debugging and inspecting DICOM metadata, and we wanted to make it easily accessible to users. As a result, it is now available as a toolbar icon within the `default` extension. This allows users to conveniently access the DICOM Tag Browser directly from the toolbar, eliminating the need for a separate extension.
|
||||
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Now that dicom tag is integrated back to default extension, how can I port my code that was implemented in the old extension? Should I create an extension or change directly into default?
|
||||
</summary>
|
||||
|
||||
If you have a custom tag browser, you have two options, either modify the default tag browser (if you think the features
|
||||
you added is useful for everyone, feel free to open a PR!), or create your own extension with your custom tag browser
|
||||
which then you can add to the toolbar.
|
||||
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
### DICOM HTML
|
||||
|
||||
Since we have added graphical overlay of DICOM SR in OHIF v3, we have temporarily downgraded the priority of displaying DICOM HTML within the viewer. While DICOM HTML support is not available in the current version of OHIF v3, we acknowledge its importance and plan to reintroduce this functionality in future updates.
|
||||
|
||||
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
is there any easy way for supporting my own dicom html viewer? Should I use extension?
|
||||
</summary>
|
||||
|
||||
Yes, you can write your own sopClassHandler and custom viewport in your custom extensions.
|
||||
After, you need to associate that with the viewport that you
|
||||
will use in the mode configuration, this way when that sopClassUID is requested it will use your custom viewport.
|
||||
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
### DICOM Microscopy
|
||||
|
||||
In OHIF v2, the DICOM microscopy engine was based on an older version of the [DICOM microscopy viewer](https://github.com/ImagingDataCommons/dicom-microscopy-viewer) maintained by our friends at IDC (Imaging Data Commons). However, in OHIF v3, we have upgraded to the latest version of the DICOM microscopy viewer. This new version offers significant improvements in terms of robustness and performance, providing users with an enhanced microscopy viewing experience.
|
||||
|
||||
One notable addition in the latest DICOM microscopy viewer is the support for annotations within the whole slide images (SM images). This feature allows users to annotate and mark specific regions of interest directly within the microscopy images.
|
||||
|
||||
:::tip
|
||||
Looking ahead, our future plans include adding DICOM SR (Structured Reporting) support for export of annotations in microscopy images. While we will enhance our support for SM images (color profiles etc.), we recommend utilizing the [SLIM Viewer](https://github.com/ImagingDataCommons/slim) developed by IDC for more sophisticated microscopy use cases.
|
||||
:::
|
||||
|
||||
|
||||
|
||||
## Extension Modules
|
||||
|
||||
|
||||
v3 Extension is likely the same as in v2. Extensions can (like before) have
|
||||
modules exported via `get{ModuleName}Module` (e.g., `getViewportModule`).
|
||||
|
||||
:::info
|
||||
There are new
|
||||
types of modules that can be exported from extensions (such as `HangingProtocolModule`, `LayoutModule`, read more about
|
||||
modules in v3 [here](../platform/extensions/index.md)).
|
||||
:::
|
||||
|
||||
The main difference between v3 and v2 is that exported modules were represented as a single object, whereas in OHIF v3, they are
|
||||
represented as an array of objects, each having a name property. This change was implemented to
|
||||
enable extensions to export multiple named submodules, providing more flexibility and modularity.
|
||||
|
||||
To access these modules in OHIF v3, you can use the namespace provided by the `ExtensionManager`. For example, consider the following code snippet
|
||||
|
||||
|
||||
```js
|
||||
getUtilityModule({ servicesManager }) {
|
||||
return [
|
||||
{
|
||||
name: 'common',
|
||||
exports: {
|
||||
getCornerstoneLibraries: () => {
|
||||
return { cornerstone, cornerstoneTools };
|
||||
},
|
||||
getEnabledElement,
|
||||
dicomLoaderService,
|
||||
registerColormap,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'core',
|
||||
exports: {
|
||||
Enums: cs3DEnums,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'tools',
|
||||
exports: {
|
||||
toolNames,
|
||||
Enums: cs3DToolsEnums,
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
```
|
||||
|
||||
|
||||
In this example, the extension is exporting multiple submodules named 'common',
|
||||
'core', and 'tools'. To access the 'common' submodule provided by the @ohif/extension-cornerstone extension,
|
||||
you can use the following code:
|
||||
|
||||
```js
|
||||
extensionManager.getModuleEntry(
|
||||
'@ohif/extension-cornerstone.utilityModule.common'
|
||||
);
|
||||
```
|
||||
|
||||
This allows you to access the specific submodule provided by the extension and utilize its functionalities within your application.
|
||||
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
How can I have a lazy-loaded component and import it from another extension?
|
||||
</summary>
|
||||
|
||||
If an extension is exporting a component, you can import it from another extension. For example, if you have an extension that exports a component called `MyComponent`, you can import it from another extension like this:
|
||||
|
||||
```js
|
||||
import { MyComponent } from '@ohif/extension-my-extension';
|
||||
```
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
### ToolbarModule
|
||||
|
||||
In OHIF v2, the toolbarModule was used to add buttons to the toolbar. For example, the following code snippet demonstrates adding a zoom tool button to the toolbar:
|
||||
|
||||
In OHIF v2
|
||||
|
||||
```js
|
||||
{
|
||||
id: 'Zoom',
|
||||
label: 'Zoom',
|
||||
icon: 'search-plus',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'Zoom' },
|
||||
},
|
||||
```
|
||||
|
||||
However, in OHIF v3, the toolbarModule has been repurposed to define different button types. For instance, OHIF v3 introduces the ohif.radioGroup and ohif.splitButton button types, which provide more flexibility in defining toolbar buttons for each mode.
|
||||
|
||||
|
||||
|
||||
```js
|
||||
{
|
||||
name: 'ohif.radioGroup',
|
||||
defaultComponent: ToolbarButton,
|
||||
clickHandler: () => {},
|
||||
},
|
||||
{
|
||||
name: 'ohif.splitButton',
|
||||
defaultComponent: ToolbarSplitButton,
|
||||
clickHandler: () => {},
|
||||
},
|
||||
```
|
||||
|
||||
To use these button types within your modes, you can define the buttons in your mode's configuration. In the onModeEnter hook, you can add the defined buttons to the toolbar using the toolbarService. Here's an example of how to add buttons to the toolbar:
|
||||
|
||||
|
||||
|
||||
```js
|
||||
// toolbar button
|
||||
{
|
||||
id: 'Zoom',
|
||||
type: 'ohif.radioGroup',
|
||||
props: {
|
||||
type: 'tool',
|
||||
icon: 'tool-zoom',
|
||||
label: 'Zoom',
|
||||
commands: _createSetToolActiveCommands('Zoom'),
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
and in `onModeEnter`
|
||||
|
||||
```js
|
||||
onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => {
|
||||
const {
|
||||
toolbarService,
|
||||
toolGroupService,
|
||||
} = servicesManager.services;
|
||||
|
||||
// Init tool groups (see cornerstone3D for more details)
|
||||
initToolGroups(extensionManager, toolGroupService, commandsManager);
|
||||
|
||||
toolbarService.addButtons(toolbarButtons);
|
||||
toolbarService.createButtonSection('primary', [
|
||||
'MeasurementTools',
|
||||
'Zoom',
|
||||
'WindowLevel',
|
||||
'Pan',
|
||||
'Capture',
|
||||
'Layout',
|
||||
'Crosshairs',
|
||||
'MoreTools',
|
||||
]);
|
||||
},
|
||||
```
|
||||
|
||||
By using the updated toolbarModule in OHIF v3, you can define and add toolbar buttons specific to each mode, providing greater flexibility and customization options for the toolbar configuration.
|
||||
|
||||
An example of split button icon in v3 is shown below
|
||||
|
||||

|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Is the tool state shared between two different modes?
|
||||
</summary>
|
||||
No, the tool state is not shared between different modes in OHIF v3. Each mode operates independently and maintains its own tool state.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
I have a custom icon. How can I add it to the toolbar?
|
||||
</summary>
|
||||
|
||||
You need to first register it via `addIcon` in the src/components/Icon, and then you can
|
||||
referenced it by name in the toolbar configuration for mode
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Can I change the toolbar's location? Can I add a secondary toolbar?
|
||||
</summary>
|
||||
Not in our default layout, but you can write your own layout in your custom extension
|
||||
and use it instead of the default one.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Can I have different tool sets for each viewport?
|
||||
</summary>
|
||||
|
||||
We don't have fully support for this yet, but we have plans for it. Basically, the plan
|
||||
is to use the viewport action bar in the top of the viewport to provide viewport-specific
|
||||
tool sets.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Are all tools from v2 support in v3?
|
||||
</summary>
|
||||
|
||||
Almost all with the exception of ROIWindow, but we have plans to add it in the future. However, there are
|
||||
much more tools in v3 that are not available in v2 such as referenceLines, Stack Image Sync, and
|
||||
Calibration tool.
|
||||
</details>
|
||||
|
||||
### CommandsModule
|
||||
|
||||
The structure of the commands module is the same as before. The only difference is that
|
||||
we use Cornerstone3D for rendering and tools. So, if you have a custom command that you were
|
||||
using in the v2, you need to migrate it to the new Cornerstone3D API.
|
||||
|
||||
You can visit the migration guide for cornerstone [here](https://www.cornerstonejs.org/docs/migrationGuides).
|
||||
|
||||
### PanelModule
|
||||
|
||||
Previously in OHIF v2 you had
|
||||
|
||||
```js
|
||||
return {
|
||||
menuOptions: [
|
||||
{
|
||||
icon: 'list',
|
||||
label: 'Segmentations',
|
||||
target: 'segmentation-panel',
|
||||
stateEvent: SegmentationPanelTabUpdatedEvent,
|
||||
},
|
||||
],
|
||||
components: [
|
||||
{
|
||||
id: 'segmentation-panel',
|
||||
component: ExtendedSegmentationPanel,
|
||||
},
|
||||
],
|
||||
defaultContext: ['VIEWER'],
|
||||
};
|
||||
```
|
||||
|
||||
but in OHIF v3 you have
|
||||
|
||||
```js
|
||||
return [
|
||||
{
|
||||
name: 'panelSegmentation',
|
||||
iconName: 'tab-segmentation',
|
||||
iconLabel: 'Segmentation',
|
||||
label: 'Segmentation',
|
||||
component: wrappedPanelSegmentation,
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
How can I add my own custom panel?
|
||||
</summary>
|
||||
To add your own custom panel in OHIF v3, you can follow these steps:
|
||||
|
||||
- Create a new React component that represents your custom panel.
|
||||
- Provide it in the getPanelModule of your extension.
|
||||
- Inside your mode, add the panel namespace to the mode's configuration for the layout module.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
How to enhance an existing panel?
|
||||
</summary>
|
||||
To enhance an existing panel in OHIF v3, you can create a new React component that extends or wraps the existing panel component. In your enhanced component, you can add additional functionality, modify the appearance, or incorporate new features specific to your use case. You can also look into the customizationService to see
|
||||
how you can use the registered points to customize the panel.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
How to change the order of appearance of panels?
|
||||
</summary>
|
||||
To change the order of appearance of panels in OHIF v3, you can modify the panel layout configuration in the mode configuration. The panel layout configuration specifies the order and arrangement of panels within the viewer interface.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Is there a way to change the viewer layout to present right panels on the left and the toolbar on the right?
|
||||
</summary>
|
||||
Not with our default layout which the default extension provides. However, you can write a new layout and provide it
|
||||
in the `getLayoutModule` which you can reference in the `layout` property of the mode configuration.
|
||||
</details>
|
||||
|
||||
### SopClassHandlerModule
|
||||
|
||||
The least changed module is the SopClassHandlerModule, although this now returns
|
||||
an array instead of a single instance. The purpose of this module is to create
|
||||
a list of displaySets based on the metadata. OHIF App uses this module to
|
||||
create one or more displaySets for each series.
|
||||
The displaySet is then used to then get assigned
|
||||
on each viewport and the viewport renders the image.
|
||||
|
||||
The `DisplaySet` created by the handler can have a member function `addInstances`
|
||||
which will update the display set with new SOP instance data, allowing the
|
||||
preservation of the display set UID when required.
|
||||
|
||||
Multiple display sets will be returned when different parts of the series are
|
||||
to be shown separately, for example, to split scout images from volume images.
|
||||
|
||||
|
||||
### ViewportModule
|
||||
|
||||
In OHIF v3, viewports are tied to series of SOP Class UIDs (sopClassUIDs). Each extension provides its own viewport for specific SOP Class UIDs, and you can choose which viewports and SOP Class UIDs your mode can handle in the mode configuration.
|
||||
|
||||
For example, in the longitudinal mode configuration, there are multiple viewports specified along with their associated SOP Class Handler Modules:
|
||||
|
||||
|
||||
```js
|
||||
viewports: [
|
||||
{
|
||||
namespace: '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked',
|
||||
displaySetsToDisplay: [ '@ohif/extension-default.sopClassHandlerModule.stack'],
|
||||
},
|
||||
{
|
||||
namespace: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr',
|
||||
displaySetsToDisplay: [ '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr'],
|
||||
},
|
||||
// additional viewports
|
||||
],
|
||||
```
|
||||
|
||||
In this example, there are six viewports specified, each identified by a unique namespace. Each viewport is associated with a specific SOP Class Handler Module through the displaySetsToDisplay property.
|
||||
|
||||
To add a new viewport, you would need to create a new SOP Class Handler Module and a new Viewport Module. The SOP Class Handler Module handles the logic for loading and handling specific SOP Class UIDs, while the Viewport Module defines the rendering and behavior of the viewport.
|
||||
|
||||
In addition to the viewports, the mode configuration should include and register each SOP Class Handler Module that your mode can handle:
|
||||
|
||||
|
||||
```js
|
||||
sopClassHandlers: [
|
||||
'@ohif/extension-default.sopClassHandlerModule.stack',
|
||||
'@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr',
|
||||
'@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video',
|
||||
'@ohif/extension-dicom-pdf.sopClassHandlerModule.dicom-pdf',
|
||||
'@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg',
|
||||
'@ohif/extension-cornerstone-dicom-rt.sopClassHandlerModule.dicom-rt',
|
||||
]
|
||||
```
|
||||
|
||||
Here, each SOP Class Handler Module is specified with its namespace.
|
||||
|
||||
By configuring the viewports and SOP Class Handler Modules in your mode, you can define how your mode interacts with different types of DICOM data and specify the appropriate rendering and behavior for each SOP Class UID.
|
||||
|
||||
## Metadata Store and Provider
|
||||
|
||||
In OHIF v2, we utilized the `platform/core/classes/metadata` module, which included the classes StudyeMetadata, SeriesMetadata, and InstanceMetadata for storing metadata. However, in OHIF v3, we have replaced these classes with a more versatile metadata store called `DICOMMetadataStore`. This new metadata store is used by each datasource to store the metadata associated with studies, series, and instances. The DICOMMetadataStore API allows you to add study/series/instance metadata to the store and retrieve metadata from it.
|
||||
|
||||
Although we have transitioned to using DICOMMetadataStore as the primary metadata storage mechanism, you still have access to OHIF's MetadataProvider. The MetadataProvider can be found in the same `platform/core/classes` location. The MetadataProvider is internally used to retrieve instance-based metadata based on UIDs, perform queries, and includes some legacy support for older versions of the loading logic.
|
||||
|
||||
|
||||
## Build
|
||||
|
||||
We have recently transitioned from bundling all the extensions and the viewer into a single bundle to a more modular approach. In this new approach, the required extensions are dynamically loaded inside a mode as needed. This change brings several advantages, including:
|
||||
|
||||
- Faster build time: Bundling only the necessary extensions reduces the build time, as you no longer need to bundle all extensions upfront.
|
||||
- Smaller bundle size: By loading extensions on-demand, the initial bundle size is reduced, resulting in faster page load times for users.
|
||||
- Faster reload for development: During development, the incremental build process allows for faster reloads, improving developer productivity.
|
||||
|
||||
This new approach does not impact the deployment process of the viewer. You can continue to follow our deployment guides, such as the [Build for Production](../deployment/build-for-production.md) guide, to deploy the viewer effectively.
|
||||
|
||||
|
||||
### Script tag usage of the OHIF viewer
|
||||
|
||||
With the transition to more advanced visualization, loading, and rendering techniques using WebWorkers, WASM, and WebGL, the script tag usage of the OHIF viewer has been deprecated. However, if you still prefer to use the script tag usage, it is theoretically possible to bundle all the required dependencies and utilize the script tag approach.
|
||||
|
||||
An alternative option for script tag usage is to employ an `iframe`. You can utilize the iframe element to load the OHIF viewer and establish communication with it using the postMessage API. This allows you to exchange messages and data between the parent window and the iframe, enabling interaction and coordination with the OHIF viewer embedded within the iframe.
|
||||
|
||||
Please note that while these alternatives exist, we recommend utilizing modern development practices and incorporating OHIF viewer within your application using a more modular and integrated approach, such as leveraging bundlers, and import statements to ensure better maintainability, extensibility, and compatibility with the OHIF ecosystem.
|
||||
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
I use OHIF v2 in an iframe. Is there any impediment for v3?
|
||||
</summary>
|
||||
No, there is no impediment for using OHIF v3 in an iframe. OHIF v3 is designed to be compatible with iframe usage, allowing you to embed the viewer within other applications or web pages seamlessly. You can still communicate with the OHIF v3 viewer using the postMessage API to exchange information and trigger actions between the parent window and the embedded iframe.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Does the build support dynamic imports? How can I use it?
|
||||
</summary>
|
||||
Yes, the build configuration in OHIF v3 supports dynamic imports. Dynamic imports allow you to asynchronously load modules or components on demand, improving performance and reducing the initial bundle size. In fact we are using this method for our viewport components. In general you can:
|
||||
|
||||
```
|
||||
import('path/to/module').then((module) => {
|
||||
// Use the imported module here
|
||||
}).catch((error) => {
|
||||
// Handle any error that occurs during dynamic import
|
||||
});
|
||||
```
|
||||
|
||||
By using dynamic imports, you can selectively load modules or components at runtime when they are needed, enhancing the efficiency and responsiveness of your application. However, note
|
||||
that these components must be available at BUILD time, and cannot be updated after
|
||||
build.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>
|
||||
How can I enhance the existing build to consume my own webpack script?
|
||||
</summary>
|
||||
You can't enhance the existing build to consume your own webpack script as of now. However, you can
|
||||
modify the webpack.base.js and webpakc.pwa.js files to add your own webpack script/modules if needed.
|
||||
|
||||
</details>
|
||||
|
||||
## UI Components
|
||||
|
||||
Migrating to Tailwind CSS, OHIF v3 is now able to have a component-oriented styling approach, speeding up development, ensuring consistent styling, making responsive design easier, and enabling extensibility
|
||||
|
||||
We have gone through extensive re-design of each part of the UI, and we have also added new components to the OHIF viewer.
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
I have a huge complex styles using native CSS, how can I reuse them?
|
||||
</summary>
|
||||
You can leverage the power of Tailwind CSS (https://TailwindCSS.com/) in OHIF v3 to reuse your existing styles. Tailwind CSS is a utility-first approach, allowing you to create reusable CSS classes by composing utility classes together. You can migrate your existing styles to Tailwind CSS by breaking them down into utility classes and utilizing the extensive set of predefined utilities provided by Tailwind CSS.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
How can I change the page color from being purplish to blueish?
|
||||
</summary>
|
||||
In OHIF v3, you can easily modify the page color by customizing the Tailwind CSS configuration. You can locate the tailwind.config.js file in your project and update the theme section, specifically the colors property, to define your desired color palette. By adjusting the values for the colors, you can change the page color to any shade of blue or other colors according to your preference.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Can I have my own React UI component working in the application? Is there a way to use the current build for it as well?
|
||||
</summary>
|
||||
Yes, you can integrate your own React UI components seamlessly into the OHIF v3 application. You can even have external
|
||||
UI dependencies and by creating your own component inside your extensions and importing it into the application, you can
|
||||
use it as if it was part of the OHIF v3 application.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
How can I replace the existing component ui/tooltip?
|
||||
</summary>
|
||||
You need to write your own component, and inside your mode layout you can replace the existing component with your own.
|
||||
As of now, for the tooltip component, you need to use the customizationService to customize it; however, the customizationService
|
||||
requires a registration of the to-be-customized property before you can customize it. Read more about customizationService.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
How can I add/consume logos/images/icons?
|
||||
</summary>
|
||||
|
||||
For logos you can use the whiteLabelling inside the configuration. However, if you need a more complex UI for your toolbar
|
||||
you need to create you own layout. See `getLayoutModule`.
|
||||
|
||||
</details>
|
||||
|
||||
## Redux store
|
||||
|
||||
In OHIF v3, we made the decision to move away from the Redux store and adopt a new approach utilizing React context providers and services with a pub/sub pattern. This shift was driven by the need for a more flexible and scalable architecture that better aligns with the plugin and extension system of OHIF. This offers
|
||||
|
||||
- Modularity and Scalability: Context providers and services enable a modular architecture for easy addition and removal of plugins and extensions.
|
||||
- Reduced Boilerplate: eliminate Redux boilerplate for simpler development.
|
||||
- Flexible Pub/Sub Pattern: Services provide a pub/sub pattern for inter-component communication.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>
|
||||
Now that redux store is gone, how can I access the user information?
|
||||
</summary>
|
||||
|
||||
You can use the `authenticationService` for that purpose.
|
||||
|
||||
</details>
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
id: index
|
||||
title: Migration Guides Overview
|
||||
summary: Introduction to OHIF migration guides covering the upgrade paths between different versions of the platform, with links to version-specific guides for migrating from one OHIF version to another.
|
||||
---
|
||||
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
|
||||
|
||||
# Migration Guides
|
||||
|
||||
Based on the version you are migrating from, you can find the migration guide for the latest version of the platform.
|
||||
|
||||
<DocCardList items={useCurrentSidebarCategory().items}/>
|
||||
Reference in new issue
Block a user