Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cornerstone 2.0 #1400

Open
wants to merge 81 commits into
base: main
Choose a base branch
from
Open

Cornerstone 2.0 #1400

wants to merge 81 commits into from

Conversation

sedghi
Copy link
Member

@sedghi sedghi commented Jul 22, 2024

Breaking Changes: Cornerstone 1.x to 2.x

While it may appear that many files have been changed, with almost all files affected, it's important to note that most changes involve fixing import types and removing dependency cycles. Let's examine these changes in more detail below.

We've addressed and documented all changes, but if you notice any undocumented modifications in the migration guides, please create a pull request to add them.

General

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 doesn'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.

Note

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/

ECMAScript Target

🔄 Before: In Cornerstone3D version 1.x, we targeted ES5.
🆕 After: With the release of version 2.0, we have updated our target to ES2022.

Note

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/

Remove of CJS, only ESM builds

🔄 Before: Cornerstone3D 1.x shipped with both CommonJS (CJS) and ECMAScript Modules (ESM) builds.
🆕 After: 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.

Note

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.

Package Exports

The Cornerstone libraries now utilize the exports field in their package.json files. This allows for more precise control over how modules are imported and ensures compatibility with different build systems.

Below are examples of how to import modules from each package, along with explanations of the exports field configuration.

@cornerstonejs/adapters
{
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "types": "./dist/esm/index.d.ts"
    },
    "./cornerstone": {
      "import": "./dist/esm/adapters/Cornerstone/index.js",
      "types": "./dist/esm/adapters/Cornerstone/index.d.ts"
    },
    "./cornerstone/*": {
      "import": "./dist/esm/adapters/Cornerstone/*.js",
      "types": "./dist/esm/adapters/Cornerstone/*.d.ts"
    },
    "./cornerstone3D": {
      "import": "./dist/esm/adapters/Cornerstone3D/index.js",
      "types": "./dist/esm/adapters/Cornerstone3D/index.d.ts"
    },
    "./cornerstone3D/*": {
      "import": "./dist/esm/adapters/Cornerstone3D/*.js",
      "types": "./dist/esm/adapters/Cornerstone3D/*.d.ts"
    },
    "./enums": {
      "import": "./dist/esm/adapters/enums/index.js",
      "types": "./dist/esm/adapters/enums/index.d.ts"
    }
    // ... other exports
  }
}

Import Examples:

import * as cornerstoneAdapters from '@cornerstonejs/adapters'; // Imports the main entry point
import * as cornerstoneAdapter from '@cornerstonejs/adapters/cornerstone'; // Imports the Cornerstone adapter
import { someModule } from '@cornerstonejs/adapters/cornerstone/someModule'; // Imports a specific module from the Cornerstone adapter
import * as cornerstone3DAdapter from '@cornerstonejs/adapters/cornerstone3D'; // Imports the Cornerstone3D adapter
// ... other imports
@cornerstonejs/core
{
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "types": "./dist/esm/index.d.ts"
    },
    "./utilities": {
      // Subpath export
      "import": "./dist/esm/utilities/index.js",
      "types": "./dist/esm/utilities/index.d.ts"
    },
    "./utilities/*": {
      // Wildcard subpath export
      "import": "./dist/esm/utilities/*.js",
      "types": "./dist/esm/utilities/*.d.ts"
    }
    // ... other exports
  }
}

Import Examples:

import * as cornerstoneCore from '@cornerstonejs/core'; // Imports the main entry point
import * as utilities from '@cornerstonejs/core/utilities'; // Imports the utilities module
import { someUtility } from '@cornerstonejs/core/utilities/someUtility'; // Imports a specific utility
// ... other imports
@cornerstonejs/tools
{
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "types": "./dist/esm/index.d.ts"
    },
    "./tools": {
      // Subpath export for tools
      "import": "./dist/esm/tools/index.js",
      "types": "./dist/esm/tools/index.d.ts"
    },
    "./tools/*": {
      // Wildcard subpath export for tools
      "import": "./dist/esm/tools/*.js",
      "types": "./dist/esm/tools/*.d.ts"
    }
    // ... other exports
  }
}

Import Examples:

import * as cornerstoneTools from '@cornerstonejs/tools'; // Imports the main entry point
import * as tools from '@cornerstonejs/tools/tools'; // Imports the tools module
import { someTool } from '@cornerstonejs/tools/tools/someTool'; // Imports a specific tool
// ... other imports
@cornerstonejs/dicom-image-loader
{
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "types": "./dist/esm/index.d.ts"
    },
    "./umd": {
      // UMD bundle export
      "import": "./dist/dynamic-import/cornerstoneDICOMImageLoader.min.js"
    },
    "./imageLoader": {
      // Subpath export for the image loader
      "import": "./dist/esm/imageLoader/index.js",
      "types": "./dist/esm/imageLoader/index.d.ts"
    }
    // ... other exports
  }
}

Import Examples:

import * as dicomImageLoader from '@cornerstonejs/dicom-image-loader'; // Imports the main entry point
import * as imageLoader from '@cornerstonejs/dicom-image-loader/imageLoader'; // Imports the imageLoader module specifically
// ... other imports

cloneDeep

🔄 Before: Used a custom or third-party cloneDeep function.
🆕 After: The structuredClone function has replaced the previous method.

You don't need to make any changes to your codebase that uses Cornerstone3D.

Note

Why depend on a third-party library when we can use the native browser API?


@cornerstonejs/streaming-image-volume-loader

After years of development on Cornerstone3D, we recognized that volume loading should be treated as a first-class feature rather than a separate library. As a result, we have merged all functionality related to streaming image loading into the core library.

  1. Removal of Separate Library: The @cornerstonejs/streaming-image-volume-loader package has been removed.
  2. Integration into Core: All streaming image volume loading functionality is now part of the @cornerstonejs/core package.

How to Migrate:

If you were previously using @cornerstonejs/streaming-image-volume-loader, you'll need to update your imports and potentially adjust your code to use the new integrated volume loading API in @cornerstonejs/core.

🔄 Before:

import {
  cornerstoneStreamingImageVolumeLoader,
  cornerstoneStreamingDynamicImageVolumeLoader,
  StreamingImageVolume,
  StreamingDynamicImageVolume,
  helpers,
  Enums,
} from '@cornerstonejs/streaming-image-volume-loader';
Enums.Events.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED;

🆕 After:

import {
  cornerstoneStreamingImageVolumeLoader,
  cornerstoneStreamingDynamicImageVolumeLoader,
  StreamingImageVolume,
  StreamingDynamicImageVolume,
} from '@cornerstonejs/core';
import { getDynamicVolumeInfo } from '@cornerstonejs/core/utilities';
import { Enums } from '@cornerstonejs/core/enums';
Enums.Events.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED;

Note

The helpers module has been replaced with specific utility functions like getDynamicVolumeInfo from the @cornerstonejs/core/utilities import.

Important

