OHIF with Cornerstone3D 2.0 (#4347)

This commit is contained in:
Alireza authored and GitHub committed 2024-11-06 07:15:27 -05:00
1 parent 8981a61b8c
commit 7ce7127d7c
803 files changed
+32760 -15893

No files matched your search

@@ -185,17 +185,6 @@ For DICOM video and PDF it has been found that Orthanc delivers multipart, while
To learn more about how you can configure the OHIF Viewer, check out our
[Configuration Guide](../index.md).
### DICOM Upload
See the [`dicomUploadEnabled`](#dicomuploadenabled) data source configuration option.
Don't forget to add the customization to the config as well
```js
customizationService: {
dicomUploadComponent:
'@ohif/extension-cornerstone.customizationModule.cornerstoneDicomUploadComponent',
},
```
### DICOM PDF
See the [`singlepart`](#singlepart) data source configuration option.
@@ -0,0 +1,191 @@
---
sidebar_position: 13
sidebar_label: Web Workers
---
# Web Worker Implementation Guide
## Overview
Web Workers enable running computationally intensive tasks in background threads without blocking the UI. This guide explains how to implement them step by step.
## Basic Setup
### 1. Create Your Worker File
First, create a worker file with your background tasks:
```javascript
// myWorker.js
import { expose } from 'comlink';
const obj = {
// Simple task
basicCalculation({ data }) {
// Your computation here
return result;
},
// Task with progress updates
longRunningTask({ data }, progressCallback) {
const total = data.length;
for (let i = 0; i < total; i++) {
// Your processing logic
if (progressCallback) {
const progress = Math.round((i / total) * 100);
progressCallback(progress);
}
}
return result;
}
};
expose(obj);
```
### 2. Register the Worker
In the main thread, can be your service, commands module, etc.
```javascript
import { getWebWorkerManager } from '@cornerstonejs/core';
const workerManager = getWebWorkerManager();
// Define worker creation function
const workerFn = () => {
return new Worker(
new URL('./myWorker.js', import.meta.url),
{ name: 'my-worker' }
);
};
// Registration options
const options = {
maxWorkerInstances: 1, // Number of concurrent workers
autoTerminateOnIdle: {
enabled: true,
idleTimeThreshold: 3000, // Terminate after 3s idle
},
};
// Register the worker
workerManager.registerWorker('my-worker', workerFn, options);
```
:::info
It is recommended to register the worker in top of the commands module. So that it
gets registered before any commands that need to use the worker.
:::
### 3. Execute Tasks
```javascript
// Basic execution
try {
const result = await workerManager.executeTask(
'my-worker',
'basicCalculation',
{ data: myData }
);
} catch (error) {
console.error('Task failed:', error);
}
// Execution with progress callback
try {
const result = await workerManager.executeTask(
'my-worker',
'longRunningTask',
{ data: myData },
{
callbacks: [
(progress) => {
console.log(`Progress: ${progress}%`);
}
]
}
);
} catch (error) {
console.error('Task failed:', error);
}
```
## Progress Events (Optional)
If you want to show progress in your UI as a loading spinner, you can implement a progress event system:
### 1. Publish Progress Events
```javascript
// Helper to trigger progress events
const publishProgress = (eventTarget, progress, taskId) => {
triggerEvent(eventTarget, 'WEB_WORKER_PROGRESS', {
progress, // number 0-100
type: 'YOUR_TASK_TYPE', // can be any string identifier
id: taskId, // unique task identifier
});
};
// Usage in your application
async function runTaskWithProgress(data) {
// Start progress
publishProgress(eventTarget, 0, data.id);
try {
const result = await workerManager.executeTask(
'my-worker',
'longRunningTask',
{ data },
{
callbacks: [
(progress) => {
publishProgress(eventTarget, progress, data.id);
}
]
}
);
// Complete progress
publishProgress(eventTarget, 100, data.id);
return result;
} catch (error) {
console.error('Task failed:', error);
throw error;
}
}
```
Note: Publishing the `WEB_WORKER_PROGRESS` event on Cornerstone's `eventTarget` will automatically trigger the built-in loading spinner. This gives users visual feedback while your worker runs in the background.
## Multiple Methods in One Worker
You can define multiple related methods in a single worker file:
```javascript
// complexWorker.js
import { expose } from 'comlink';
const obj = {
processingMethod1({ data }, progressCallback) {
// Implementation
},
processingMethod2({ data }, progressCallback) {
// Implementation
},
processingMethod3({ data }, progressCallback) {
// Implementation
},
// Shared helper methods
_internalHelper() {
// Helper logic
}
};
expose(obj);
```
+16
View File
@@ -347,3 +347,19 @@ customizationService.addModeCustomizations([
:::note
Notice the arrays and objects, the values are arrays
:::
## How do I change the cine auto mount behavior
You can change the cine auto mount behavior by adding the `autoCineModalities` mode customization, the value is an array of modalities that should be mounted with cine.
By default the viewer will mount with cine enabled for `OT` and `US` modalities.
```js
customizationService.addModeCustomizations([
{
id: 'autoCineModalities',
modalities: ['OT', 'US'],
},
]);
```
@@ -0,0 +1,257 @@
---
id: 0-general
title: General
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# 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,
},
},
```
---
## 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>
@@ -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
@@ -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
```
@@ -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>
@@ -0,0 +1,211 @@
---
id: seg-creation
title: SegmentationService Creation
---
## 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>
---
@@ -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>
---
@@ -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>
---
@@ -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>
@@ -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!
:::
@@ -0,0 +1,28 @@
---
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 `measurements` to be more consistent with the rest of the extensions.
**Action Needed**
Update any references to the `measure` panel to `measurements` 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-default.panelModule.measurements
</TabItem>
</Tabs>
@@ -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.
@@ -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
---
@@ -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
});
```
@@ -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.
```
@@ -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
@@ -0,0 +1,140 @@
---
title: UI
---
## `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.
---
@@ -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');
},
},
]);
```
@@ -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.
@@ -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.
@@ -1,309 +0,0 @@
---
sidebar_position: 1
sidebar_label: 3.8 -> 3.9-beta
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# Migration Guide
## 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>
### Run newer yarn version
You must be running a newer yarn version for react 18.
It isn't clear the exact yarn required.
### 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:
<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>
<br/>
---
<br/>
## Renaming
The panel in the default extension is renamed from `measure` to `measurements` to be more consistent with the rest of the extensions.
**Action Needed**
Update any references to the `measure` panel to `measurements` 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-default.panelModule.measurements
</TabItem>
</Tabs>
<br/>
---
<br/>
## 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
<br/>
---
<br/>
## 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,
},
}
}
```
<br/>
---
<br/>
## 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:
```
<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.
```
"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.
### Usage of Dynamic Imports
## 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.
<br/>
---
<br/>
## 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-).
<br/>
---
<br/>
## Dynamic Modules
TBD
## Renaming some interfaces
A few interfaces are being renamed to simple types to reflect the fact that
they don't contain methods and are thus more properly simple types.
* IDisplaySet renamed to DisplaySet
* Adding some field declarations to agree with actual usage
## 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.
@@ -142,7 +142,13 @@ this pattern, where multiple toolbar buttons are using the same evaluator but wi
You can choose to set up multiple evaluators for a single button. This comes in handy when you need to assess the button according to various conditions. For example, we aim to prevent the Cine player from showing up on the 3D viewport, so we have:
```js
evaluate: ['evaluate.cine', 'evaluate.not3D'],
evaluate: [
'evaluate.cine',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['volume3d'],
},
],
```
You can even come up with advanced evaluators such as:
@@ -20,11 +20,10 @@ There are seven events that get publish in `MeasurementService`:
| Event | Description |
| --------------------- | ------------------------------------------------------ |
| SEGMENTATION_UPDATED | Fires when a segmentation is updated e.g., segment added, removed etc.|
| SEGMENTATION_MODIFIED | Fires when a segmentation is updated e.g., segment added, removed etc.|
| SEGMENTATION_DATA_MODIFIED | Fires when the segmentation data changes |
| SEGMENTATION_ADDED | Fires when a new segmentation is added to OHIF |
| SEGMENTATION_REMOVED | Fires when a segmentation is removed from OHIF |
| SEGMENTATION_CONFIGURATION_CHANGED | Fires when a segmentation configuration is changed |
| SEGMENT_LOADING_COMPLETE | Fires when a segment group adds its pixel data to the volume |
| SEGMENTATION_LOADING_COMPLETE | Fires when the full segmentation volume is filled with its segments |
@@ -33,7 +32,7 @@ There are seven events that get publish in `MeasurementService`:
### Segmentation Creation
- `createSegmentationForDisplaySet`: based on a reference displaySet, create a new segmentation. E.g., create a new segmentation based on a CT series
- `createEmptyLabelmapForDisplaySetUID`: based on a reference displaySet, create a new segmentation. E.g., create a new segmentation based on a CT series
- `createSegmentationForSEGDisplaySet`: given a segDisplaySet loaded by a sopClassHandler, create a new segmentation
- `addSegmentationRepresentationToToolGroup`: given the toolGroupId, add the given segmentationId to the toolGroup.
@@ -1,76 +0,0 @@
---
sidebar_position: 8
sidebar_label: State Sync Service
---
# State Sync Service
## Overview
The state sync service is designed to allow short and long term memory of things such as
annotations applied, last annotation state, hanging protocol viewport state,
window level etc. This allows for better interaction with things like navigation
between hanging protocols, ensuring that the previously displayed layouts
can be redisplayed after returning to a given hanging protocol.
Currently, all the state sync service configurations have one of the following two
lifetimes. See the mode description for general information on the mode lifetime.
* Application load - when the application is restarted, the state is lost
* `clearOnModeExit` - which stores state until the mode onModeExit is called, and then throws away the remaining state. This is useful for mode specific information.
### TODO work - add more storage locations
It is expected to add a few more storage locations, which will store to various
locations on updates:
* User specific server store - to store things between application restarts at the user level
* Browser state store - to store things in the browser local state, to recover after crashing.
* Study specific server store - to store things relevant to a given study between application restarts, on the server.
## Events
Currently the service does not fire events.
## API
- `register`: to create a new named state storage
- `reduce`: to apply a set of changes to several states at once
- `getState`: to retrieve the current state
- `onModeExit`: clears the states configured as clearOnModeExit states
### register
The register call is typically added to an extension to create a new
syncable state. A typical call is shown below, registering the viewport
grid store state as a modal state.
```javascript
stateSyncService.register('viewportGridStore', { clearOnModeExit: true });
```
### getState
The `getState` call returns an object containing all of the registered states,
by id. The values can be read directly, but should not be modified.
### reduce
The `reduce` call is used to apply a set of updates to various states. The
updates are performed for every state as a simply "set" call.
### onModeExit
When the Mode is exited, the onModeExit is called on the sync state, and this
clears all states registered with `clearOnModeExit: true`.
To avoid clearing the state, the mode definition should store any transient
state in the mode onModeExit and recover it in the `mode.onModeEnter`.
## OHIF Registered State Sync Stores
There are a number of defined stores here. It is recommended to update this
list as state stores are added:
### Default Extension Stores
* `viewportGridStore` has viewport grid restore information for returning to an earlier grid layout.
* `reuseIdMap` has a map of names to display sets for preserving user changes to hp display set selections.
* `hanging` has a map of the hanging protocol stage information applied (HPInfo)
### Cornerstone Extension Stores
* `lutPresentationStore` has the cornerstone LUT (window level) presentation state information
* `positionPresentationStore` has the cornerstone viewport position (camera, initial image) information
@@ -0,0 +1,30 @@
---
sidebar_position: 8
sidebar_label: Viewport Action Corners
---
# Viewport Action Corners Service
The Viewport Action Corners Service is a powerful tool for managing interactive components in the corners of viewports within the OHIF viewer. This service allows developers to dynamically add, remove, and organize various UI elements such as menus, buttons, or custom components in specific locations around the viewport.
## Overview
The Viewport Action Corners Service extends the PubSubService and provides methods to:
- Add single or multiple components to viewport corners
- Clear components from a specific viewport
- Manage the state of viewport corner components
## Key Features
- **Flexible Positioning**: Components can be placed in top-left, top-right, bottom-left, or bottom-right corners of the viewport.
- **Priority Ordering**: Components can be assigned priority indices for ordering within a corner.
- **Viewport-Specific**: Actions are associated with specific viewports, allowing for individualized control.
- **Dynamic Updates**: Components can be added or removed at runtime, enabling context-sensitive UI elements.
## Usage
To use the Viewport Action Corners Service, you typically interact with it through the `servicesManager`. Here's a basic example of how to add a component:
Take a look at how we add window level menu to the top right corner of the viewport in the `OHIFCornerstoneViewport` component.
+25 -18
View File
@@ -11,10 +11,26 @@ const path = require('path');
const fs = require('fs');
const versions = fs.readFileSync('../../version.txt', 'utf8').split('\n');
const ArchivedVersionsDropdownItems = [
{
version: '2.0-deprecated',
href: 'https://v2.docs.ohif.org',
isExternal: true,
},
{
version: '1.0-deprecated',
href: 'https://v1.docs.ohif.org',
isExternal: true,
},
];
const baseUrl = process.env.BASE_URL || '/';
/** @type {import('@docusaurus/types').DocusaurusConfig} */
module.exports = {
future: {
experimental_faster: true,
},
title: 'OHIF',
tagline: 'Open-source web-based medical imaging platform',
organizationName: 'Open Health Imaging Foundation',
@@ -47,22 +63,6 @@ module.exports = {
// path.resolve(__dirname, './pluginOHIFWebpackConfig.js'),
// /path.resolve(__dirname, './postcss.js'),
'docusaurus-plugin-image-zoom', // 3rd party plugin for image click to pop
[
'@docusaurus/plugin-client-redirects',
{
fromExtensions: ['html'],
redirects: [
{
// we need this for https://cloud.google.com/healthcare/docs/how-tos/dicom-viewers
to: '/2.0-deprecated/deployment/recipes/google-cloud-healthcare',
from: [
'/connecting-to-image-archives/google-cloud-healthcare',
'/connecting-to-image-archives/google-cloud-healthcare.html',
],
},
],
},
],
[
'@docusaurus/plugin-ideal-image',
{
@@ -200,9 +200,16 @@ module.exports = {
value: '<hr class="dropdown-separator">',
},
{
to: '/versions',
label: 'All versions',
type: 'html',
className: 'dropdown-archived-versions',
value: '<b>Archived versions</b>',
},
...ArchivedVersionsDropdownItems.map(item => ({
label: `${item.version} `,
href: item.href,
target: item.isExternal ? '_blank' : undefined,
rel: item.isExternal ? 'noopener noreferrer' : undefined,
})),
],
},
{
+10 -9
View File
@@ -41,15 +41,16 @@
]
},
"dependencies": {
"@docusaurus/core": "3.5.2",
"@docusaurus/plugin-client-redirects": "3.5.2",
"@docusaurus/plugin-google-gtag": "3.5.2",
"@docusaurus/plugin-ideal-image": "3.5.2",
"@docusaurus/plugin-pwa": "3.5.2",
"@docusaurus/preset-classic": "3.5.2",
"@docusaurus/remark-plugin-npm2yarn": "3.5.2",
"@docusaurus/theme-classic": "3.5.2",
"@docusaurus/theme-live-codeblock": "3.5.2",
"@docusaurus/core": "3.6.0",
"@docusaurus/plugin-client-redirects": "3.6.0",
"@docusaurus/plugin-google-gtag": "3.6.0",
"@docusaurus/plugin-ideal-image": "3.6.0",
"@docusaurus/plugin-pwa": "3.6.0",
"@docusaurus/preset-classic": "3.6.0",
"@docusaurus/remark-plugin-npm2yarn": "3.6.0",
"@docusaurus/theme-classic": "3.6.0",
"@docusaurus/theme-live-codeblock": "3.6.0",
"@docusaurus/faster": "3.6.0",
"@mdx-js/react": "3.0.1",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-checkbox": "^1.1.1",
+127
View File
@@ -537,3 +537,130 @@ div[class^='announcementBar_'] {
);
font-weight: 700;
}
/* #__docusaurus {
height: 100%;
} */
.dropdown-separator {
border-top: 1px solid #808080;
}
/* flex items , center */
.dropdown__link {
display: flex;
align-items: center;
}
.footer__link-item {
display: flex;
align-items: center;
}
/* add proper ui link styling */
/* Bullet point styling */
ul {
list-style-type: disc;
padding-left: 1.5rem;
margin: 1rem 0;
}
ul li {
margin-bottom: 0.5rem;
}
/* Nested bullet points */
ul ul {
list-style-type: circle;
margin: 0.5rem 0;
}
/* For documentation bullet points specifically */
.markdown ul {
list-style-type: disc;
padding-left: 1.5rem;
}
.markdown ul li {
margin-bottom: 0.5rem;
}
/* Markdown link styling */
.markdown a {
color: #0066cc;
text-decoration: none;
transition: color 0.2s ease;
}
.markdown a:hover {
color: #0051a3;
text-decoration: underline;
}
/* Dark mode link styling */
html[data-theme='dark'] .markdown a {
color: #66b3ff;
}
html[data-theme='dark'] .markdown a:hover {
color: #99ccff;
}
/* Horizontal rule styling */
.markdown hr {
height: 1px;
border: none;
background: linear-gradient(to right, #0066cc, #66b3ff);
margin: 2rem 0;
opacity: 0.6;
}
/* Dark mode horizontal rule */
html[data-theme='dark'] .markdown hr {
background: linear-gradient(to right, #66b3ff, #99ccff);
}
/* Markdown code block styling */
.markdown pre {
font-size: 0.9rem;
}
.theme-code-block {
font-size: 0.9rem;
}
/* Target both light and dark themes */
[data-theme='light'] .theme-code-block,
[data-theme='dark'] .theme-code-block {
font-size: 0.9rem;
}
/* Dropdown menu positioning and interaction fixes */
.dropdown {
position: relative;
}
.dropdown__menu {
top: 100%;
margin-top: 0;
padding-top: 0.5rem;
}
/* Add a hover area to prevent menu from disappearing */
.dropdown__menu::before {
content: '';
position: absolute;
top: -10px;
left: 0;
right: 0;
height: 10px;
}
/* Ensure menu stays visible while hovering */
.dropdown:hover .dropdown__menu,
.dropdown__menu:hover {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
@@ -9,6 +9,7 @@ import {
} from '../../../../ui-next/src/components/Accordion';
import { DataRow } from '../../../../ui-next/src/components/DataRow';
import { actionOptionsMap, dataList } from '../../../../ui-next/assets/data';
import BrowserOnly from '@docusaurus/BrowserOnly';
interface DataItem {
id: number;
@@ -36,95 +37,95 @@ export default function Measurements() {
};
const organSegmentationGroup = dataList.find(
(listGroup: ListGroup) => listGroup.type === 'Organ Segmentation'
listGroup => listGroup.type === 'Organ Segmentation'
);
const roiToolsGroup = dataList.find((listGroup: ListGroup) => listGroup.type === 'ROI Tools');
const roiToolsGroup = dataList.find(listGroup => listGroup.type === 'ROI Tools');
if (!organSegmentationGroup) {
return <div className="text-red-500">Organ Segmentation data not found.</div>;
}
if (!roiToolsGroup) {
return <div className="text-red-500">ROI Tools data not found.</div>;
if (!organSegmentationGroup || !roiToolsGroup) {
return null; // Avoid rendering until these groups are ready.
}
return (
<div className="my-4 flex max-w-6xl justify-end bg-black py-6">
{/* Simulated Panel List for "Segmentation" */}
<div className="w-64 space-y-0">
<Accordion
type="multiple"
defaultValue={['measurements-list', 'measurements-additional']}
tabIndex={0}
>
{/* Segmentation Tools */}
<AccordionItem value="measurements-list">
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
<span>Measurements</span>
</AccordionTrigger>
<AccordionContent>
<div className="mx-2 my-0">
<div className="text-foreground text-sm">2024-Jan-01</div>
<div className="text-muted-foreground border-input border-b-2 pb-1 text-sm">
Study title lorem ipsum
</div>
</div>
<BrowserOnly>
{() => (
<div className="my-4 flex max-w-6xl justify-end bg-black py-6">
{/* Simulated Panel List for "Segmentation" */}
<div className="w-64 space-y-0">
<Accordion
type="multiple"
defaultValue={['measurements-list', 'measurements-additional']}
tabIndex={0}
>
{/* Segmentation Tools */}
<AccordionItem value="measurements-list">
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
<span>Measurements</span>
</AccordionTrigger>
<AccordionContent>
<div className="mx-2 my-0">
<div className="text-foreground text-sm">2024-Jan-01</div>
<div className="text-muted-foreground border-input border-b-2 pb-1 text-sm">
Study title lorem ipsum
</div>
</div>
<div className="flex h-9 w-full items-center rounded pr-0.5">
<div className="flex space-x-1">
<Button
size="sm"
variant="ghost"
className="pl-1.5"
>
<Icons.Download />
<span className="pl-1">CSV</span>
</Button>
<Button
size="sm"
variant="ghost"
className="pl-0.5"
>
<Icons.Add />
Create DICOM SR
</Button>
</div>
</div>
<div className="space-y-px">
{roiToolsGroup.items.map((item, index) => {
const compositeId = `${roiToolsGroup.type}-${item.id}-panel`; // Ensure unique composite ID
return (
<DataRow
key={`panel-${compositeId}`} // Prefix to ensure uniqueness
number={index + 1}
title={item.title}
description={item.description}
optionalField={item.optionalField}
colorHex={item.colorHex}
details={item.details}
series={item.series} // Pass the new series field
actionOptions={actionOptionsMap[roiToolsGroup.type] || ['Action']}
onAction={(action: string) => handleAction(compositeId, action)}
isSelected={selectedRowId === compositeId}
onSelect={() => handleRowSelect(compositeId)}
/>
);
})}
</div>
</AccordionContent>
</AccordionItem>
<div className="flex h-9 w-full items-center rounded pr-0.5">
<div className="flex space-x-1">
<Button
size="sm"
variant="ghost"
className="pl-1.5"
>
<Icons.Download />
<span className="pl-1">CSV</span>
</Button>
<Button
size="sm"
variant="ghost"
className="pl-0.5"
>
<Icons.Add />
Create DICOM SR
</Button>
</div>
</div>
<div className="space-y-px">
{roiToolsGroup.items.map((item, index) => {
const compositeId = `${roiToolsGroup.type}-${item.id}-panel`; // Ensure unique composite ID
return (
<DataRow
key={`panel-${compositeId}`} // Prefix to ensure uniqueness
number={index + 1}
title={item.title}
description={item.description}
optionalField={item.optionalField}
colorHex={item.colorHex}
details={item.details}
series={item.series} // Pass the new series field
actionOptions={actionOptionsMap[roiToolsGroup.type] || ['Action']}
onAction={(action: string) => handleAction(compositeId, action)}
isSelected={selectedRowId === compositeId}
onSelect={() => handleRowSelect(compositeId)}
/>
);
})}
</div>
</AccordionContent>
</AccordionItem>
{/* Additional Findings */}
<AccordionItem value="measurements-additional">
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
<span>Additional Findings</span>
</AccordionTrigger>
<AccordionContent>
<div className="bg-muted mb-0.5 h-12 rounded-b pb-3"></div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</div>
{/* Additional Findings */}
<AccordionItem value="measurements-additional">
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
<span>Additional Findings</span>
</AccordionTrigger>
<AccordionContent>
<div className="bg-muted mb-0.5 h-12 rounded-b pb-3"></div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</div>
)}
</BrowserOnly>
);
}
@@ -1,3 +1,5 @@
'use client';
import React, { useState } from 'react';
import { DataRow } from '../../../../ui-next/src/components/DataRow';
@@ -34,6 +36,7 @@ import { Label } from '../../../../ui-next/src/components/Label';
import { Input } from '../../../../ui-next/src/components/Input';
import { Tabs, TabsList, TabsTrigger } from '../../../../ui-next/src/components/Tabs';
import { actionOptionsMap, dataList } from '../../../../ui-next/assets/data';
import { TooltipProvider } from '../../../../ui-next/src/components/Tooltip';
interface DataItem {
id: number;
@@ -74,232 +77,234 @@ export default function SegmentationPanel() {
return (
<div className="my-4 flex h-full w-full max-w-6xl justify-end bg-black py-6">
<div className="w-64 space-y-0">
<Accordion
type="multiple"
defaultValue={['segmentation-tools', 'segmentation-list']}
>
{/* Segmentation Tools */}
<AccordionItem value="segmentation-tools">
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
<span>Segmentation Tools</span>
</AccordionTrigger>
<AccordionContent>
<div className="bg-muted mb-0.5 h-32 rounded-b pb-3"></div>
</AccordionContent>
</AccordionItem>
<TooltipProvider>
<Accordion
type="multiple"
defaultValue={['segmentation-tools', 'segmentation-list']}
>
{/* Segmentation Tools */}
<AccordionItem value="segmentation-tools">
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
<span>Segmentation Tools</span>
</AccordionTrigger>
<AccordionContent>
<div className="bg-muted mb-0.5 h-32 rounded-b pb-3"></div>
</AccordionContent>
</AccordionItem>
{/* Segmentation List */}
<AccordionItem value="segmentation-list">
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
<span>Segmentation List</span>
</AccordionTrigger>
<AccordionContent>
<div className="mb-0">
{/* Header Controls */}
<div className="bg-muted flex h-10 w-full items-center space-x-1 rounded-t px-1.5">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
>
<Icons.More className="h-6 w-6" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>
<Icons.Add className="text-foreground" />
<span className="pl-2">Create New Segmentation</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuLabel>Manage Current Segmentation</DropdownMenuLabel>
<DropdownMenuItem>
<Icons.Series className="text-foreground" />
<span className="pl-2">Remove from Viewport</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Rename className="text-foreground" />
<span className="pl-2">Rename</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Icons.Export className="text-foreground" />
<span className="pl-2">Export & Download</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>Export DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM RTSTRUCT</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icons.Delete className="text-red-600" />
<span className="pl-2 text-red-600">Delete</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Select>
<SelectTrigger className="w-full overflow-hidden">
<SelectValue placeholder="Segmentation 1" />
</SelectTrigger>
<SelectContent>
<SelectItem value="seg1">Segmentation 1</SelectItem>
<SelectItem value="seg2">Segmentation 2</SelectItem>
<SelectItem value="seg3">Segmentation Long Name 123</SelectItem>
</SelectContent>
</Select>
<Button
variant="ghost"
size="icon"
>
<Icons.Info className="h-6 w-6" />
</Button>
</div>
{/* Appearance Settings */}
<AccordionItem value="segmentation-display">
<AccordionTrigger className="bg-muted hover:bg-accent mt-0.5 flex h-7 w-full items-center justify-between rounded-b pr-1 pl-2 font-normal text-white">
<div className="flex space-x-2">
<Icons.Controls className="text-primary" />
<span className="text-primary pr-1">Appearance Settings</span>
</div>
</AccordionTrigger>
<AccordionContent>
<div className="bg-muted mb-0.5 space-y-2 rounded-b px-1.5 pt-0.5 pb-3">
<div className="mx-1 mb-2.5 mt-1 flex items-center justify-between space-x-4">
{/* Display Label with Selected Tab */}
<div className="text-muted-foreground text-xs">Show: {selectedTab}</div>
{/* Tabs Controls */}
<Tabs
value={selectedTab}
onValueChange={setSelectedTab}
{/* Segmentation List */}
<AccordionItem value="segmentation-list">
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
<span>Segmentation List</span>
</AccordionTrigger>
<AccordionContent>
<div className="mb-0">
{/* Header Controls */}
<div className="bg-muted flex h-10 w-full items-center space-x-1 rounded-t px-1.5">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
>
<TabsList>
<TabsTrigger value="Fill & Outline">
<Icons.DisplayFillAndOutline className="text-primary" />
</TabsTrigger>
<TabsTrigger value="Outline Only">
<Icons.DisplayOutlineOnly className="text-primary" />
</TabsTrigger>
<TabsTrigger value="Fill Only">
<Icons.DisplayFillOnly className="text-primary" />
</TabsTrigger>
</TabsList>
</Tabs>
</div>
{/* Opacity Slider */}
<div className="my-2 flex items-center">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Opacity
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[85]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="85"
/>
</div>
{/* Border Slider */}
<div className="my-2 flex items-center">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Border
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[10]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="2"
/>
</div>
{/* Sync Changes Switch */}
<div className="my-2 flex items-center pl-1 pb-1">
<Switch defaultChecked />
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
Sync changes in all viewports
</Label>
</div>
<div className="border-input w-full border"></div>
{/* Display Inactive Segmentations Switch */}
<div className="my-2 flex items-center pl-1">
<Switch defaultChecked />
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
Display inactive segmentations
</Label>
</div>
{/* Additional Opacity Slider */}
<div className="my-2 flex items-center">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Opacity
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[65]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="65"
/>
</div>
</div>
</AccordionContent>
</AccordionItem>
{/* Action Buttons */}
<div className="my-px flex h-9 w-full items-center justify-between rounded pl-0.5 pr-7">
<Button
size="sm"
variant="ghost"
className="pr pl-0.5"
>
<Icons.Add />
Add Segment
</Button>
<Button
size="icon"
variant="ghost"
>
<Icons.Hide className="h-6 w-6" />
</Button>
</div>
</div>
<Icons.More className="h-6 w-6" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>
<Icons.Add className="text-foreground" />
<span className="pl-2">Create New Segmentation</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuLabel>Manage Current Segmentation</DropdownMenuLabel>
<DropdownMenuItem>
<Icons.Series className="text-foreground" />
<span className="pl-2">Remove from Viewport</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Rename className="text-foreground" />
<span className="pl-2">Rename</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Icons.Export className="text-foreground" />
<span className="pl-2">Export & Download</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>Export DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM RTSTRUCT</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icons.Delete className="text-red-600" />
<span className="pl-2 text-red-600">Delete</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Select>
<SelectTrigger className="w-full overflow-hidden">
<SelectValue placeholder="Segmentation 1" />
</SelectTrigger>
<SelectContent>
<SelectItem value="seg1">Segmentation 1</SelectItem>
<SelectItem value="seg2">Segmentation 2</SelectItem>
<SelectItem value="seg3">Segmentation Long Name 123</SelectItem>
</SelectContent>
</Select>
<Button
variant="ghost"
size="icon"
>
<Icons.Info className="h-6 w-6" />
</Button>
</div>
{/* Data Rows */}
<div className="space-y-px">
{organSegmentationGroup.items.map((item, index) => {
const compositeId = `${organSegmentationGroup.type}-${item.id}-panel`; // Ensure unique composite ID
return (
<DataRow
key={`panel-${compositeId}`} // Prefix to ensure uniqueness
number={index + 1}
title={item.title}
description={item.description}
optionalField={item.optionalField}
colorHex={item.colorHex}
details={item.details}
series={item.series}
actionOptions={actionOptionsMap[organSegmentationGroup.type] || ['Action']}
onAction={(action: string) => handleAction(compositeId, action)}
isSelected={selectedRowId === compositeId}
onSelect={() => handleRowSelect(compositeId)}
/>
);
})}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
{/* Appearance Settings */}
<AccordionItem value="segmentation-display">
<AccordionTrigger className="bg-muted hover:bg-accent mt-0.5 flex h-7 w-full items-center justify-between rounded-b pr-1 pl-2 font-normal text-white">
<div className="flex space-x-2">
<Icons.Controls className="text-primary" />
<span className="text-primary pr-1">Appearance Settings</span>
</div>
</AccordionTrigger>
<AccordionContent>
<div className="bg-muted mb-0.5 space-y-2 rounded-b px-1.5 pt-0.5 pb-3">
<div className="mx-1 mb-2.5 mt-1 flex items-center justify-between space-x-4">
{/* Display Label with Selected Tab */}
<div className="text-muted-foreground text-xs">Show: {selectedTab}</div>
{/* Tabs Controls */}
<Tabs
value={selectedTab}
onValueChange={setSelectedTab}
>
<TabsList>
<TabsTrigger value="Fill & Outline">
<Icons.DisplayFillAndOutline className="text-primary" />
</TabsTrigger>
<TabsTrigger value="Outline Only">
<Icons.DisplayOutlineOnly className="text-primary" />
</TabsTrigger>
<TabsTrigger value="Fill Only">
<Icons.DisplayFillOnly className="text-primary" />
</TabsTrigger>
</TabsList>
</Tabs>
</div>
{/* Opacity Slider */}
<div className="my-2 flex items-center">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Opacity
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[85]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="85"
/>
</div>
{/* Border Slider */}
<div className="my-2 flex items-center">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Border
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[10]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="2"
/>
</div>
{/* Sync Changes Switch */}
<div className="my-2 flex items-center pl-1 pb-1">
<Switch defaultChecked />
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
Sync changes in all viewports
</Label>
</div>
<div className="border-input w-full border"></div>
{/* Display Inactive Segmentations Switch */}
<div className="my-2 flex items-center pl-1">
<Switch defaultChecked />
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
Display inactive segmentations
</Label>
</div>
{/* Additional Opacity Slider */}
<div className="my-2 flex items-center">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Opacity
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[65]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="65"
/>
</div>
</div>
</AccordionContent>
</AccordionItem>
{/* Action Buttons */}
<div className="my-px flex h-9 w-full items-center justify-between rounded pl-0.5 pr-7">
<Button
size="sm"
variant="ghost"
className="pr pl-0.5"
>
<Icons.Add />
Add Segment
</Button>
<Button
size="icon"
variant="ghost"
>
<Icons.Hide className="h-6 w-6" />
</Button>
</div>
</div>
{/* Data Rows */}
<div className="space-y-px">
{organSegmentationGroup.items.map((item, index) => {
const compositeId = `${organSegmentationGroup.type}-${item.id}-panel`; // Ensure unique composite ID
return (
<DataRow
key={`panel-${compositeId}`} // Prefix to ensure uniqueness
number={index + 1}
title={item.title}
description={item.description}
optionalField={item.optionalField}
colorHex={item.colorHex}
details={item.details}
series={item.series}
actionOptions={actionOptionsMap[organSegmentationGroup.type] || ['Action']}
onAction={(action: string) => handleAction(compositeId, action)}
isSelected={selectedRowId === compositeId}
onSelect={() => handleRowSelect(compositeId)}
/>
);
})}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</TooltipProvider>
</div>
</div>
);
+305 -314
View File
@@ -1,25 +1,12 @@
import React, { useState } from 'react';
import { Button } from '../../../../ui-next/src/components/Button';
import {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
} from '../../../../ui-next/src/components/Select';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuLabel,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
@@ -39,9 +26,9 @@ import { Slider } from '../../../../ui-next/src/components/Slider';
import { Switch } from '../../../../ui-next/src/components/Switch';
import { Label } from '../../../../ui-next/src/components/Label';
import { Input } from '../../../../ui-next/src/components/Input';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../../../ui-next/src/components/Tabs';
import { ChevronDownIcon } from '@radix-ui/react-icons';
import { Tabs, TabsList, TabsTrigger } from '../../../../ui-next/src/components/Tabs';
import { TooltipProvider } from '../../../../ui-next/src';
import BrowserOnly from '@docusaurus/BrowserOnly';
interface DataItem {
id: number;
@@ -88,316 +75,320 @@ export default function TMTVPatterns() {
}
return (
<div className="px-auto my-4 flex h-full w-full justify-center bg-black py-6">
<div className="w-64 space-y-0">
<Accordion
type="multiple"
defaultValue={['segmentation-tools', 'segmentation-list', 'tmv1-group', 'tmv2-group']}
collapsible
>
{/* Segmentation Tools */}
<AccordionItem value="segmentation-tools">
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
<span>Segmentation Tools</span>
</AccordionTrigger>
<AccordionContent>
<div className="bg-muted mb-0.5 h-32 rounded-b pb-3"></div>
</AccordionContent>
</AccordionItem>
{/* Segmentation List */}
<AccordionItem value="segmentation-list">
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
<span>Segmentation List</span>
</AccordionTrigger>
<AccordionContent>
{/* Appearance Settings */}
<AccordionItem value="segmentation-display">
<AccordionTrigger className="bg-muted hover:bg-accent mt-0.5 flex h-7 w-full items-center justify-between rounded-b pr-1 pl-2 font-normal text-white">
<div className="flex space-x-2">
<Icons.Controls className="text-primary" />
<span className="text-primary pr-1">Appearance Settings</span>
</div>
<BrowserOnly>
{() => (
<div className="px-auto my-4 flex h-full w-full justify-center bg-black py-6">
<div className="w-64 space-y-0">
<Accordion
type="multiple"
defaultValue={['segmentation-tools', 'segmentation-list', 'tmv1-group', 'tmv2-group']}
collapsible
>
{/* Segmentation Tools */}
<AccordionItem value="segmentation-tools">
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
<span>Segmentation Tools</span>
</AccordionTrigger>
<AccordionContent>
<div className="bg-muted mb-0.5 space-y-2 rounded-b px-px pt-0.5 pb-3">
<div className="mx-1 mb-2.5 mt-1 flex items-center justify-between space-x-4">
{/* Display Label with Selected Tab */}
<div className="text-muted-foreground text-xs">Show: {selectedTab}</div>
{/* Tabs Controls */}
<Tabs
value={selectedTab}
onValueChange={setSelectedTab}
>
<TabsList>
<TabsTrigger value="Fill & Outline">
<Icons.DisplayFillAndOutline className="text-primary" />
</TabsTrigger>
<TabsTrigger value="Outline Only">
<Icons.DisplayOutlineOnly className="text-primary" />
</TabsTrigger>
<TabsTrigger value="Fill Only">
<Icons.DisplayFillOnly className="text-primary" />
</TabsTrigger>
</TabsList>
</Tabs>
</div>
{/* Opacity Slider */}
<div className="my-2 flex items-center ">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Opacity
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[85]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="85"
/>
</div>
{/* Border Slider */}
<div className="my-2 flex items-center">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Border
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[10]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="2"
/>
</div>
{/* Sync Changes Switch */}
<div className="my-2 flex items-center pl-1 pb-1">
<Switch defaultChecked />
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
Sync changes in all viewports
</Label>
</div>
<div className="border-input w-full border"></div>
{/* Display Inactive Segmentations Switch */}
<div className="my-2 flex items-center pl-1">
<Switch defaultChecked />
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
Display inactive segmentations
</Label>
</div>
{/* Additional Opacity Slider */}
<div className="my-2 flex items-center ">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Opacity
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[65]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="65"
/>
</div>
</div>
<div className="bg-muted mb-0.5 h-32 rounded-b pb-3"></div>
</AccordionContent>
</AccordionItem>
{/* TMTV1 Group */}
<AccordionItem value="tmv1-group">
<AccordionTrigger className="hover:bg-popover mr-0 flex h-8 w-full items-center pl-0 pr-1">
<div className="text-foreground border-input flex h-8 w-full items-center justify-between border-t-2">
{/* Left Group: DropdownMenu and TMTV1 Label */}
<div className="flex items-center space-x-1">
<DropdownMenu>
<DropdownMenuTrigger asChild>
{/* Segmentation List */}
<AccordionItem value="segmentation-list">
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
<span>Segmentation List</span>
</AccordionTrigger>
<AccordionContent>
{/* Appearance Settings */}
<AccordionItem value="segmentation-display">
<AccordionTrigger className="bg-muted hover:bg-accent mt-0.5 flex h-7 w-full items-center justify-between rounded-b pr-1 pl-2 font-normal text-white">
<div className="flex space-x-2">
<Icons.Controls className="text-primary" />
<span className="text-primary pr-1">Appearance Settings</span>
</div>
</AccordionTrigger>
<AccordionContent>
<div className="bg-muted mb-0.5 space-y-2 rounded-b px-px pt-0.5 pb-3">
<div className="mx-1 mb-2.5 mt-1 flex items-center justify-between space-x-4">
{/* Display Label with Selected Tab */}
<div className="text-muted-foreground text-xs">Show: {selectedTab}</div>
{/* Tabs Controls */}
<Tabs
value={selectedTab}
onValueChange={setSelectedTab}
>
<TabsList>
<TabsTrigger value="Fill & Outline">
<Icons.DisplayFillAndOutline className="text-primary" />
</TabsTrigger>
<TabsTrigger value="Outline Only">
<Icons.DisplayOutlineOnly className="text-primary" />
</TabsTrigger>
<TabsTrigger value="Fill Only">
<Icons.DisplayFillOnly className="text-primary" />
</TabsTrigger>
</TabsList>
</Tabs>
</div>
{/* Opacity Slider */}
<div className="my-2 flex items-center">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Opacity
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[85]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="85"
/>
</div>
{/* Border Slider */}
<div className="my-2 flex items-center">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Border
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[10]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="2"
/>
</div>
{/* Sync Changes Switch */}
<div className="my-2 flex items-center pl-1 pb-1">
<Switch defaultChecked />
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
Sync changes in all viewports
</Label>
</div>
<div className="border-input w-full border"></div>
{/* Display Inactive Segmentations Switch */}
<div className="my-2 flex items-center pl-1">
<Switch defaultChecked />
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
Display inactive segmentations
</Label>
</div>
{/* Additional Opacity Slider */}
<div className="my-2 flex items-center">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Opacity
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[65]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="65"
/>
</div>
</div>
</AccordionContent>
</AccordionItem>
{/* TMTV1 Group */}
<AccordionItem value="tmv1-group">
<AccordionTrigger className="hover:bg-popover mr-0 flex h-8 w-full items-center pl-0 pr-1">
<div className="text-foreground border-input flex h-8 w-full items-center justify-between border-t-2">
{/* Left Group: DropdownMenu and TMTV1 Label */}
<div className="flex items-center space-x-1">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="ml-1"
>
<Icons.More className="h-6 w-6" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>
<Icons.Add className="text-foreground" />
<span className="pl-2">Add Segment</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icons.Series className="text-foreground" />
<span className="pl-2">Remove from Viewport</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Rename className="text-foreground" />
<span className="pl-2">Rename</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Hide className="text-foreground" />
<span className="pl-2">Hide or Show all Segments</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Icons.Export className="text-foreground" />
<span className="pl-2">Export & Download</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>Export DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM RTSTRUCT</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icons.Delete className="text-red-600" />
<span className="pl-2 text-red-600">Delete</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<div className="pl-1.5">TMTV1 Segmentation</div>
</div>
<div className="mr-1 flex items-center">
<Button
variant="ghost"
size="icon"
className="ml-1"
>
<Icons.More className="h-6 w-6" />
<Icons.Info className="h-6 w-6" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>
<Icons.Add className="text-foreground" />
<span className="pl-2">Add Segment</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icons.Series className="text-foreground" />
<span className="pl-2">Remove from Viewport</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Rename className="text-foreground" />
<span className="pl-2">Rename</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Hide className="text-foreground" />
<span className="pl-2">Hide or Show all Segments</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Icons.Export className="text-foreground" />
<span className="pl-2">Export & Download</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>Export DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM RTSTRUCT</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icons.Delete className="text-red-600" />
<span className="pl-2 text-red-600">Delete</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<div className="pl-1.5">TMTV1 Segmentation</div>
</div>
<div className="mr-1 flex items-center">
<Button
variant="ghost"
size="icon"
>
<Icons.Info className="h-6 w-6" />
</Button>
</div>
</div>
</AccordionTrigger>
<AccordionContent>
{/* Data Rows for TMTV1 */}
<div className="space-y-px">
{tmvGroup.items.map((item, index) => {
const compositeId = `${tmvGroup.type}-${item.id}-panel`; // Ensure unique composite ID
return (
<DataRow
key={`panel-${compositeId}`} // Prefix to ensure uniqueness
number={index + 1}
title={item.title}
description={item.description}
optionalField={item.optionalField}
colorHex={item.colorHex}
details={item.details}
series={item.series}
actionOptions={actionOptionsMap[tmvGroup.type] || ['Action']}
onAction={(action: string) => handleAction(compositeId, action)}
isSelected={selectedRowId === compositeId}
onSelect={() => handleRowSelect(compositeId)}
/>
);
})}
</div>
</AccordionContent>
</AccordionItem>
{/* TMTV2 Group */}
<AccordionItem value="tmv2-group">
<AccordionTrigger className="hover:bg-popover mr-0 flex h-8 w-full items-center pl-0 pr-1">
<div className="text-foreground border-input flex h-8 w-full items-center justify-between border-t-2">
<div className="flex items-center space-x-1">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="ml-1"
>
<Icons.More className="h-6 w-6" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>
<Icons.Add className="text-foreground" />
<span className="pl-2">Add Segment</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icons.Series className="text-foreground" />
<span className="pl-2">Remove from Viewport</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Rename className="text-foreground" />
<span className="pl-2">Rename</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Hide className="text-foreground" />
<span className="pl-2">Hide or Show all Segments</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Icons.Export className="text-foreground" />
<span className="pl-2">Export & Download</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>Export DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM RTSTRUCT</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icons.Delete className="text-red-600" />
<span className="pl-2 text-red-600">Delete</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</AccordionTrigger>
<AccordionContent>
{/* Data Rows for TMTV1 */}
<div className="space-y-px">
{tmvGroup.items.map((item, index) => {
const compositeId = `${tmvGroup.type}-${item.id}-panel`; // Ensure unique composite ID
return (
<DataRow
key={`panel-${compositeId}`} // Prefix to ensure uniqueness
number={index + 1}
title={item.title}
description={item.description}
optionalField={item.optionalField}
colorHex={item.colorHex}
details={item.details}
series={item.series}
actionOptions={actionOptionsMap[tmvGroup.type] || ['Action']}
onAction={(action: string) => handleAction(compositeId, action)}
isSelected={selectedRowId === compositeId}
onSelect={() => handleRowSelect(compositeId)}
/>
);
})}
</div>
</AccordionContent>
</AccordionItem>
{/* TMTV2 Group */}
<AccordionItem value="tmv2-group">
<AccordionTrigger className="hover:bg-popover mr-0 flex h-8 w-full items-center pl-0 pr-1">
<div className="text-foreground border-input flex h-8 w-full items-center justify-between border-t-2">
<div className="flex items-center space-x-1">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="ml-1"
>
<Icons.More className="h-6 w-6" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>
<Icons.Add className="text-foreground" />
<span className="pl-2">Add Segment</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icons.Series className="text-foreground" />
<span className="pl-2">Remove from Viewport</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Rename className="text-foreground" />
<span className="pl-2">Rename</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Hide className="text-foreground" />
<span className="pl-2">Hide or Show all Segments</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Icons.Export className="text-foreground" />
<span className="pl-2">Export & Download</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>Export DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM RTSTRUCT</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icons.Delete className="text-red-600" />
<span className="pl-2 text-red-600">Delete</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<div className="pl-1.5">TMTV2 Segmentation</div>
</div>
<div className="mr-1 flex items-center ">
<Button
variant="ghost"
size="icon"
>
<Icons.Info className="h-6 w-6" />
</Button>
</div>
</div>
</AccordionTrigger>
<AccordionContent>
{/* Data Rows for TMTV2 */}
<div className="space-y-px">
{tmv2Group.items.map((item, index) => {
const compositeId = `${tmv2Group.type}-${item.id}-panel`; // Ensure unique composite ID
return (
<DataRow
key={`panel-${compositeId}`} // Prefix to ensure uniqueness
number={index + 1}
title={item.title}
description={item.description}
optionalField={item.optionalField}
colorHex={item.colorHex}
details={item.details}
series={item.series}
actionOptions={actionOptionsMap[tmv2Group.type] || ['Action']}
onAction={(action: string) => handleAction(compositeId, action)}
isSelected={selectedRowId === compositeId}
onSelect={() => handleRowSelect(compositeId)}
/>
);
})}
<div className="pl-1.5">TMTV2 Segmentation</div>
</div>
<div className="mr-1 flex items-center">
<Button
variant="ghost"
size="icon"
>
<Icons.Info className="h-6 w-6" />
</Button>
</div>
</div>
</AccordionTrigger>
<AccordionContent>
{/* Data Rows for TMTV2 */}
<div className="space-y-px">
{tmv2Group.items.map((item, index) => {
const compositeId = `${tmv2Group.type}-${item.id}-panel`; // Ensure unique composite ID
return (
<DataRow
key={`panel-${compositeId}`} // Prefix to ensure uniqueness
number={index + 1}
title={item.title}
description={item.description}
optionalField={item.optionalField}
colorHex={item.colorHex}
details={item.details}
series={item.series}
actionOptions={actionOptionsMap[tmv2Group.type] || ['Action']}
onAction={(action: string) => handleAction(compositeId, action)}
isSelected={selectedRowId === compositeId}
onSelect={() => handleRowSelect(compositeId)}
/>
);
})}
</div>
</AccordionContent>
</AccordionItem>
{/* Footer or Additional Information */}
<div className="bg-popover text-foreground flex h-8 items-center justify-between pl-9 pr-3 text-sm font-semibold">
<span>TMTV</span>
<span>21.555 mL</span>
</div>
</AccordionContent>
</AccordionItem>
{/* Footer or Additional Information */}
<div className="bg-popover text-foreground flex h-8 items-center justify-between pl-9 pr-3 text-sm font-semibold">
<span>TMTV</span>
<span>21.555 mL</span>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</div>
</Accordion>
</div>
</div>
)}
</BrowserOnly>
);
}
+63
View File
@@ -0,0 +1,63 @@
import React from 'react';
import Layout from '@theme/Layout';
import Link from '@docusaurus/Link';
export default function Versions() {
const versions = [
{
version: 'Version 1',
status: 'deprecated',
description: 'Built with Meteor as a full stack application.',
},
{
version: 'Version 2',
status: 'deprecated',
description: 'Front end image viewer built with React',
},
{
version: 'Version 3.x-beta',
status: 'master branch',
description: 'With latest bug fixes and features but not yet released (released under beta)',
},
{
version: 'Version 3.x',
status: 'release branch',
description: 'Released version of the OHIF platform which is more stable and tested',
},
];
return (
<Layout
title="Versions"
description="OHIF Platform Versions"
>
<div className="margin-top--lg padding-horiz--lg container">
<h1>Versions</h1>
<p>
As we are increasing the efforts to make the OHIF platform more robust and up-to-date with
the latest software engineering practices, here we are listing the versions of the OHIF
platform that we are currently supporting, and the versions that have been deprecated.
</p>
<h2>Product Version</h2>
<p>Currently we have four product versions:</p>
<ul className="versions-list">
{versions.map((item, index) => (
<li key={index}>
<strong>{item.version}</strong> ({item.status}): {item.description}
</li>
))}
</ul>
<p>
You can read more about the differences between the versions in the{' '}
<Link to="/docs/development/getting-started#branches">development section</Link> of the
documentation to understand which version is more suitable for your use case.
</p>
</div>
</Layout>
);
}
-20
View File
@@ -1,20 +0,0 @@
# Versions
As we are increasing the efforts to make the OHIF platform more robust and up-to-date
with the latest software engineering practices, here we are listing the versions of
the OHIF platform that we are currently supporting, and the versions that have been
deprecated.
## Product Version
Currently we have three product versions:
- Version 1 (deprecated): Built with Meteor as a full stack application.
- Version 2 (deprecated): Front end image viewer built with React
- Version 3.x-beta (master branch): With latest bug fixes and features but not yet released (released under beta)
- Version 3.x (release branch): Released version of the OHIF platform which is more stable and tested
You can read more about the differences between the versions in the [development section](../../docs/development/getting-started#branches) of the documentation
to understand which version is more suitable for your use case.
@@ -1,4 +0,0 @@
{
"label": "I Want to ...",
"position": 8
}
@@ -1,40 +0,0 @@
# Add a Logo to the Viewer
The OHIF Framework provides [**ohif-header**](https://github.com/OHIF/Viewers/tree/master/Packages/ohif-header) package to add a header into application layout. **ohif-header** package is designed as a [custom block helper](http://blazejs.org/api/spacebars.html#Custom-Block-Helpers) named **header** to define your own header context.
If you would like to add SVG logo to header please follow these steps.
1. Add your SVG logo into public folder.
2. Add **header** content block which will be located on the top of the application layout into your application's main template. You can also pass some parameters to **header** content block to customize the header.
* headerClasses: the list of classes which will be applied to header element
* brandHref: the url of the logo to link
``` html
{{#header headerClasses="header-big bg-blue" brandHref="your logo link"}}
...
{{/header}}
```
1. Create a section called as **brand** in **header** content block and add your logo content which is displayed on the left side of the header as default into section **brand**.
``` html
{{#header}}
{{#section "brand"}}
<!-- Add logo image -->
<svg>
<use xlink:href="/yourLogo.svg"}}></use>
</svg>
<!-- Add logo text -->
<div>Logo Text</div>
{{/section}}
{{/header}}
```
For example, see how it works in [OHIF Viewer](https://github.com/OHIF/Viewers/blob/master/OHIFViewer/client/components/ohifViewer/ohifViewer.html#L2)
@@ -1,22 +0,0 @@
# Add a Tool to the Viewer
To add a tool to the Viewer there are a few steps:
1. Add the tool itself to the repository.
If you're using something from Cornerstone Tools you can skip this step.
Some examples of custom tools can be found in the lesion tracker: https://github.com/OHIF/Viewers/tree/master/Packages/ohif-lesiontracker/client/compatibility
2. Add the toolbar button itself to the array of tools in the Toolbar:
https://github.com/OHIF/Viewers/blob/574a6d02b090b8b2f020430c5919f8377b8316c6/OHIFViewer/client/components/toolbarSection/toolbarSection.js
3. **A:** Add it to the toolManager (if it's a tool, such as length / angle):
https://github.com/OHIF/Viewers/blob/574a6d02b090b8b2f020430c5919f8377b8316c6/Packages/lesiontracker/client/tools.js#L2
** --- OR --- **
**B:** Add it to the functionList if it's a command (e.g. toggle CINE play, or Invert the current viewport):
https://github.com/OHIF/Viewers/blob/574a6d02b090b8b2f020430c5919f8377b8316c6/OHIFViewer/client/components/viewer/viewer.js#L12
@@ -1,61 +0,0 @@
# OHIF Standalone Installation Instructions For Windows Server 2016:
**Note: Turn-Off Windows Defender Realtime protection during this process**
1. Install Chocolatey
a. Go to this URL for instructions:
https://chocolatey.org/install#install-with-cmdexe
b. Execute this command in the cmd line as Admin:
```
@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
```
c. Type `choco -?` to ensure the installation is OK.
2. Install Meteor
a. Type the command: `choco install meteor`
3. Install node.js
a. Download MSI from: https://nodejs.org/en/download/
4. Install MongoDB
a. Download MSI from:
https://www.mongodb.com/download-center/community
5. Install Git
a. https://git-scm.com/download/win
6. Download the OHIF Viewer repository from GitHub, or use Git to clone it (recommended)
a. GitHub Repo: https://github.com/OHIF/Viewers
b. GIT Clone command: `git clone https://github.com/OHIF/Viewers`
7. Set the Meteor Packages folder environment variable
a. Go to Advanced System Settings
b. Under Advanced, click on the "Environment Variables..." button
c. Under System Variables, click "New..."
d. Set the following:
i. Variable name: METEOR_PACKAGE_DIRS
ii. Variable Value:
example: C:\OHIF\Viewers\Packages
8. Using the command line, navigate/cs to the standalone viewer folder, example: `cd C:\OHIF\Viewers\StandaloneViewer\StandaloneViewer`
9. run the command: `meteor npm install`
10. Run the command: `meteor`
a. if you get this error "Error: EPERM: operation not permitted, unlink" Or,
b. You feel the build is stale for a very long time > 10min with no visual indication, restart the cmd prompt and repeat this step (`meteor`)
# Troubleshooting:
1. If you get an error: Error: EPERM: operation not permitted, unlink `c:\xxxxx`
a. https://github.com/phoenixframework/phoenix/issues/2464
@@ -1,4 +0,0 @@
{
"label": "Extras",
"position": 13
}
@@ -1,345 +0,0 @@
---
title: Development Environment Installation
---
**Development Environment Installation (Ubuntu)**
**CONFIDENTIAL DOCUMENT**
This is a confidential document and property of Radical Imaging LLC. It shall not be transmitted, copied or sent to anyone without prior authorization.
**Index**
[[TOC]]
<table>
<tr>
<td>Acronyms and definitions</td>
<td></td>
</tr>
<tr>
<td>Acronym/Definition</td>
<td>Meaning</td>
</tr>
<tr>
<td>N/A</td>
<td>N/A</td>
</tr>
</table>
<table>
<tr>
<td>Referenced Documents</td>
<td></td>
</tr>
<tr>
<td>Doc No</td>
<td>Doc Title</td>
</tr>
<tr>
<td>DOC00001</td>
<td>Radical Imaging Official Documents Template</td>
</tr>
</table>
# PURPOSE AND OBJECTIVES
The purpose of this document is to describe the development environment installation of OHIF viewer/Lesion Tracker on Linux Ubuntu.
# DEVELOPMENT ENVIRONMENT INSTALLATION
## Docker
Docker is an open source software platform to create, deploy and manage virtualized application containers on common operating systems, with several allied tools.
To install Docker, follow the instructions below:
1. Open a terminal, and update the apt package index, by running the following command:
sudo apt-get update
Expected result:
<!-- ![image alt text](image_0.png) -->
2. Install packages to allow apt to use a repository over HTTPS, by running the following command:
sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
Expected result:
<!-- ![image alt text](image_1.png) -->
3. Add Docker’s official GPG key, by running the following command:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
Expected result:
<!-- ![image alt text](image_2.png) -->
4. Verify that you now have the key with the fingerprint **9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88**, by searching for the last 8 characters of the fingerprint after running the following command:
sudo apt-key fingerprint 0EBFCD88
Expected result:
<!-- ![image alt text](image_3.png) -->
5. Use the following command to set up the stable repository.
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
Expected result:
<!-- ![image alt text](image_4.png) -->
6. Install the latest version of Docker CE, by running the following command:
sudo apt-get install docker-ce
Expected result:
<!-- ![image alt text](image_5.png) -->
7. Verify that Docker CE is installed correctly by running the hello-world image:
sudo docker run hello-world
Expected result:
<!-- ![image alt text](image_6.png) -->
**Note**: Additional information about Docker installation on Linux Ubuntu OS can be foud at [https://docs.docker.com/install/linux/docker-ce/ubuntu/](https://docs.docker.com/install/linux/docker-ce/ubuntu/).
## DCM4CHE
Dcm4che ([https://www.dcm4che.org/](https://www.dcm4che.org/)) is a collection of open source applications and utilities for healthcare.
To install Dcm4che, follow the instructions below:
1. Before start, you need to create the following folders on your *Home *directory:
* DCM4CHEE
* dcm4chee-arc
* db
* ldap
* slapd.d
* storage
* wildfly
2. Create the following files on the DCM4CHEE folder, with the correspondent contents:
docker-compose.env
```
STORAGE_DIR=/storage/fs1
POSTGRES_DB=pacsdb
POSTGRES_USER=pacs
POSTGRES_PASSWORD=pacs
```
docker-compose.yml
```
version: "3"
services:
ldap:
image: dcm4che/slapd-dcm4chee:2.4.44-14.1
logging:
driver: json-file
options:
max-size: "10m"
ports:
- "389:389"
env_file: docker-compose.env
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- ~/dcm4chee-arc/ldap:/var/lib/ldap
- ~/dcm4chee-arc/slapd.d:/etc/ldap/slapd.d
db:
image: dcm4che/postgres-dcm4chee:10.4-14
logging:
driver: json-file
options:
max-size: "10m"
ports:
- "5432:5432"
env_file: docker-compose.env
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- ~/dcm4chee-arc/db:/var/lib/postgresql/data
arc:
image: dcm4che/dcm4chee-arc-psql:5.14.1
logging:
driver: json-file
options:
max-size: "10m"
ports:
- "8080:8080"
- "8443:8443"
- "9990:9990"
- "11112:11112"
- "2575:2575"
env_file: docker-compose.env
environment:
WILDFLY_CHOWN: /opt/wildfly/standalone /storage
WILDFLY_WAIT_FOR: ldap:389 db:5432
depends_on:
- ldap
- db
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- ~/dcm4chee-arc/wildfly:/opt/wildfly/standalone
- ~/dcm4chee-arc/storage:/storage</td>
```
Files/Folder will become like the image above:
<!-- ![image alt text](image_7.png) -->
1. Open a terminal inside DCM4CHE folder, and run the following command:
sudo snap install docker
Expected result:
<!-- ![image alt text](image_8.png) -->
4. Still inside the DCM4CHE folder, run the following command:
sudo docker-compose -p dcm4chee up -d
Expected result:
<!-- ![image alt text](image_9.png) -->
1. The following commands can be used to control DCM4CHE:
Stop all 3 containers: docker-compose -p dcm4chee stop
Start all 3 containers again: docker-compose -p dcm4chee start
Stop and delete all 3 containers: docker-compose -p dcm4chee down
2. Now it is necessary to import some DICOM studies to DCM4CHE. Before importing these studies, you will need to download them. To do so, access the address [http://34.224.187.57:3000/studylist](http://34.224.187.57:3000/studylist), right click one or more studies, and select the option Export:
<!-- ![image alt text](image_10.png) -->
3. Confirm the study export on the modal window that will appear:
<!-- ![image alt text](image_11.png) -->
4. The study(ies) export will begin. You can follow the exporting progress:
<!-- ![image alt text](image_12.png) -->
5. After the exporting progress, the study(ies) will be downloaded in a file called "studies.zip". Create a folder named “Studies” on your Home folder, and extract this and any other studies you download in this folder:
<!-- ![image alt text](image_13.png) -->
6. Open the terminal, go to the DCM4CHE folder, and run the following command in order to send the studies to DCM4CHE:
docker run -v ~/Studies/:/tmp --rm --network=dcm4chee_default dcm4che/dcm4che-tools:5.14.0 storescu -cDCM4CHEE@arc:11112 /tmp
Expected result:
<!-- ![image alt text](image_14.png) -->
7. Now, go to your browser and access the URL [http://localhost:8080/dcm4chee-arc/ui2/](http://localhost:8080/dcm4chee-arc/ui2/). Once opened, click on the refresh icon on the most right:
<!-- ![image alt text](image_15.png) -->
8. The studies received by DCM4CHE will be shown:
<!-- ![image alt text](image_16.png) -->
9. Open the terminal, go to your Home folder, and run the following command in order to clone viewers repository to your local:
git clone https://github.com/OHIF/Viewers.git
Expected result:
<!-- ![image alt text](image_17.png) -->
10. Then, enter the Viewers folder, and run the following command in order to make sure that you are in master branch:
git checkout master
Expected result:
<!-- ![image alt text](image_18.png) -->
11. Now go to OHIFViewer folder and run the following command:
meteor npm install
Expected result:
<!-- ![image alt text](image_19.png) -->
12. Still on the OHIFViewer folder, run the following command:
./bin/dcm4cheeDICOMWeb.sh
Expected result:
<!-- ![image alt text](image_20.png) -->
13. Now, go to your browser and access the URL [http://localhost:3000](http://localhost:3000). Once opened, change the Study Date filter to start on the year 2000:
<!-- ![image alt text](image_21.png) -->
14. The imported studies will be show. Double click a study to open it:
<!-- ![image alt text](image_22.png) -->
## Meteor
Meteor is a JavaScript web framework that allows for rapid prototyping and produces cross-platform code.
To install Meteor, follow the instructions below:
**_Remark_***: This guide covers the Linux Ubuntu version. Installation instructions can be different on other operating systems.*
1. Open a terminal, and run the following command:
curl https://install.meteor.com/ | sh
Expected result:
<!-- ![image alt text](image_23.png) -->
## Starting OHIF Viewer after system restart
These are the steps to run OHIFVIewer after system restart:.
1. Open terminal
2. Access the DCM4CHEE folder, on your Home folder
3. Run the following command in order to start docker containers:
docker-compose -p dcm4chee start
4. Access the Viewers/OHIFViewer folder, on your Home folder
5. Run the following command in order to run OHIF Viewer:
./bin/dcm4cheeDICOMWeb.sh
6. Access the address [http://localhost:3000](http://localhost:3000)
@@ -1,96 +0,0 @@
---
title: Standalone Viewer
---
# Standalone Viewer
## Quick Start
Install dependencies:
```bash
meteor npm install
```
Run the application:
```bash
METEOR_PACKAGE_DIRS="../../Packages" ROOT_URL=http://localhost:3000 meteor
```
Open your web browser and navigate to one of the following URLs to test the standalone viewer application:
```bash
http://localhost:3000/testId
```
Or, to load DICOMs:
```bash
http://localhost:3000/testDICOMs
```
### To Build for the Client
It is possible to build this standalone viewer to run as a client-only bundle of HTML, JavaScript, and CSS.
1. First, install [meteor-build-client-fixed2](https://www.npmjs.com/package/meteor-build-client-fixed2).
````bash
sudo npm install -g meteor-build-client-fixed2
````
2. Next, build the client bundle into an output folder ("myOutputFolder") with a base URL ("localhost:3000"). In production, this would be the URL where the Viewer is available.
````
METEOR_PACKAGE_DIRS="../../Packages" meteor-build-client-fixed2 ../myOutputFolder -u localhost:3000 --legacy
````
3. Test the bundled client-side package locally.
Note: You will need to have Python installed to run the test server for this case. It is not a typical simple HTTP server. The bundled script redirects all URLs following the base URL to index.html. It will then use the routes defined in your application to handle the URL parameters.
In our case, this means it will request a JSON file at baseURL/api/[id parameter].
So if you navigate to http://localhost:3000/sampleJPEG.json the application will retrieve the JSON from http://localhost:3000/api/sampleJPEG.json and use it to populate the viewer. If something appears to be broken, make sure you retrieve a JSON file at the /api URL.
Create the api folder for your data
````bash
cd myOutputFolder
mkdir api
````
Copy your data into the folder
````bash
cp ../etc/sample* api/
````
Run the server
```` bash
python ../etc/redirectingSimpleServer.py
````
Open your web browser and navigate to http://localhost:3000/sampleJPEG.json or http://localhost:3000/sampleDICOM.json
## Authorization Header
A ```token``` fragment parameter can be specified. If present this value will be used for http bearer authorization when making requests for the above JSON, and when retrieving images using the Cornerstone Image Loaders.
Example :
```
http://localhost:3000/sampleDICOM.json#token=1a2b3c4d
```
### Testing the Sample client-only build
For the sake of simplicity we have also included a pre-built client-only version of the standalone viewer, which can be found in the SampleClientOnlyBuild folder.
You can test this with:
```` bash
cd SampleClientOnlyBuild
python ../etc/redirectingSimpleServer.py
````
@@ -1,51 +0,0 @@
---
id: Introduction
slug: /
sidebar_position: 1
---
##### Looking for your Deploy Preview? - <a onClick="function redirect() { window.location.href='/demo/'; } redirect();">Deploy Preview for Viewer</a>
The [Open Health Imaging Foundation](https://www.ohif.org) is developing an open
source framework for constructing web-based medical imaging applications. The
application framework is built using modern HTML / CSS / JavaScript and uses
[Cornerstone](https://cornerstonejs.org/) at its core to display and manipulate
medical images. It is built with Meteor, a Node.js-based full-stack JavaScript
platform.
This documentation concerns the OHIF framework itself and its three example
applications: the OHIF Viewer, Lesion Tracker, and the Standalone Viewer.
## The **OHIF Viewer**: A general purpose DICOM Viewer ([demo](http://viewer.ohif.org/))
![OHIF Viewer Screenshot](./assets/img/viewer.png)
The Open Health Imaging Foundation intends to provide a simple general purpose
DICOM Viewer which can be easily extended for specific uses. The primary purpose
of the OHIF Viewer is to serve as a testing ground for the underlying packages
and the [Cornerstone](https://cornerstonejs.org/) family of libraries.
## **Lesion Tracker**: An oncology-focused imaging application ([demo](http://lesiontracker.ohif.org/))
![Lesion Tracker Screenshot](./assets/img/lesionTracker.png)
The Lesion Tracker is designed to facilitate quantitative assessments of tumour
burden over time. It is similar in scope to the ePAD Imaging Platform
(https://epad.stanford.edu/), developed at Stanford Medicine.
## Study List & DICOM Connectivity
![Study List Screenshot](./assets/img/worklist.png)
The solution provides a study list and other resources for connecting to PACS
and other Image Archives through standard communication approaches (DICOM Web,
DICOM Messages).
## Standalone Viewer ([demo](https://ohif-viewer.s3-website.eu-central-1.amazonaws.com/?url=https://raw.githubusercontent.com/OHIF/Viewers/master/StandaloneViewer/etc/sampleDICOM.json))
The Standalone Viewer offers only the client-side portions of the OHIF Viewer
with the Study List pages removed. This single-page viewer can be hosted as a
static site (e.g. on Amazon S3, Azure Blob Storage, or Github Pages), and easily
integrated with existing back-end DICOM storage systems. Alternative
[Cornerstone](https://cornerstonejs.org/) Image Loaders can be included to allow
your viewer to support non-DICOM objects (e.g. PNG, JPEG).
Binary file not shown.

Before

Width:  |  Height:  |  Size: 585 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 733 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 517 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 595 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 576 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 521 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 329 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 329 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 485 KiB

Loaded 100 of 803 files, more files were not shown because too many files have changed in this diff. Show more