init: sudah ganti logo, hilangin setting, dan investigational use dialog

This commit is contained in:
one
2025-03-06 11:32:45 +07:00
commit 8f31d4ed41
2857 changed files with 355646 additions and 0 deletions

View File

@@ -0,0 +1,288 @@
---
id: 0-general
title: General
---
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-).
---
## 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,
},
},
```
---
## SidePanel auto switch if open
In `basic viewer` mode, if 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 occur if the side panel is closed. To enable or disable this feature, adjust your mode configuration accordingly.
```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>

View File

@@ -0,0 +1,48 @@
---
id: seg-new-arch
title: New Architecture
---
## 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

View File

@@ -0,0 +1,433 @@
---
id: seg-api
title: SegmentationService API
---
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
```

View File

@@ -0,0 +1,189 @@
---
id: seg-representation
title: 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>

View File

@@ -0,0 +1,215 @@
---
id: seg-creation
title: Segmentation Creation
---
## 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>
---

View File

@@ -0,0 +1,193 @@
---
id: seg-service-mod
title: SegmentationService Modifications
---
---
## 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>
---

View File

@@ -0,0 +1,362 @@
---
id: seg-style
title: SegmentationService Style
---
## 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>
---

View File

@@ -0,0 +1,374 @@
---
id: seg-other
title: Other Changes
---
## 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>

View File

@@ -0,0 +1,11 @@
---
id: segmentation-index
title: Segmentation
sidebar_position: 1
---
:::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!
:::

View File

@@ -0,0 +1,33 @@
---
id: 2-renamings
title: Renamings
sidebar_position: 2
---
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`.

View File

@@ -0,0 +1,40 @@
---
id: 3-data-sources
title: Data Sources
sidebar_position: 3
---
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.

View File

@@ -0,0 +1,43 @@
---
title: Measurements
---
## 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
---

View File

@@ -0,0 +1,40 @@
---
id: viewport-action-corner
title: ViewportActionCorner
---
## 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
});
```

View File

@@ -0,0 +1,343 @@
---
id: state-sync-service
title: StateSyncService
---
## 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();
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.
```

View File

@@ -0,0 +1,18 @@
---
id: 6-rtstruct
title: RTSTRUCT
sidebar_position: 6
---
# 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

View File

@@ -0,0 +1,149 @@
---
title: UI
---
## 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.
---

View File

@@ -0,0 +1,120 @@
---
title: Refactoring
---
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');
},
},
]);
```

View File

@@ -0,0 +1,99 @@
---
title: Other Changes
---
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.

View File

@@ -0,0 +1,7 @@
---
id: 3p8-to-3p9
title: 3.8 -> 3.9
sidebar_position: 1
---
Here are the changes you need to make to migrate from 3.8 to 3.9.