Make sure to update all relevant import statements throughout your codebase to reflect these changes.


@cornerstonejs/core

Initialization

Removal of detect-gpu and detectGPUConfig

🔄 Before: Used detect-gpu for GPU tier detection.
🆕 After: Default GPU tier of 2 (medium tier) is used, with option to configure.

Important

Cornerstone3D 2.x has removed the dependency on detect-gpu. This addresses issues in environments with restricted internet access.

Key Changes:

  1. Default GPU Tier: Now uses a default GPU tier of 2 (medium tier).
  2. No Internet Dependency: No longer requires internet access for GPU detection.
  3. Configurable GPU Tier: You can still configure your own GPU tier if needed.

How to Migrate:

If you were previously relying on detect-gpu, update your initialization code:

cornerstone3D.init({ gpuTier: 3 });

Removal of use16BitDataType

🔄 Before: Used use16BitDataType flag to request 16-bit data type from web worker.
🆕 After: Always uses native data type for cache storage and converts for rendering when necessary.

Removal of enableCacheOptimization

🔄 Before: Used enableCacheOptimization flag.
🆕 After: Cache optimization is automatic.

Volume Viewports Actor UID, ReferenceId, and VolumeId

Previous Behavior

When adding a volume to volume viewports, the logic used to determine the actor's UID was as follows:

const uid = actorUID || volumeId;
volumeActors.push({
  uid,
  actor,
  slabThickness,
  referenceId: volumeId,
});

Updated Behavior

We've made changes to improve clarity and functionality. The actor UID is now distinct:

const uid = actorUID || uuidv4();
volumeActors.push({
  uid,
  actor,
  slabThickness,
  referencedId: volumeId,
});

Key Changes

  1. Unique Actor UID: The actor UID is now always a unique identifier (uuidv4()).
  2. Renaming referenceId to referencedId: For improved clarity.

Migrations

🔄 Before:

const defaultActor = viewport.getDefaultActor();
const volumeId = defaultActor.uid;
const volume = cache.getVolume(volumeId);

or

volumeId = viewport.getDefaultActor()?.uid;
cache.getVolume(volumeId)?.metadata.Modality;

or

const { uid: volumeId } = viewport.getDefaultActor();

🆕 After:

const volume = cache.getVolume(viewport.getVolumeId());

Viewport APIs

ImageDataMetaData

🔄 Before:

interface ImageDataMetaData {
  // ... other properties
  numComps: number;
  // ... other properties
}

🆕 After:

export interface ImageDataMetaData {
  // ... other properties
  numberOfComponents: number;
  // ... other properties
}

Reset Camera

🔄 Before:

viewport.resetCamera(false, true, false);

🆕 After:

viewport.resetCamera({
  resetZoom: true,
  resetPan: false,
  resetToCenter: false,
});

Rotation

🔄 Before:

viewport.getProperties().rotation;
viewport.setProperties({ rotation: 10 });

🆕 After:

const { rotation } = viewport.getViewPresentation();
// or
const { rotation } = viewport.getCamera();

viewport.setViewPresentation({ rotation: 10 });
// or
viewport.setCamera({ rotation: 10 });

Note

rotation is not a property of the viewport but rather a view prop. You can now access it through getViewPresentation.

getReferenceId

🔄 Before: viewport.getReferenceId()
🆕 After: viewport.getViewReferenceId()

Note

It is more accurate to use getViewReferenceId to reflect the actual function of the method since it returns view-specific information, and not about the actor reference.

New PixelData Model and VoxelManager

Important

The Cornerstone library has undergone significant changes in how it handles image volumes and texture management.

Key changes include:

  1. Single source of truth (image cache)
  2. New volume creation approach
  3. Introduction of VoxelManager for tools
  4. Handling of non-image volumes
  5. Optimized caching mechanism
  6. Elimination of SharedArrayBuffer dependency

Introduction of VoxelManager

A new VoxelManager class has been introduced to handle voxel data more efficiently.

Migration Steps:

  1. Replace direct scalar data access with VoxelManager methods.
  2. Use voxelManager.getScalarDataLength() instead of scalarData.length.
  3. Use getAtIndex(index) and setAtIndex(index, value) for accessing and modifying voxel data.
  4. Use available VoxelManager methods for various operations.
  5. Use voxelManager.getArrayOfModifiedSlices() to get the list of modified slices.
  6. Use the forEach method for efficient iteration.
  7. Access volume information through appropriate properties.
  8. Handle RGB data appropriately.
  9. Consider performance optimizations.
  10. Use additional methods for dynamic volumes (4D datasets).

Example of migrating a simple volume processing function:

🔄 Before:

function processVolume(volume) {
  const scalarData = volume.getScalarData();
  for (let i = 0; i < scalarData.length; i++) {
    if (scalarData[i] > 100) {
      scalarData[i] = 100;
    }
  }
}

🆕 After:

function processVolume(volume) {
  const voxelManager = volume.voxelManager;
  const length = voxelManager.getScalarDataLength();
  for (let i = 0; i < length; i++) {
    const value = voxelManager.getAtIndex(i);
    if (value > 100) {
      voxelManager.setAtIndex(i, 100);
    }
  }
}

Note

For volumes (IImageVolume): Search your custom codebase for getScalarData or scalarData. Instead, use voxelManager to access the scalar data API.

Tip

If you can't use the atomic data API through getAtIndex and getAtIJK, you can fall back to voxelManager.getCompleteScalarDataArray() to rebuild the full scalar data array like cornerstone3D 1.0. However, this is not recommended due to performance and memory concerns. Use it only as a last resort.

Note

For stack images (IImage): You can still use image.getPixelData() OR access the scalarData array from the voxelManager with image.voxelManager.getScalarData().

Image Volume Construction

The construction of image volumes has been updated to use VoxelManager and new properties, eliminating the need for large scalar data arrays.

Note

There is no scalarData array in the volume object, and imageIds is sufficient to describe the volume.

🔄 Before:

const streamingImageVolume = new StreamingImageVolume({
  volumeId,
  metadata,
  dimensions,
  spacing,
  origin,
  direction,
  scalarData,
  sizeInBytes,
  imageIds,
});

🆕 After:

const streamingImageVolume = new StreamingImageVolume({
  volumeId,
  metadata,
  dimensions,
  spacing,
  origin,
  direction,
  imageIds,
  dataType,
  numberOfComponents,
});

Migration Steps:

  1. Remove scalarData and sizeInBytes from the constructor parameters.
  2. Add dataType and numberOfComponents to the constructor parameters.
  3. The VoxelManager will be created internally based on these parameters.

Note

This change reflects the shift from using large scalar data arrays to using the VoxelManager for data management. It allows for more efficient memory usage and better handling of streaming data.

Accessing Volume Properties

Some volume properties are now accessed differently due to the VoxelManager integration.

🔄 Before:

const numberOfComponents = imageData
  .getPointData()
  .getScalars()
  .getNumberOfComponents();

🆕 After:

