---
Memory rework docs here https://github.com/cornerstonejs/cornerstone3D/pu…ll/1419
---
Here are the breaking changes from the cornerstone 1.x to 2.x.
Most of the changes are related to the new Segmentation model, but there are also changes in the DICOM Image Loader, Viewport APIs, Cache, and Events. Let's dive into the details.
## Building And Bundling
### Typescript Version
We have upgraded the typescript version from 4.6 to 5.5 in the 2.0 version of the cornerstone3D.
This upgrade most likely don't require any changes in your codebase, but it is recommended to update the typescript version in your project to 5.5
to avoid any issues in the future.
<details>
<summary>Why?</summary>
The upgrade to TypeScript 5.4 allows us to leverage the latest features and improvements offered by the TypeScript standard. You can read more about it here: https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/
</details>
### ECMAScript Target
In Cornerstone3D version 1.x, we targeted ES5. With the release of version 2.0, we have updated our target to `ES2022`.
<details>
<summary>Why?</summary>
It will result in a smaller bundle size and improved performance. There is a good chance that your setup already supports ES2022:
https://compat-table.github.io/compat-table/es2016plus/
</details>
### Remove of CJS
Starting with Cornerstone3D 2.x, we will no longer ship the CommonJS (CJS) build of the library. You most likely won't need to make any changes to your codebase. If you are aliasing the cjs library in your bundler, you can remove it completely.
<details>
<summary>Why?</summary>
Both Node.js and modern browsers now support ECMAScript Modules (ESM) by default. However, in the rare case where you need a non-ESM version, you can use the Universal Module Definition (UMD) build of the library.
</details>
---
## DICOM Image Loader
### Decoders Update
`@cornerstonejs/dicomImageLoader` previously utilized the old API for web workers, which is now deprecated. It has transitioned to the new web worker API via the `comlink` package. This change enables more seamless interaction with web workers and facilitates compiling and bundling the web workers to match the ESM version of the library.
<details>
<summary>Why?</summary>
To consolidate the web worker API using a new ES module format, which will enable new bundlers like `vite` to work seamlessly with the library.
</details>
### Removing support for non-worker decoders
We have removed support for non-web worker decoders in the 2.0 version of the cornerstone3D. This change is to ensure that the library is more performant and to reduce the bundle size.
<details>
<summary>Why?</summary>
We see no compelling reason to use non-worker decoders anymore. Web worker decoders offer superior performance and better compatibility with modern bundlers.
</details>
### DICOM Image Loader ESM default
We have changed the default export of the DICOM Image Loader to ESM in the 2.0 version of the cornerstone3D and correctly
publish types
This mean you don't need to have an alias for the dicom image loader anymore
<Tabs>
<TabItem value="Before" label="Before 📦 " default>
Probably in your webpack or other bundler you had this
```js
alias: {
'@cornerstonejs/dicom-image-loader':
'@cornerstonejs/dicom-image-loader/dist/dynamic-import/cornerstoneDICOMImageLoader.min.js',
},
```
</TabItem>
<TabItem value="After" label="After 🚀🚀">
Now you can remove this alias and use the default import
</TabItem>
</Tabs>
<details>
<summary>Why?</summary>
ESM is the future of JavaScript, and we want to ensure that the library is compatible with modern bundlers and tools.
</details>
### InitCornerstoneDICOMImageLoader
We have cleaned up how you initialize the DICOM Image Loader in the 2.0 version of the cornerstone3D:
<Tabs>
<TabItem value="Before" label="Before 📦 " default>
```js
cornerstoneDICOMImageLoader.external.cornerstone = cornerstone;
cornerstoneDICOMImageLoader.external.dicomParser = dicomParser;
cornerstoneDICOMImageLoader.configure({
useWebWorkers: true,
decodeConfig: {
convertFloatPixelDataToInt: false,
use16BitDataType: preferSizeOverAccuracy || useNorm16Texture,
},
});
let maxWebWorkers = 1;
if (navigator.hardwareConcurrency) {
maxWebWorkers = Math.min(navigator.hardwareConcurrency, 7);
}
var config = {
maxWebWorkers,
startWebWorkersOnDemand: false,
taskConfiguration: {
decodeTask: {
initializeCodecsOnStartup: false,
strict: false,
},
},
};
cornerstoneDICOMImageLoader.webWorkerManager.initialize(config);
```
</TabItem>
<TabItem value="After" label="After 🚀🚀">
```js
let maxWebWorkers = 1;
if (navigator.hardwareConcurrency) {
maxWebWorkers = Math.min(navigator.hardwareConcurrency, 7);
}
cornerstoneDICOMImageLoader.configure({
cornerstone,
dicomParser,
useWebWorkers: true,
maxWebWorkers,
decodeConfig: {
convertFloatPixelDataToInt: false,
use16BitDataType: preferSizeOverAccuracy || useNorm16Texture,
},
});
```
</TabItem>
</Tabs>
<details>
<summary>Why?</summary>
Due to circular dependencies in the previous version, we modified the initialization process for the DICOM image loader. This change enhances the library's robustness and simplifies maintenance.
</details>
---
## Viewport APIs
### Reset Camera
Previously, we had a `resetCamera` method that took positional arguments. Now it takes an object argument.
<Tabs>
<TabItem value="Before" label="Before 📦 " default>
```js
viewport.resetCamera(false, true, false);
```
</TabItem>
<TabItem value="After" label="After 🚀🚀">
```js
viewport.resetCamera({
resetZoom: true,
resetPan: false,
resetToCenter: false,
});
```
</TabItem>
</Tabs>
<details>
<summary>Why?</summary>
This change enhances our future development process by ensuring we won't need to modify the method signature later. It also improves readability for users calling the method.
</details>
### Rotation
The `rotation` property has been removed from `getProperties`
<Tabs>
<TabItem value="Before" label="Before 📦 " default>
```js
viewport.getProperties().rotation;
viewport.setProperties({ rotation: 10 });
```
</TabItem>
<TabItem value="After" label="After 🚀🚀">
```js
const { rotation } = viewport.getViewPresentation();
viewport.setViewPresentation({ rotation: 10 });
```
</TabItem>
</Tabs>
<details>
<summary>Why?</summary>
`rotation` is not a property of the viewport but rather a view prop. You can now access it through `getViewPresentation`.
</details>
### getReferenceId
is now `getViewReferenceId`
```js
getReferenceId-- > getViewReferenceId;
```
<details>
<summary>Why?</summary>
It is more accurate to use `getViewReferenceId` to reflect the actual function of the method since it returns view-specific information.
</details>
### Actor property `referenceId`
is now renamed to `referencedId`
<Tabs>
<TabItem value="Before" label="Before 📦 " default>
```js
export type ActorEntry = {
uid: string,
actor: Actor | VolumeActor | ImageActor | ICanvasActor,
/** the id of the reference volume from which this actor is derived or created*/
referenceId?: string,
slabThickness?: number,
clippingFilter?: any,
};
```
</TabItem>
<TabItem value="After" label="After 🚀🚀">
```js
export type ActorEntry = {
uid: string,
actor: Actor | VolumeActor | ImageActor | ICanvasActor,
/** the id of the referenced object (e.g., volume) from which this actor is derived or created*/
referencedId?: string,
slabThickness?: number,
clippingFilter?: any,
};
```
</TabItem>
</Tabs>
<details>
<summary>Why?</summary>
The term `referencedId` is more accurate and reflects the actual function of the property. It aligns with our library's naming conventions, such as `referencedImageId` and `referencedVolumeId`. Since an Actor can be derived from either a volume or an image, using `referencedId` instead of `referenceId` is more precise.
</details>
---
## Cache
### VolumeCache
By default when you create an image volume in the VolumeCache we allocate the memory for each image in the ImageCache as well.
You don't need to make any changes to your codebase
<details>
<summary>Why?</summary>
Since it's free, we can allocate memory for the images in the ImageCache and assign a view for their pixelData on a portion of the volume. This approach offers several benefits:
1. Converting between stack and volume viewports becomes faster.
2. When dealing with stack and volume labelmaps, updates in a volume viewport take effect instantly in the stack viewport and vice versa.
</details>
### ImageVolume
convertToCornerstoneImage is now deprecated in favor of getCornerstoneImage
```js
volume.convertToCornerstoneImage(imageId, imageIdIndex) --> volume.getCornerstoneImage(imageId, imageIdIndex)
```
<details>
<summary>Why?</summary>
1. The naming was incorrect. It was not actually a cornerstone image, but a cornerstone image load object, which is different.
2. It was a duplicate.
</details>
---
## Events and Event Details
### VOLUME_SCROLL_OUT_OF_BOUNDS
is now `VOLUME_VIEWPORT_SCROLL_OUT_OF_BOUNDS`
<details>
<summary>Why?</summary>
This change was made to maintain consistency with the naming of other events in the library.
</details>
### CameraModifiedEventDetail
Does not publish the `rotation` anymore, and it has moved to ICamera which is published in the event
```js
type CameraModifiedEventDetail = {
previousCamera: ICamera,
camera: ICamera,
element: HTMLDivElement,
viewportId: string,
renderingEngineId: string,
};
```
access the rotation from the camera object which previously was in the event detail root
### STACK_VIEWPORT_NEW_STACK publisher
Is not the element not the eventTarget
```js
eventTarget.addEventListener(Events.STACK_VIEWPORT_NEW_STACK, newStackHandler);
// should be now
element.addEventListener(Events.STACK_VIEWPORT_NEW_STACK, newStackHandler);
```
<details>
<summary>Why?</summary>
We made this change to maintain consistency, as all other events like VOLUME_NEW image were occurring on the element. This modification makes more sense because when the viewport has a new stack, it should trigger an event on the viewport element itself.
</details>
---
## Renaming and Nomenclature
### Units
In the annotation cachedStats you need to use the new units
```js
unit-- > lengthUnits;
areaUnit-- > areaUnits;
modalityUnit-- > pixelValueUnits;
```
Also the function `getModalityUnit` is now `getPixelValueUnits` if you were using it.
```js
getModalityUnit-- > getPixelValueUnits;
```
As a side effect `getCalibratedLengthUnitsAndScale` now returns `{areaUnits, lengthUnits, scale}` instead of `{units, areaUnits, scale}`
<details>
<summary>Why?</summary>
There was too much inconsistency in the units used throughout the library. We had `unit`, `areaUnits`, `modalityUnit`, and various others. Now, we have consolidated these units. You need to update your codebase to reflect the new unit system if you are hydrating annotations for Cornerstone3D.
In addition modalityUnit is now pixelValueUnits to reflect the correct term, since for a single modality there can be multiple pixel values (e.g, PT SUV, PT RAW, PT PROC)
</details>
## Other
### cloneDeep
The `structuredClone` function has replaced the previous method. You don't need to make any changes to your codebase that uses Cornerstone3D.
<details>
<summary>Why?</summary>
Why to depend on a third-party library when we can use the native browser API?
</details>
### Always Prescale
By default, Cornerstone3D always prescales images with the modality LUT. You probably don't need to make any changes to your codebase.
<details>
<summary>Why?</summary>
Previously, the decision to prescale was made by the viewport, and all viewports were doing it. However, we observed prescaling bugs in some custom image loaders that users had implemented. These issues have now been resolved by always prescaling.
</details>
### getDataInTime
The imageCoordinate is renamed to worldCoordinate in the 2.0 version of the cornerstone3D. As it
is the correct term and was misleading in the previous version.
<Tabs>
<TabItem value="Before" label="Before 📦 " default>
```js
const options = {
imageCoordinate
};
function getDataInTime(
dynamicVolume,
options
):
```
</TabItem>
<TabItem value="After" label="After 🚀🚀">
```js
const options = {
worldCoordinate
};
function getDataInTime(
dynamicVolume,
options
):
```
</TabItem>
</Tabs>
<details>
<summary>Why?</summary>
This is the way
</details>
### triggerAnnotationRenderForViewportIds
Now only requires viewportIds and doesn't need renderingEngine anymore
```js
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIds) ---> triggerAnnotationRenderForViewportIds(viewportIds)
```
<details>
<summary>Why?</summary>
Since there is one rendering engine per viewport, there is no need to pass the rendering engine as an argument.
</details>
---
## New Segmentation Model
### SegmentationDisplayTool
There's no need to add the SegmentationDisplayTool to the toolGroup anymore.
Before
```js
toolGroup2.addTool(SegmentationDisplayTool.toolName);
toolGroup1.setToolEnabled(SegmentationDisplayTool.toolName);
```
Now
```js
// nothing
```
<details>
<summary>Why?</summary>
We have eliminated the unnecessary connection between the toolGroup and segmentation display. The segmentation display now automatically appears in the viewport when you add a segmentation representation to it.
</details>
---
### Viewport-based Representations
In the 2.0 version of Cornerstone3D, we have transitioned from tool group-based segmentation representation rendering to viewport-based ones.
**Why? (important enough to not be collapsed)**
1. We discovered that tying rendering to a tool group is not an effective approach. In Cornerstone3D 1.x, segmentation rendering was linked to tool groups, which typically consist of multiple viewports. This created complications when users wanted to add segmentations to some viewports but not others within the same tool group. It often necessitated creating an extra tool group for a specific viewport to customize or prevent rendering.
2. We realized this decision was flawed. While it's appropriate for tools to be bound to tool groups, viewport-specific functionalities like segmentation rendering should be the responsibility of individual viewports. Son the second version of our library, we transitioned from tool group-based segmentation representations to viewport-based ones. Now, instead of adding or removing representations to a tool group, users can add them directly to viewports. This change provides much finer control over what each viewport renders. The new approach has proven to be highly effective, and we recognize its significant potential for further improvements.
3. In addition there were numerous methods using the term `segment` when they actually referred to `segmentIndex`. Many places used `segmentIndex` and `segment` interchangeably. Now, a `segment` is consistently referred to as a `segment`, and a `segmentIndex` is consistently referred to as a `segmentIndex`.
#### State
```js
// Add , remove, get
addSegmentationRepresentations(toolGroupId, representationsArray, config?) --> addSegmentationRepresentations(viewportId, representationsArray, config?)
addSegmentationRepresentation(toolGroupId, representation) --> addSegmentationRepresentation(viewportId, representation)
removeSegmentationsFromToolGroup(toolGroupId, representationUIDs) --> removeSegmentationRepresentations(viewportId, representationUIDs)
getSegmentationRepresentations(toolGroupId) --> getSegmentationRepresentations(viewportId)
//
getSegmentationRepresentationByUID(toolGroupId, representationUID) --> getSegmentationRepresentation(representationUID)
findSegmentationRepresentationByUID(repUID) --> getSegmentationRepresentation(representationUID)
```
As a result of moving from `toolGroup` to `viewports`, our segmentation state hierarchy has changed as well.
<Tabs>
<TabItem value="Before" label="Before 📦 " default>
```js
export type SegmentationState = {
colorLUT: Types.ColorLUT[];
segmentations: Segmentation[];
globalConfig: SegmentationRepresentationConfig;
toolGroups: {
[key: string]: {
segmentationRepresentations: ToolGroupSpecificRepresentations;
config: SegmentationRepresentationConfig;
};
};
};
```
</TabItem>
<TabItem value="After" label="After 🚀🚀">
```js
export type SegmentationState = {
colorLUT: Types.ColorLUT[];
segmentations: Segmentation[];
globalConfig: SegmentationRepresentationConfig;
representations: {
[key: string]: SegmentationRepresentation;
};
/** viewports association with segmentation representations */
viewports: {
[viewportId: string]: {
[segRepresentationUID: string]: {
visible: boolean;
active: boolean;
segmentsHidden: Set<number>;
};
};
};
};
```
</TabItem>
</Tabs>
```
As you see there is a new viewports object that holds the association between the viewports and the segmentation representations
### Config
Previously, we had three types of configurations: global, tool group-specific, and segment-specific. Let's examine how each has changed:
#### Global Config
Remains the same, only change is
```js
renderInactiveSegmentations --> renderInactiveRepresentations
```
#### Tool Group Specific and Segment Specific Config
Previously we had
- `segmentationRepresentationSpecificConfig` which was the config for the representation
- `segmentSpecificConfig` which was the config for the segments in that representation
Now we have moved to a single config at the root of the representation state level
<Tabs>
<TabItem value="Before" label="Before 📦 " default>
```ts
export type ToolGroupSpecificRepresentationState = {
/**
* Segmentation Representation UID
*/
segmentationRepresentationUID: string;
/**
* The segmentationId that this representation is derived from
*/
segmentationId: string;
/**
* The representation type
*/
type: Enums.SegmentationRepresentations;
/**
* Whether the segmentation is the active (manipulatable) segmentation or not
* which means it is inactive
*/
active: boolean;
/**
* Hidden segment indices in the segmentation
*/
segmentsHidden: Set<number>;
/**
* The index of the colorLUT from the state that this segmentationData is
* using to render
*/
colorLUTIndex: number;
/**
* Poly Seg generated
*/
polySeg?: {
enabled: boolean;
options?: any;
};
// rendering config
config: LabelmapRenderingConfig;
// appearance config
segmentationRepresentationSpecificConfig?: RepresentationConfig;
segmentSpecificConfig?: SegmentSpecificRepresentationConfig;
};
```
</TabItem>
<TabItem value="After" label="After 🚀🚀">
```ts
export type BaseSegmentationRepresentation = {
/**
* Segmentation Representation UID
*/
segmentationRepresentationUID: string;
/**
* The segmentationId that this representation is derived from
*/
segmentationId: string;
/**
* The representation type
*/
type: Enums.SegmentationRepresentations;
/**
* The index of the colorLUT from the state that this segmentationData is
* using to render
*/
colorLUTIndex: number;
/**
* Poly Seg generated
*/
polySeg?: {
enabled: boolean;
options?: any;
};
/** rendering config for display of this representation */
rendering: LabelmapRenderingConfig;
/** appearance config for display of this representation */
config: {
/** default configuration for the representation - applied to all segments*/
allSegments?: RepresentationConfig;
/**
* segment specific configuration for the representation, might be different
* for each segment. Use cases: to highligh a specific segment with a brighter
* color
*/
perSegment?: SegmentRepresentationConfig;
};
};
```
</TabItem>
</Tabs>
Note the `segmentationRepresentationSpecificConfig` and `segmentSpecificConfig` have been moved to the `config` object
and the config has been renamed to `rendering` to reflect the actual purpose of the object.
#### Methods
```js
getSegmentationRepresentationSpecificConfig(toolGroupId, segmentationRepresentationUID) --> getSegmentationRepresentationConfig(segmentationRepresentationUID)
setSegmentationRepresentationSpecificConfig(toolGroupId, segmentationRepresentationUID, config) --> setSegmentationRepresentationConfig(segmentationRepresentationUID, config)
```
and
```js
getSegmentSpecificConfig(toolGroupId, segmentationRepresentationUID, segmentIndex) --> getSegmentIndexConfig(segmentationRepresentationUID, segmentIndex)
setSegmentSpecificConfig(toolGroupId, segmentationRepresentationUID, segmentIndex, config) --> setSegmentIndexConfig(segmentationRepresentationUID, segmentIndex, config)
```
and we have removed the ToolGroupSpecificConfig both getters and setters
```js
getToolGroupSpecificConfig --> Removed
setToolGroupSpecificConfig --> Removed
```
---
### Active
```js
getActiveSegmentationRepresentation(toolGroupId) -> getActiveSegmentationRepresentation(viewportId)
setActiveSegmentationRepresentation(toolGroupId, representationUID) --> setActiveSegmentationRepresentation(viewportId, representationUID)
getActiveSegmentation(toolGroupId) --> getActiveSegmentation(viewportId)
```
### Other renaming
```js
getSegmentationIdRepresentations(segmentationId) --> getSegmentationRepresentationsForSegmentation(segmentationId)
```
```js
getToolGroupIdsWithSegmentation(segmentationId) --> getViewportIdsWithSegmentation(segmentationId)
```
### Visibility
```js
setSegmentationVisibility(toolGroupId, representationUID, visibility) --> setSegmentationRepresentationVisibility(viewportId, representationUID, visibility)
getSegmentationVisibility(toolGroupId, representationUId) --> getSegmentationRepresentationVisibility(viewportId, representationUID)
setSegmentsVisibility(toolGroupId, representationUID, segmentIndices, visibility) --> setSegmentIndicesVisibility(viewportId, representationUID, segmentIndices, visibility)
// segments
getSegmentVisibility(toolGroupId, representationUID, segmentIndex) -> getSegmentIndexVisibility(viewportId, representationUID, segmentIndex)
setSegmentVisibility(toolGroupId, representationUID, segmentIndex, visibility) -> setSegmentIndexVisibility(viewportId, representationUID, segmentIndex, visibility)
// Hidden
getSegmentsHidden(toolGroupId, representationUID) --> getHiddenSegmentIndices(viewportId, representationUID)
```
<details>
<summary>Why?</summary>
Since the visibility should be set on the representation, and segmentation is not the owner of the visibility, a segmentation can have
two representations with different visibility on each viewport
</details>
### Locking
```js
getLockedSegments -> getLockedSegmentIndices
```
### Color
```js
getColorForSegmentIndex --> getSegmentIndexColor
setColorForSegmentIndex --> setSegmentIndexColor
```
<details>
<summary>Why?</summary>
Consistency is key, we already had `setSegmentVisibility` and `getSegmentVisibility` and many more
</details>
### Stack Labelmaps
To create a Stack Labelmap, you no longer need to manually create a reference between labelmap imageIds and viewport imageIds. We now handle this process automatically for you.
<Tabs>
<TabItem value="Before" label="Before 📦 " default>
```js
segmentation.addSegmentations([
{
segmentationId,
representation: {
type: csToolsEnums.SegmentationRepresentations.Labelmap,
data: {
imageIdReferenceMap:
cornerstoneTools.utilities.segmentation.createImageIdReferenceMap(
imageIds,
segmentationImageIds
),
},
},
},
]);
```
</TabItem>
<TabItem value="After" label="After 🚀🚀">
```js
segmentation.addSegmentations([
{
segmentationId,
representation: {
type: csToolsEnums.SegmentationRepresentations.Labelmap,
data: {
imageIds: segmentationImageIds,
},
},
},
]);
```
</TabItem>
</Tabs>
<details>
<summary>Why?</summary>
This is a long Why ...
The previous model required users to provide an imageIdReferenceMap, which linked labelmap imageIds to viewport imageIds. This approach presented several challenges when implementing advanced segmentation use cases:
1. Manual creation of the map was error-prone, particularly regarding the order of imageIds.
2. Once a segmentation was associated with specific viewport imageIds, rendering it elsewhere became problematic. For example:
- Rendering a CT image stack segmentation on a single key image.
- Rendering a CT image stack segmentation on a stack that includes both CT and other images.
- Rendering a DX dual energy segmentation from energy 1 on energy 2.
- Rendering a CT labelmap from a stack viewport on a PT labelmap in the same space.
These scenarios highlight the limitations of the previous model.
We've now transitioned to a system where users only need to provide imageIds. During rendering, we match the viewport's current imageId against the labelmap imageIds and render the segmentation if there's a match. This matching process occurs in the SegmentationStateManager, with the criterion being that the segmentation must be in the same plane as the referenced viewport.
This new approach enables numerous additional use cases and offers greater flexibility in segmentation rendering.
</details>
---
#### Events
##### triggerSegmentationRepresentationModified
`triggerSegmentationRepresentationModified` now only requires the `representationUID`
```js
triggerSegmentationRepresentationModified(toolGroupId, representationUID) --> triggerSegmentationRepresentationModified(representationUID)
```
and it will not publish `toolGroupId` anymore
#### triggerSegmentationRepresentationRemoved
`triggerSegmentationRepresentationRemoved` now only requires the `representationUID`
```js
triggerSegmentationRepresentationRemoved(toolGroupId, representationUID) --> triggerSegmentationRepresentationRemoved(representationUID)
```
and it will not publish `toolGroupId` anymore
##### triggerSegmentationRender
Before, the function required a `toolGroupId`, but now it requires an optional `viewportId`. If you don't provide it, it will render segmentations of all viewports.
```js
triggerSegmentationRender(toolGroupId) --> triggerSegmentationRender(viewportId)
```
Additionally, there's a new method called `triggerSegmentationRenderBySegmentationId` which accepts a `segmentationId` and will render only that specific segmentation:
```js
triggerSegmentationRenderBySegmentationId(segmentationId);
```
### Other renaming
```js
getSegmentAtWorldPoint --> getSegmentIndexAtWorldPoint
getSegmentAtLabelmapBorder --> getSegmentIndexAtLabelmapBorder
getToolGroupIdFromSegmentationRepresentationUID --> removed since it's not needed anymore
```
<details>
<summary>Why?</summary>
Since it returns an index and not a segment
</details>