const { numberOfComponents } = imageData.get('numberOfComponents');

Migration Steps:

  1. Replace getPointData().getScalars().getNumberOfComponents() with get('numberOfComponents').
  2. Use the destructuring syntax to extract the numberOfComponents property.

Important

These changes represent a significant update to the Cornerstone library's handling of image volumes and textures. The introduction of the VoxelManager and the elimination of large scalar data arrays for volumes provide several benefits:

  1. Reduced memory usage
  2. Improved performance
  3. Better streaming support
  4. More flexible data management

Developers will need to update their code to use the new VoxelManager API and adjust how they interact with volume data and textures.

Image Loader

Changes in Volume Creation Functions

The createLocalVolume function has been updated:

🔄 Before:

function createLocalVolume(
  options: LocalVolumeOptions,
  volumeId: string,
  preventCache = false
): IImageVolume {
  // ...
}

🆕 After:

function createLocalVolume(
  volumeId: string,
  options = {} as LocalVolumeOptions
): IImageVolume {
  // ...
}

Migration Steps:

  1. Update all calls to createLocalVolume by moving the volumeId parameter to the first position.
  2. Remove the preventCache parameter and handle caching separately if needed.

Changes in Derived Volume Creation

The createAndCacheDerivedVolume function now returns synchronously:

🔄 Before:

async function createAndCacheDerivedVolume(
  referencedVolumeId: string,
  options: DerivedVolumeOptions
): Promise<IImageVolume> {
  // ...
}

🆕 After:

function createAndCacheDerivedVolume(
  referencedVolumeId: string,
  options: DerivedVolumeOptions
): IImageVolume {
  // ...
}

Migration Steps:

  1. Remove await keywords when calling createAndCacheDerivedVolume.
  2. Update any code that expects a Promise to handle the synchronous return value.

Renamed Functions

  • createAndCacheDerivedSegmentationVolume is now createAndCacheDerivedLabelmapVolume
  • createLocalSegmentationVolume is now createLocalLabelmapVolume

Migration Steps:

  1. Update all calls to these functions with their new names.

Target Buffer Type Migration

The targetBufferType option has been replaced with a targetBuffer object:

🔄 Before:

interface DerivedImageOptions {
  targetBufferType?: PixelDataTypedArrayString;
  // ...
}

🆕 After:

interface DerivedImageOptions {
  targetBuffer?: {
    type: PixelDataTypedArrayString;
  };
  // ...
}

Migration Steps:

  1. Update all interfaces and function signatures that use targetBufferType to use targetBuffer instead.
  2. Change all occurrences of targetBufferType: 'SomeType' to targetBuffer: { type: 'SomeType' }.

Changes in createAndCacheDerivedImage Function

The function now returns an IImage object directly:

🔄 Before:

export function createAndCacheDerivedImage(
  referencedImageId: string,
  options: DerivedImageOptions = {},
  preventCache = false
): Promise<IImage> {
  // ...
  return imageLoadObject.promise;
}

🆕 After:

export function createAndCacheDerivedImage(
  referencedImageId: string,
  options: DerivedImageOptions = {}
): IImage {
  // ...
  return localImage;
}

Migration Steps:

  1. Update any code that expects a Promise from createAndCacheDerivedImage to work with the directly returned IImage object.
  2. Remove the preventCache parameter from function calls.

Segmentation Image Helpers

The segmentation image helper functions have been renamed and updated:

🔄 Before:

function createAndCacheDerivedSegmentationImages(
  referencedImageIds: Array<string>,
  options: DerivedImageOptions = {
    targetBufferType: 'Uint8Array',
  }
): DerivedImages {
  // ...
}

🆕 After:

function createAndCacheDerivedLabelmapImages(
  referencedImageIds: string[],
  options = {} as DerivedImageOptions
): IImage[] {
  return createAndCacheDerivedImages(referencedImageIds, {
    ...options,
    targetBuffer: { type: 'Uint8Array' },
  });
}

Migration Steps:

  1. Rename createAndCacheDerivedSegmentationImages to createAndCacheDerivedLabelmapImages.
  2. Rename createAndCacheDerivedSegmentationImage to createAndCacheDerivedLabelmapImage.
  3. Update function calls to use the new names and parameter structure.
  4. Remove any await or .then() calls when using createAndCacheDerivedLabelmapImage.

Tip

These changes aim to improve performance, reduce memory usage, and provide more efficient data access, especially for large datasets. While they require significant updates to existing code, they provide a more efficient and flexible foundation for working with large medical imaging datasets.

Cache Class

The Cache class has undergone significant changes in version 2:

Removal of Volume-specific Cache Size

🔄 Before: Separate volume cache size existed.
🆕 After: Only image cache is used.

Migration Steps:

  1. Remove any references to _volumeCacheSize if you had them.

isCacheable Method Update

The isCacheable method now considers shared cache keys.

New putImageSync and putVolumeSync Methods

🔄 Before: These methods did not exist.
🆕 After:

public putImageSync(imageId: string, image: IImage): void {
  // ... (validation code)
}

public putVolumeSync(volumeId: string, volume: IImageVolume): void {
  // ... (validation code)
}

Migration Steps:

  1. Use the new putImageSync and putVolumeSync methods when you need to add an image or volume to the cache synchronously.

Renaming and Nomenclature

Enums

Removal of SharedArrayBufferModes

The following methods have been removed from @cornerstonejs/core:

  • getShouldUseSharedArrayBuffer
  • setUseSharedArrayBuffer
  • resetUseSharedArrayBuffer

GeometryType

🔄 Before: GeometryType.CONTOUR, GeometryType.Surface
🆕 After: GeometryType.Contour, GeometryType.Surface

ViewportType

🔄 Before: ViewportType.WholeSlide
🆕 After: ViewportType.WHOLE_SLIDE

Example:

const viewportInput = {
    viewportId,
    type: ViewportType.WHOLE_SLIDE,
    element,
    defaultOptions: {
      background: <Types.Point3>[0.2, 0, 0.2],
    },
  };

  renderingEngine.enableElement(viewportInput);

Events and Event Details

VOLUME_SCROLL_OUT_OF_BOUNDS -> VOLUME_VIEWPORT_SCROLL_OUT_OF_BOUNDS

STACK_VIEWPORT_NEW_STACK -> VIEWPORT_NEW_IMAGE_SET

🔄 Before:

eventTarget.addEventListener(Events.VIEWPORT_NEW_IMAGE_SET, newStackHandler);

🆕 After:

element.addEventListener(Events.VIEWPORT_NEW_IMAGE_SET, newStackHandler);

Note

This change maintains consistency, as all other events like VOLUME_NEW_IMAGE were occurring on the element.

CameraModifiedEventDetail

The rotation is now accessed from the camera object in the event detail.

ImageVolumeModifiedEventDetail

The imageVolume is no longer available in the event detail. Use cache.getVolume method to get the imageVolume if needed.

4D Or Dynamic Volume

imageIdsGroups is now imageIdGroups

🔄 Before:

const { imageIdsGroups } = splitImageIdsBy4DTags(imageIds);

🆕 After:

const { imageIdGroups } = splitImageIdsBy4DTags(imageIds);

StreamingDynamicImageVolume

Constructor Changes

🔄 Before:

constructor(
  imageVolumeProperties: Types.ImageVolumeProps & { splittingTag: string },
  streamingProperties: Types.IStreamingVolumeProperties
) {
  // ...
}

🆕 After:

constructor(
  imageVolumeProperties: ImageVolumeProps & {
    splittingTag: string;
    imageIdGroups: string[][];
  },
  streamingProperties: IStreamingVolumeProperties
) {
  // ...
}

Migration Steps:

  1. Update the constructor call to include imageIdGroups instead of scalarData.
  2. Remove any code that previously handled scalarData arrays.

New Methods for ImageId Management

New methods introduced:

  • getCurrentTimePointImageIds()
  • flatImageIdIndexToTimePointIndex()
  • flatImageIdIndexToImageIdIndex()

Removal of getScalarData Method and Using VoxelManager for Dynamic Image Volumes

🔄 Before: Used getScalarData() method
🆕 After: Use VoxelManager methods

Example:

const voxelValue = volume.voxelManager.get(index);
const scalarData = volume.voxelManager.getCurrentTimePointScalarData();

Exports Imports

🔄 Before:

import {
  cornerstoneStreamingDynamicImageVolumeLoader,
  StreamingDynamicImageVolume,
  helpers,
  Enums,
} from '@cornerstonejs/streaming-image-volume-loader';

Enums.Events.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED;

🆕 After:

import {
  cornerstoneStreamingDynamicImageVolumeLoader,
  StreamingDynamicImageVolume,
} from '@cornerstonejs/core';

import { getDynamicVolumeInfo } from '@cornerstonejs/core/utilities';
import { Enums } from '@cornerstonejs/core/enums';

Enums.Events.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED;

getDataInTime

The imageCoordinate option is now worldCoordinate.

generateImageFromTimeData

🔄 Before:

function generateImageFromTimeData(
  dynamicVolume: Types.IDynamicImageVolume,
  operation: string,
  frameNumbers?: number[]
);

🆕 After:

function generateImageFromTimeData(
  dynamicVolume: Types.IDynamicImageVolume,
  operation: Enums.GenerateImageType,
  options: {
    frameNumbers?: number[];
  }
): Float32Array;

@cornerstonejs/tools

triggerAnnotationRenderForViewportIds

🔄 Before:

triggerAnnotationRenderForViewportIds(renderingEngine, viewportIds)

🆕 After:

triggerAnnotationRenderForViewportIds(viewportIds)

Tools

StackScrollMouseWheelTool -> StackScrollTool

🔄 Before:

cornerstoneTools.addTool(StackScrollMouseWheelTool);
toolGroup.addTool(StackScrollMouseWheelTool.toolName);
toolGroup.setToolActive(StackScrollMouseWheelTool.toolName);

🆕 After:

cornerstoneTools.addTool(StackScrollTool);
toolGroup.addTool(StackScrollTool.toolName);
toolGroup.setToolActive(StackScrollTool.toolName, {
  bindings: [
    {
      mouseButton: MouseBindings.Wheel,
    },
  ],
});

VolumeRotateMouseWheelTool -> StackScrollTool

🔄 Before:

cornerstoneTools.addTool(VolumeRotateMouseWheelTool);
mipToolGroup.addTool(VolumeRotateMouseWheelTool.toolName);
mipToolGroup.setToolActive(VolumeRotateMouseWheelTool.toolName);

🆕 After:

cornerstoneTools.addTool(StackScrollTool);
mipToolGroup.addTool(StackScrollTool.toolName, {
  rotate: {
    enabled: true,
    direction: DIRECTIONS.Z,
    rotateIncrementDegrees: 30,
  },
});
mipToolGroup.setToolActive(StackScrollTool.toolName, {
  bindings: [
    {
      mouseButton: MouseBindings.Wheel,
    },
  ],
});

BaseTool

🔄 Before:

const volumeId = this.getTargetVolumeId(viewport);
const imageData = this.getTargetIdImage(targetId, renderingEngine);

🆕 After:

const imageData = this.getTargetImageData(targetId);

ToolGroups

Note

If the ID for a toolgroup already exists, it will return that toolgroup. Previously, it would throw an error.

Here's the converted version of the migration guide for the new segmentation model in Cornerstone3D 2.0, using GitHub Markdown format:

New Segmentation Model

Important

In Cornerstone3D 2.0, we've transitioned from tool group-based segmentation representation rendering to viewport-based ones.

Why?

  1. Tying rendering to a tool group was not effective. It created complications when users wanted to add segmentations to some viewports but not others within the same tool group.
  2. 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.

Segmentation State

The Segmentation type has been restructured:

🔄 Before:

type Segmentation = {
  segmentationId: string;
  type: Enums.SegmentationRepresentations;
  label: string;
  activeSegmentIndex: number;
  segmentsLocked: Set<number>;
  cachedStats: { [key: string]: number };
  segmentLabels: { [key: string]: string };
  representationData: SegmentationRepresentationData;
};

🆕 After:

type Segmentation = {
  segmentationId: string;
  label: string;
  segments: {
    [segmentIndex: number]: Segment;
  };
  representationData: RepresentationsData;
};

type Segment = {
  segmentIndex: number;
  label: string;
  locked: boolean;
  cachedStats: { [key: string]: unknown };
  active: boolean;
};

Representation Data Key

The SegmentationRepresentations enum has been updated to use title case:

🔄 Before:

enum SegmentationRepresentations {
  Labelmap = 'LABELMAP',
  Contour = 'CONTOUR',
  Surface = 'SURFACE',
}

🆕 After:

enum SegmentationRepresentations {
  Labelmap = 'Labelmap',
  Contour = 'Contour',
  Surface = 'Surface',
}

Segmentation Representation

The representation structure is now viewport-specific:

🔄 Before:

type SegmentationState = {
  toolGroups: {
    [key: string]: {
      segmentationRepresentations: ToolGroupSpecificRepresentations;
      config: SegmentationRepresentationConfig;
    };
  };
};

🆕 After:

type SegmentationState = {
  viewportSegRepresentations: {
    [viewportId: string]: Array<SegmentationRepresentation>;
  };
};

Removal of SegmentationDisplayTool

🔄 Before:

toolGroup2.addTool(SegmentationDisplayTool.toolName);
toolGroup1.setToolEnabled(SegmentationDisplayTool.toolName);

🆕 After:

// No need to add or enable SegmentationDisplayTool

Stack Labelmaps

🔄 Before:

segmentation.addSegmentations([
  {
    segmentationId,
    representation: {
      type: csToolsEnums.SegmentationRepresentations.Labelmap,
      data: {
        imageIdReferenceMap:
          cornerstoneTools.utilities.segmentation.createImageIdReferenceMap(
            imageIds,
            segmentationImageIds
          ),
      },
    },
  },
]);

🆕 After:

segmentation.addSegmentations([
  {
    segmentationId,
    representation: {
      type: csToolsEnums.SegmentationRepresentations.Labelmap,
      data: {
        imageIds: segmentationImageIds,
      },
    },
  },
]);

Adding Segmentations

Function Signature Update

🔄 Before:

function addSegmentations(
  segmentationInputArray: SegmentationPublicInput[]
): void

🆕 After:

function addSegmentations(
  segmentationInputArray: SegmentationPublicInput[],
  suppressEvents?: boolean
): void

SegmentationPublicInput Type Updates

🔄 Before:

type SegmentationPublicInput = {
  segmentationId: string;
  representation: {
    type: Enums.SegmentationRepresentations;
    data?: RepresentationData;
  };
};

🆕 After:

type SegmentationPublicInput = {
  segmentationId: string;
  representation: {
    type: Enums.SegmentationRepresentations;
    data?: RepresentationData;
  };
  config?: {
    segments?: {
      [segmentIndex: number]: Partial<Segment>;
    };
    label?: string;
  };
};

Adding Segmentation Representations

Viewport-Centric Approach

🔄 Before:

function addSegmentationRepresentations(
  toolGroupId: string,
  representationInputArray: RepresentationPublicInput[],
  toolGroupSpecificRepresentationConfig?: SegmentationRepresentationConfig
): Promise<string[]>;

🆕 After:

function addSegmentationRepresentations(
  viewportId: string,
  segmentationInputArray: RepresentationPublicInput[]
);

RepresentationPublicInput Changes

🔄 Before:

type RepresentationPublicInput = {
  segmentationId: string;
  type: Enums.SegmentationRepresentations;
  options?: {
    segmentationRepresentationUID?: string;
    colorLUTOrIndex?: Types.ColorLUT | number;
    polySeg?: {
      enabled: boolean;
      options?: any;
    };
  };
};

🆕 After:

type RepresentationPublicInput = {
  segmentationId: string;
  type?: Enums.SegmentationRepresentations;
  config?: {
    colorLUTOrIndex?: Types.ColorLUT[] | number;
  };
};

New Representation-Specific Functions

🆕 After:

function addContourRepresentationToViewport(
  viewportId: string,
  contourInputArray: RepresentationPublicInput[]
);

function addLabelmapRepresentationToViewport(
  viewportId: string,
  labelmapInputArray: RepresentationPublicInput[]
);

function addSurfaceRepresentationToViewport(
  viewportId: string,
  surfaceInputArray: RepresentationPublicInput[]
);

Multi-Viewport Functions

🆕 After:

function addContourRepresentationToViewportMap(viewportInputMap: {
  [viewportId: string]: RepresentationPublicInput[];
});

function addLabelmapRepresentationToViewportMap(viewportInputMap: {
  [viewportId: string]: RepresentationPublicInput[];
});

function addSurfaceRepresentationToViewportMap(viewportInputMap: {
  [viewportId: string]: RepresentationPublicInput[];
});

Events

Removal of ToolGroup Specific Events

🔄 Before:

function triggerSegmentationRepresentationModified(
  toolGroupId: string,
  segmentationRepresentationUID?: string
): void

function triggerSegmentationRepresentationRemoved(
  toolGroupId: string,
  segmentationRepresentationUID: string
): void

🆕 After:

function triggerSegmentationRepresentationModified(
  viewportId: string,
  segmentationId: string,
  type?: SegmentationRepresentations
): void

function triggerSegmentationRepresentationRemoved(
  viewportId: string,
  segmentationId: string,
  type: SegmentationRepresentations
): void

Simplified Segmentation Modified Event

🔄 Before:

function triggerSegmentationModified(segmentationId?: string): void

🆕 After:

function triggerSegmentationModified(segmentationId: string): void

Updated Event Detail Types

🔄 Before:

type SegmentationRepresentationModifiedEventDetail = {
  toolGroupId: string;
  segmentationRepresentationUID: string;
};

type SegmentationRepresentationRemovedEventDetail = {
  toolGroupId: string;
  segmentationRepresentationUID: string;
};

type SegmentationRenderedEventDetail = {
  viewportId: string;
  toolGroupId: string;
};

🆕 After:

type SegmentationRepresentationModifiedEventDetail = {
  segmentationId: string;
  type: string;
  viewportId: string;
};

type SegmentationRepresentationRemovedEventDetail = {
  segmentationId: string;
  type: string;
  viewportId: string;
};

type SegmentationRenderedEventDetail = {
  viewportId: string;
  segmentationId: string;
  type: string;
};

Segmentation Config/Style

New Functions

  • getStyle(specifier)
  • setStyle(specifier, style)
  • setRenderInactiveSegmentations(viewportId, renderInactiveSegmentations)
  • getRenderInactiveSegmentations(viewportId)
  • resetToGlobalStyle()
  • hasCustomStyle(specifier)

Getting Global Segmentation Config

🔄 Before:

const globalConfig = getGlobalConfig();
const labelmapConfig = getGlobalRepresentationConfig(
  SegmentationRepresentations.Labelmap
);

🆕 After:

const labelmapStyle = getStyle({ type: SegmentationRepresentations.Labelmap });

Setting Global Segmentation Config

🔄 Before:

setGlobalConfig(newGlobalConfig);
setGlobalRepresentationConfig(
  SegmentationRepresentations.Labelmap,
  newLabelmapConfig
);

🆕 After:

setStyle({ type: SegmentationRepresentations.Labelmap }, newLabelmapStyle);

Getting and Setting ToolGroup-Specific Config

🔄 Before:

const toolGroupConfig = getToolGroupSpecificConfig(toolGroupId);
setToolGroupSpecificConfig(toolGroupId, newToolGroupConfig);

🆕 After:

setStyle(
  {
    viewportId: 'viewport1',
    segmentationId: 'segmentation1',
    type: SegmentationRepresentations.Labelmap,
  },
  newLabelmapStyle
);

const style = getStyle({
  viewportId: 'viewport1',
  segmentationId: 'segmentation1',
  type: SegmentationRepresentations.Labelmap,
});

Getting and Setting Segmentation Representation-Specific Config

🔄 Before:

const representationConfig = getSegmentationRepresentationSpecificConfig(
  toolGroupId,
  segmentationRepresentationUID
);

setSegmentationRepresentationSpecificConfig(
  toolGroupId,
  segmentationRepresentationUID,
  {
    LABELMAP: {
      renderOutline: true,
      outlineWidth: 2,
    },
  }
);

🆕 After:

const style = getStyle({
  segmentationId: 'segmentation1',
  type: SegmentationRepresentations.Labelmap,
});

setStyle(
  {
    segmentationId: 'segmentation1',
    type: SegmentationRepresentations.Labelmap,
  },
  {
    renderOutline: true,
    outlineWidth: 2,
  }
);

Getting and Setting Segment-Specific Config

🔄 Before:

const segmentConfig = getSegmentSpecificConfig(
  toolGroupId,
  segmentationRepresentationUID,
  segmentIndex
);

setSegmentSpecificConfig(
  toolGroupId,
  segmentationRepresentationUID,
  segmentIndex,
  newSegmentConfig
);

🆕 After:

setStyle(
  {
    segmentationId: 'segmentation1',
    type: SegmentationRepresentations.Labelmap,
    segmentIndex: 1,
  },
  newSegmentStyle
);

const segmentStyle = getStyle({
  segmentationId: 'segmentation1',
  type: SegmentationRepresentations.Labelmap,
  segmentIndex: 1,
});

Note

These changes represent a significant update to the Cornerstone library's handling of segmentations. The new approach provides more flexibility and better control over segmentation representations at the viewport level.

Here's the converted version of the migration guide for the additional changes in Cornerstone3D 2.0, using GitHub Markdown format:

Setting Render Inactive Segmentations

🔄 Before:

setGlobalConfig({ renderInactiveSegmentations: true });

🆕 After:

// Set whether to render inactive segmentations in a viewport
setRenderInactiveSegmentations(viewportId, true);

// Get whether inactive segmentations are rendered in a viewport
const renderInactive = getRenderInactiveSegmentations(viewportId);

Resetting to Global Style

🆕 After:

resetToGlobalStyle();

Example Migration

🔄 Before:

import {
  getGlobalConfig,
  getGlobalRepresentationConfig,
  getToolGroupSpecificConfig,
  setGlobalConfig,
  setGlobalRepresentationConfig,
  setToolGroupSpecificConfig,
  setSegmentSpecificConfig,
  getSegmentSpecificConfig,
  setSegmentationRepresentationSpecificConfig,
  getSegmentationRepresentationSpecificConfig,
} from './segmentationConfig';

// Get the global segmentation config
const globalConfig = getGlobalConfig();

// Set global representation config
setGlobalRepresentationConfig(SegmentationRepresentations.Labelmap, {
  renderOutline: true,
  outlineWidth: 2,
});

// Set toolGroup-specific config
setToolGroupSpecificConfig(toolGroupId, {
  representations: {
    LABELMAP: {
      renderOutline: false,
    },
  },
});

// Set segment-specific config
setSegmentSpecificConfig(
  toolGroupId,
  segmentationRepresentationUID,
  segmentIndex,
  {
    LABELMAP: {
      renderFill: false,
    },
  }
);

🆕 After:

import {
  getStyle,
  setStyle,
  setRenderInactiveSegmentations,
  getRenderInactiveSegmentations,
  resetToGlobalStyle,
  hasCustomStyle,
} from '@cornerstonejs/core';

// Get the global style for Labelmap representation
const labelmapStyle = getStyle({ type: SegmentationRepresentations.Labelmap });

// Set the global style for Labelmap representation
setStyle(
  { type: SegmentationRepresentations.Labelmap },
  {
    renderOutline: true,
    outlineWidth: 2,
  }
);

// Set style for a specific viewport and segmentation
setStyle(
  {
    viewportId: 'viewport1',
    segmentationId: 'segmentation1',
    type: SegmentationRepresentations.Labelmap,
  },
  {
    renderOutline: false,
  }
);

// Set style for a specific segment
setStyle(
  {
    segmentationId: 'segmentation1',
    type: SegmentationRepresentations.Labelmap,
    segmentIndex: segmentIndex,
  },
  {
    renderFill: false,
  }
);

// Set render inactive segmentations for a viewport
setRenderInactiveSegmentations('viewport1', true);

// Get render inactive segmentations setting for a viewport
const renderInactive = getRenderInactiveSegmentations('viewport1');

// Reset all styles to global
resetToGlobalStyle();

Note

  • The new getStyle and setStyle functions provide a unified way to manage segmentation styles across different levels.
  • The specifier object allows you to target specific viewports, segmentations, and segments.
  • The effective style is determined by a hierarchy that considers global styles, segmentation-specific styles, and viewport-specific styles.

Active

Viewport-based Operations

🔄 Before:

function getActiveSegmentationRepresentation(toolGroupId: string);
function getActiveSegmentation(toolGroupId: string);
function setActiveSegmentationRepresentation(
  toolGroupId: string,
  segmentationRepresentationUID: string
);

🆕 After:

function getActiveSegmentation(viewportId: string);
function setActiveSegmentation(
  viewportId: string,
  segmentationId: string,
  suppressEvent: boolean = false
);

Migration Steps:

  1. Replace toolGroupId with viewportId in function calls.
  2. Update getActiveSegmentationRepresentation and getActiveSegmentation calls to use the new getActiveSegmentation function.
  3. Replace setActiveSegmentationRepresentation calls with setActiveSegmentation, using the new parameter structure.

Visibility

Viewport-Centric Approach

🔄 Before:

function setSegmentationVisibility(
  toolGroupId: string,
  segmentationRepresentationUID: string,
  visibility: boolean
): void

🆕 After:

function setSegmentationRepresentationVisibility(
  viewportId: string,
  specifier: {
    segmentationId: string;
    type?: SegmentationRepresentations;
  },
  visibility: boolean
): void

Migration Steps:

  1. Replace toolGroupId with viewportId in function calls.
  2. Use a specifier object instead of segmentationRepresentationUID.
  3. Include segmentationId in the specifier object.
  4. Optionally specify the type of segmentation representation.

Segment-Level Visibility Control

🔄 Before:

function setSegmentVisibility(
  toolGroupId: string,
  segmentationRepresentationUID: string,
  segmentIndex: number,
  visibility: boolean
): void

🆕 After:

function setSegmentIndexVisibility(
  viewportId: string,
  specifier: {
    segmentationId: string;
    type?: SegmentationRepresentations;
  },
  segmentIndex: number,
  visibility: boolean
): void

Migration Steps:

  1. Update function names from setSegmentVisibility to setSegmentIndexVisibility.
  2. Replace toolGroupId with viewportId.
  3. Use a specifier object with segmentationId and optional type instead of segmentationRepresentationUID.

Locking

Retrieving Locked Segments

🔄 Before:

function getLockedSegments(segmentationId: string): number[] | []

🆕 After:

function getLockedSegmentIndices(segmentationId: string): number[] | []

Migration Steps:

  1. Update all calls from getLockedSegments to getLockedSegmentIndices.

Color

Viewport-Centric Approach

🔄 Before:

function setColorLUT(
  toolGroupId: string,
  segmentationRepresentationUID: string,
  colorLUTIndex: number
): void

🆕 After:

function setColorLUT(
  viewportId: string,
  segmentationId: string,
  colorLUTsIndex: number
): void

Migration Steps:

  1. Replace toolGroupId with viewportId in function calls.
  2. Replace segmentationRepresentationUID with segmentationId.

Color LUT Management

🔄 Before:

function addColorLUT(colorLUT: Types.ColorLUT, colorLUTIndex: number): void

🆕 After:

function addColorLUT(colorLUT: Types.ColorLUT, colorLUTIndex?: number): number

Migration Steps:

  1. Update calls to addColorLUT to handle the returned index if needed.
  2. Make the colorLUTIndex parameter optional in function calls.

Segment Color Retrieval and Setting

🔄 Before:

function getColorForSegmentIndex(
  toolGroupId: string,
  segmentationRepresentationUID: string,
  segmentIndex: number
): Types.Color

function setColorForSegmentIndex(
  toolGroupId: string,
  segmentationRepresentationUID: string,
  segmentIndex: number,
  color: Types.Color
): void

🆕 After:

function getSegmentIndexColor(
  viewportId: string,
  segmentationId: string,
  segmentIndex: number
): Types.Color

function setSegmentIndexColor(
  viewportId: string,
  segmentationId: string,
  segmentIndex: number,
  color: Types.Color
): void

Migration Steps:

  1. Rename getColorForSegmentIndex to getSegmentIndexColor.
  2. Rename setColorForSegmentIndex to setSegmentIndexColor.
  3. Update function calls to use viewportId instead of toolGroupId.
  4. Replace segmentationRepresentationUID with segmentationId in function calls.

Other Changes

Renaming

getSegmentAtWorldPoint --> getSegmentIndexAtWorldPoint
getSegmentAtLabelmapBorder --> getSegmentIndexAtLabelmapBorder

getToolGroupIdsWithSegmentation

🔄 Before:

function getToolGroupIdsWithSegmentation(segmentationId: string): string[]

🆕 After:

function getViewportIdsWithSegmentation(segmentationId: string): string[]

Migration Steps:

  1. Replace getToolGroupIdsWithSegmentation with getViewportIdsWithSegmentation.

Important

These changes reflect a significant shift from tool group-based to viewport-based operations in Cornerstone3D 2.0. Make sure to update all relevant parts of your codebase to align with this new approach.

Here's the converted version of the migration guide for the additional changes in Cornerstone3D 2.0, using GitHub Markdown format:

Segmentation Representation Management

🔄 Before:

function addSegmentationRepresentation(
  toolGroupId: string,
  segmentationRepresentation: ToolGroupSpecificRepresentation,
  suppressEvents?: boolean
): void

function getSegmentationRepresentationByUID(
  toolGroupId: string,
  segmentationRepresentationUID: string
): ToolGroupSpecificRepresentation | undefined

function removeSegmentationRepresentation(
  toolGroupId: string,
  segmentationRepresentationUID: string
): void

🆕 After:

function addSegmentationRepresentation(
  viewportId: string,
  segmentationRepresentation: SegmentationRepresentation,
  suppressEvents?: boolean
): void

function getSegmentationRepresentation(
  viewportId: string,
  specifier: {
    segmentationId: string;
    type: SegmentationRepresentations;
  }
): SegmentationRepresentation | undefined

function removeSegmentationRepresentation(
  viewportId: string,
  specifier: {
    segmentationId: string;
    type: SegmentationRepresentations;
  },
  immediate?: boolean
): void

Migration Steps:

  1. Update all calls to addSegmentationRepresentation to use viewportId instead of toolGroupId.
  2. Replace getSegmentationRepresentationByUID with getSegmentationRepresentation, using the new specifier object.
  3. Update removeSegmentationRepresentation calls to use the new specifier object instead of segmentationRepresentationUID.

PolySEG

Import

🆕 After:

async function peerImport(moduleId) {
  if (moduleId === '@icr/polyseg-wasm') {
    return import('@icr/polyseg-wasm');
  }
}

import { init } from '@cornerstonejs/core';

await init({ peerImport });

Options

🔄 Before:

await segmentation.addSegmentationRepresentations(toolGroupId2, [
  {
    segmentationId,
    type: csToolsEnums.SegmentationRepresentations.Labelmap,
    options: {
      polySeg: {
        enabled: true,
      },
    },
  },
]);

🆕 After:

await segmentation.addSegmentationRepresentations(viewportId2, [
  {
    segmentationId,
    type: csToolsEnums.SegmentationRepresentations.Labelmap,
  },
]);

Actor UID for labelmaps

🔄 Before:

const volumeInputs: Types.IVolumeInput[] = [
  {
    volumeId: labelMapData.volumeId,
    actorUID: segmentationRepresentationUID,
    visibility,
    blendMode: Enums.BlendModes.MAXIMUM_INTENSITY_BLEND,
  },
];

🆕 After:

const volumeInputs: Types.IVolumeInput[] = [
  {
    volumeId,
    actorUID: `${segmentationId}-${SegmentationRepresentations.Labelmap}`,
    visibility,
    blendMode: Enums.BlendModes.MAXIMUM_INTENSITY_BLEND,
  },
];

New function for getting segmentation actor:

export function getSegmentationActor(
  viewportId: string,
  specifier: {
    segmentationId: string;
    type: SegmentationRepresentations;
  }
): Types.VolumeActor | Types.ImageActor | undefined;

New Utilities

🆕 After:

function clearSegmentValue(
  segmentationId: string,
  segmentIndex: number
)

Renaming and Nomenclature

Types

🔄 Before: PointsManager
🆕 After: IPointsManager

Migration:

import { IPointsManager } from '@cornerstonejs/tools/types';

Units

getCalibratedLengthUnitsAndScale Signature

🔄 Before:

const getCalibratedLengthUnitsAndScale = (image, handles) => {
  // ...
  return { units, areaUnits, scale };
};

🆕 After:

const getCalibratedLengthUnitsAndScale = (image, handles) => {
  // ...
  return { unit, areaUnit, scale };
};

getModalityUnit -> getPixelValueUnits

BasicStatsCalculator

🔄 Before: noPointsCollection
🆕 After: storePointData

Renaming

  • getSegmentAtWorldPoint -> getSegmentIndexAtWorldPoint
  • getSegmentAtLabelmapBorder -> getSegmentIndexAtLabelmapBorder

Others

roundNumber

🆕 After:

import { roundNumber } from '@cornerstonejs/core/utilities';

pointInShapeCallback

🆕 After:

import { pointInShapeCallback } from '@cornerstonejs/core/utilities';

const pointsInShape = pointInShapeCallback(imageData, {
  pointInShapeFn: shapeFnCriteria,
  callback: (point) => {
    // callback logic for each point
  },
  boundsIJK: boundsIJK,
  returnPoints: true, // Optionally, to return the points inside the shape
});

@cornerstonejs/dicom-image-loader

Initialization

🔄 Before:

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);

🆕 After:

cornerstoneDICOMImageLoader.configure({
  cornerstone,
  dicomParser,
  maxWebWorkers: 2, // or whatever number you want
});

Decoders Update

Remove the following rule from your bundler configuration:

{
  test: /\.worker\.(mjs|js|ts)$/,
  use: [
    {
      loader: 'worker-loader',
    },
  ],
},

Always Prescale

Note

By default, Cornerstone3D always prescales images with the modality LUT (rescale slope and intercept). You probably don't need to make any changes to your codebase.

Removal of minAfterScale and maxAfterScale on imageFrame

Use smallestPixelValue and largestPixelValue instead.

DICOM Image Loader ESM default

🔄 Before:

 alias: {
  '@cornerstonejs/dicom-image-loader':
    '@cornerstonejs/dicom-image-loader/dist/dynamic-import/cornerstoneDICOMImageLoader.min.js',
},

🆕 After: Remove this alias and use the default import.

@cornerstonejs/nifti-image-volume-loader

🔄 Before:

const niftiURL =
  'https://ohif-assets.s3.us-east-2.amazonaws.com/nifti/MRHead.nii.gz';
const volumeId = 'nifti:' + niftiURL;

const volume = await volumeLoader.createAndCacheVolume(volumeId);

setVolumesForViewports(
  renderingEngine,
  [{ volumeId }],
  viewportInputArray.map((v) => v.viewportId)
);

🆕 After:

import {
  cornerstoneNiftiImageLoader,
  createNiftiImageIdsAndCacheMetadata,
} from '@cornerstonejs/nifti-volume-loader';

const niftiURL =
  'https://ohif-assets.s3.us-east-2.amazonaws.com/nifti/CTACardio.nii.gz';

// register the image loader for nifti files
imageLoader.registerImageLoader('nifti', cornerstoneNiftiImageLoader);

// similar to the rest of the cornerstone3D image loader
const imageIds = await createNiftiImageIdsAndCacheMetadata({ url: niftiURL });

// For stack viewports
viewport.setStack(imageIds);

// for volume viewports
const volume = await volumeLoader.createAndCacheVolume(volumeId, {
  imageIds,
});

await volume.load();
setVolumesForViewports(
  renderingEngine,
  [{ volumeId }],
  viewportInputArray.map((v) => v.viewportId)
);

Developer Experience

Dependency Cycles

Run yarn run format-check to ensure correct formatting and check for dependencies.

Published APIs

Run yarn run build:update-api and include the generated files in your PR.

Karma tests

Setup and Cleanup

🔄 Before:

beforeEach(function () {
  csTools3d.init();
  csTools3d.addTool(BidirectionalTool);
  cache.purgeCache();
  this.DOMElements = [];
  this.stackToolGroup = ToolGroupManager.createToolGroup('stack');
  this.stackToolGroup.addTool(BidirectionalTool.toolName, {
    configuration: { volumeId: volumeId },
  });
  this.stackToolGroup.setToolActive(BidirectionalTool.toolName, {
    bindings: [{ mouseButton: 1 }],
  });

  this.renderingEngine = new RenderingEngine(renderingEngineId);
  imageLoader.registerImageLoader('fakeImageLoader', fakeImageLoader);
  volumeLoader.registerVolumeLoader('fakeVolumeLoader', fakeVolumeLoader);
  metaData.addProvider(fakeMetaDataProvider, 10000);
});

afterEach(function () {
  csTools3d.destroy();
  cache.purgeCache();
  eventTarget.reset();
  this.renderingEngine.destroy();
  metaData.removeProvider(fakeMetaDataProvider);
  imageLoader.unregisterAllImageLoaders();
  ToolGroupManager.destroyToolGroup('stack');

  this.DOMElements.forEach((el) => {
    if (el.parentNode) {
      el.parentNode.removeChild(el);
    }
  });
});

🆕 After:

beforeEach(function () {
  const testEnv = testUtils.setupTestEnvironment({
    renderingEngineId,
    toolGroupIds: ['default'],
    viewportIds: [viewportId],
    tools: [BidirectionalTool],
    toolConfigurations: {
      [BidirectionalTool.toolName]: {
        configuration: { volumeId: volumeId },
      },
    },
    toolActivations: {
      [BidirectionalTool.toolName]: {
        bindings: [{ mouseButton: 1 }],
      },
    },
  });
  renderingEngine = testEnv.renderingEngine;
  toolGroup = testEnv.toolGroups['default'];
});

afterEach(function () {
  testUtils.cleanupTestEnvironment({
    renderingEngineId,
    toolGroupIds: ['default'],
  });
});

Viewport Creation

🆕 After:

const element = testUtils.createViewports(renderingEngine, {
  viewportId,
  viewportType: ViewportType.STACK,
  width: 512,
  height: 128,
});

Image Id and Volume Id

🔄 Before:

const imageId1 = 'fakeImageLoader:imageURI_64_64_10_5_1_1_0';

🆕 After:

const imageInfo1 = {
  loader: 'fakeImageLoader',
  name: 'imageURI',
  rows: 64,
  columns: 64,
  barStart: 32,
  barWidth: 5,
  xSpacing: 1,
  ySpacing: 1,
  sliceIndex: 0,
};

const imageId1 = testUtils.encodeImageIdInfo(imageInfo1);

For volumeId:

const volumeId = testUtils.encodeVolumeIdInfo({
  loader: 'fakeVolumeLoader',
  name: 'volumeURI',
  rows: 100,
  columns: 100,
  slices: 4,
  xSpacing: 1,
  ySpacing: 1,
  zSpacing: 1,
});

Note

These changes represent significant updates to the Cornerstone3D library. Make sure to update your codebase accordingly and test thoroughly after migration.

@sedghi sedghi changed the title update Cornerstone 2.0 Jul 22, 2024
Copy link

netlify bot commented Jul 22, 2024

Deploy Preview for cornerstone-3d-docs failed. Why did it fail? →

Name Link
🔨 Latest commit 5addf8e
🔍 Latest deploy log https://app.netlify.com/sites/cornerstone-3d-docs/deploys/671bf33ff68fed0008e67607

);
await viewport.setStack([imageId], 0);
const viewport = renderingEngine.getViewport(viewportId);
await (viewport as Types.IStackViewport).setStack([imageId], 0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you leave this as a generic viewport and call setDataIds instead? I think that would be more consistent across viewport types.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want the migration to be as minimal as possible. I understand it will be consistent, but perhaps this could be implemented in Cornerstone3D 3.0?

@wayfarer3130
Copy link
Collaborator

For the image volume, the access to that data is pretty low level, assuming that the internal organization of the pixel data is exactly a typed array and that there may or may not be slices associated with it. That causes us memory issues as we try to allocate larger buffers, and also forces us to share the image slice data in order to not need to copy the data all the time. If we could start having a better interface on top of the ImageVolume and other image data objects to hide some of the internal details, it would eventually allow us to use a single representation consistently. The VoxelManager is designed to be an abstraction on top of different types of views of both single and multiple images. Given that we don't get new versions of CS3D very often, starting to use the VoxelManager more consistently and perhaps deprecating the use of the full data object would be worthwhile.

package.json Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants