-
Notifications
You must be signed in to change notification settings - Fork 453
feat(decimatedVolumeLoader): optional downsizing and downsampling of volumes. #2340
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
Merged
wayfarer3130
merged 82 commits into
cornerstonejs:main
from
mbellehumeur:feat/dynamic-loading
Dec 19, 2025
+1,096
−9
Merged
Changes from 75 commits
Commits
Show all changes
82 commits
Select commit
Hold shift + click to select a range
efac35c
Initialize dynamic loading example
mbellehumeur ab63317
start
mbellehumeur 4b8e7dd
feat(volumeDynamicLoading): enhance dynamic loading with configurable…
mbellehumeur 9089c4f
feat(loaders): add decimateVolumeLoader to loaders and exports
mbellehumeur ec1195f
feat(loaders): integrate decimateVolumeLoader with volumeLoader and e…
mbellehumeur b1d35e2
feat(loaders): enhance decimateVolumeLoader with in-plane decimation …
mbellehumeur 68f414a
feat(loaders): enhance decimateVolumeLoader to support image post-pro…
mbellehumeur 6bb8570
Added comment for possible DICOM value change
mbellehumeur fe6f98c
feat(loaders): adjust decimation calculations
mbellehumeur 4c2585e
feat(examples): Rename Volume Decimated Loading example
mbellehumeur d29112a
fix(cache): update image dimensions handling in BaseStreamingImageVol…
mbellehumeur d7c7aac
fix(volumeDecimatedLoading): update decimation values and streamline …
mbellehumeur 190f00a
fix(decimateImagePixels): improve decimation logic and handle trivial…
mbellehumeur ccf1036
revert unnecessary changes
mbellehumeur bfc3233
fix(volumeDecimatedLoading): enforce consistent GPU rendering setting…
mbellehumeur 7e28d55
fix(decimateImagePixels): PR comment - correct decimation calculation…
mbellehumeur b83ccb6
docs(decimateImagePixels): add detailed function documentation for im…
mbellehumeur cb4c661
refactor(decimateVolumeLoader): replace individual decimation paramet…
mbellehumeur cf24463
Rename to enhanced volume loading
mbellehumeur ae3e64f
Rename the volume loader.
mbellehumeur 4b22eaa
Rename example
mbellehumeur 38341b8
clean-up decimatePixels comments
mbellehumeur 3869f72
refactor(enhancedVolumeLoader): update ijkDecimation defaults and adj…
mbellehumeur c42cbdb
revert
mbellehumeur db43879
Add enhanced volume loading with timing display
mbellehumeur f220885
Add LengthTool to volumeEnhancedLoading example; update ijkDecimation…
mbellehumeur 8191f1b
Refactor logging and error handling in volume rendering components; r…
mbellehumeur c419ccd
stack caching off
mbellehumeur 31aad68
Implement image decimation checks in BaseStreamingImageVolume; remove…
mbellehumeur d6c676a
Put sampleDistanceMultiplier back
mbellehumeur 4d76bce
Refactor volumeEnhancedLoading example: update dropdown labels, add c…
mbellehumeur 5ad581e
Enhance VolumeViewport3D: add sampleDistanceMultiplier support and im…
mbellehumeur 1d65e49
Refactor image loading and decimation handling: remove localStorage c…
mbellehumeur b2365c6
Implement decimation parameter handling in image loaders: add functio…
mbellehumeur 6723e4e
Remove unnecessary console logs in Cache and ImageVolume classes for …
mbellehumeur 53ebb3a
Refactor imageLoader and applyPreset: streamline cached image handlin…
mbellehumeur f7f6ff9
Refactor BaseStreamingImageVolume and improve comments: remove redund…
mbellehumeur 989738b
Improve error handling and type definitions: add debug logging for im…
mbellehumeur cecd441
Add debug logging for requested decimation in enhancedVolumeLoader: s…
mbellehumeur 743299c
remove localStorage
mbellehumeur c7e412b
Refactor StreamingImageVolume and enhancedVolumeLoader: remove image …
mbellehumeur f660483
Fix first rendering of volume rendering tool after applying decimatio…
mbellehumeur 2925de9
Enhance enhancedVolumeLoader documentation: clarify decimation capabi…
mbellehumeur 2872560
Delete packages/core/src/utilities/decimateImagePixels.ts
mbellehumeur e02aeb1
Update enhancedVolumeLoader documentation: clarify decimation handlin…
mbellehumeur 099a8e6
Merge branch 'feat/dynamic-loading' of https://github.com/mbellehumeu…
mbellehumeur 81a80f9
Merge branch 'main' into feat/dynamic-loading
mbellehumeur b7432d2
Refactor image loading: Remove unused stripDecimationParam function a…
mbellehumeur 72ceb6e
Refactor BaseVolumeViewport and VolumeViewport3D: Clean up whitespace…
mbellehumeur 38722f0
Enhance BaseStreamingImageVolume: Restore warning for cancelled image…
mbellehumeur c3ee413
Refactor addOrUpdateSurfaceToElement: Change actor type casting from …
mbellehumeur 234993e
Refactor addVolumesAsIndependentComponents: Update actor type casting…
mbellehumeur 1008ea9
Refactor BaseVolumeViewport: Remove unused imports for setConfigurati…
mbellehumeur aa6ac57
Remove decimation from generateVolume and move to enhancedVolumeLoade…
mbellehumeur 8eb9d6c
update enhancedVolumeLaoder description
mbellehumeur a98d20d
Update comment as per review
mbellehumeur ef9e9f2
Move volume cropping tool changes to another PR
mbellehumeur 3d6adec
Export enhancedVolumeLoader from core index for improved accessibilit…
mbellehumeur 25b0879
pull origin main
mbellehumeur 6df4557
Update import path for inPlaneDecimationModifier to include file exte…
mbellehumeur e3649b0
Refactor import path for inPlaneDecimationModifier and remove unused …
mbellehumeur d383c03
Merge branch 'main' of https://github.com/mbellehumeur/cornerstone3D …
mbellehumeur dbe277c
refactor: update ijkDecimation type to use points.points3 for enhance…
mbellehumeur 16ae96d
fix: export points namespace for enhanced volume modifiers
mbellehumeur 9dd1766
fix: add points export to enhanced volume modifiers
mbellehumeur dce4c12
fix: correct type assertion for defaultActor in addVolumesAsIndepende…
mbellehumeur 29a21f1
fix: update type assertion for actor in addOrUpdateSurfaceToElement f…
mbellehumeur ee53f95
refactor: remove unused variables from volumeViewport3D example
mbellehumeur cdb49f5
refactor: enhance viewport property handling in volumeViewport3D example
mbellehumeur f47735b
docs: clarify decimation parameter usage in enhancedVolumeLoader
mbellehumeur 739b41b
refactor: remove unused parameter from applyPreset function
mbellehumeur 6129eb0
refactor: simplify actor type assertion in addVolumesAsIndependentCom…
mbellehumeur 1514cf6
refactor: update volume dimensions calculation to use imageIds length
mbellehumeur fdecd4b
refactor: add comment to clarify spacing calculation in generateVolum…
mbellehumeur b6ebe9a
refactor: replace enhancedVolumeLoader with decimatedVolumeLoader and…
mbellehumeur b16e431
Merge remote-tracking branch 'origin/main' into feat/dynamic-loading
wayfarer3130 8175c2f
Correct array access syntax for dimensions
mbellehumeur 467c6b1
Document inPlaneDecimationModifier functionality
mbellehumeur 4fd0e12
Enhance documentation for low resolution image loader
mbellehumeur f81bf44
Fix syntax for accessing dimensions in BaseStreamingImageVolume
mbellehumeur 5a23b80
Refactor decimation parameter handling in addDecimationToImageId func…
mbellehumeur 47ce69c
Merge remote-tracking branch 'origin/main' into feat/dynamic-loading
wayfarer3130 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,248 @@ | ||
| import StreamingImageVolume from '../cache/classes/StreamingImageVolume'; | ||
| import type { IRetrieveConfiguration } from '../types'; | ||
| import { generateVolumePropsFromImageIds } from '../utilities/generateVolumePropsFromImageIds'; | ||
| import decimate from '../utilities/decimate'; | ||
| import VoxelManager from '../utilities/VoxelManager'; | ||
| import { | ||
| applyDecimatedVolumeModifiers, | ||
| inPlaneDecimationModifier, | ||
| } from './decimatedVolumeModifiers'; | ||
| import type { | ||
| DecimatedVolumeLoaderOptions, | ||
| DecimatedVolumeModifierContext, | ||
| points, | ||
| } from './decimatedVolumeModifiers'; | ||
| interface IVolumeLoader { | ||
| promise: Promise<StreamingImageVolume>; | ||
| cancel: () => void; | ||
| decache: () => void; | ||
| } | ||
|
|
||
| /** | ||
| * Decimated volume loader that creates a StreamingImageVolume while delegating | ||
| * volume mutations to a plugin-style modifier chain. The loader still handles the | ||
| * responsibilities of preparing imageIds (including optional k-axis decimation), | ||
| * instantiating the streaming volume, and updating VTK/voxel-manager state, but it | ||
| * now relies on `decimatedVolumeModifiers` to encapsulate any transformations that | ||
| * are specific to projection parameters such as decimation. | ||
| * | ||
| * The loader currently wires a dedicated modifier set for: | ||
| * - **K-axis decimation handling**: Removes slices before metadata generation so | ||
| * that spacing reflects the decimated stack without manual scaling. | ||
| * - **In-plane decimation**: Implemented by `InPlaneDecimationModifier`, which | ||
| * updates dimensions, spacing, and DICOM metadata in a self-contained class. | ||
| * | ||
| * Additional modifiers can be introduced by implementing the shared | ||
| * `DecimatedVolumeModifier` interface. For example: | ||
| * ``` | ||
| * const modifiers = [ | ||
| * inPlaneDecimationModifier, | ||
| * orientationModifier, // rotates the direction matrix or redefines axis order | ||
| * partialRangeModifier, // limits the volume to a subset of frames / slices | ||
| * windowingModifier, // applies window/level or LUT adjustments at load time | ||
| * ]; | ||
| * ``` | ||
| * Modifiers are executed via `applyDecimatedVolumeModifiers`, so the loader still | ||
| * just selects the set to run and trusts the chain to mutate props appropriately. | ||
| * | ||
| * @param volumeId - The unique identifier for the volume | ||
| * @param options - Configuration options for volume loading | ||
| * @param options.imageIds - Array of DICOM imageIds to construct the volume from (required) | ||
| * @param options.progressiveRendering - Enable progressive rendering or provide custom | ||
| * retrieve configuration for streaming behavior | ||
| * @param options.ijkDecimation - Decimation factors for [I, J, K] axes where I/J affect | ||
| * in-plane resolution and K affects slice count. Defaults to [1, 1, 1] (no decimation). | ||
| * Example: [2, 2, 2] reduces dimensions by half in all axes. | ||
| * | ||
| * * | ||
| * @returns An object containing: | ||
| * - `promise`: Resolves to the created StreamingImageVolume instance | ||
| * - `cancel`: Function to cancel ongoing volume loading | ||
| * - `decache`: Function to destroy and remove the volume from cache | ||
| * | ||
| * @throws Error if imageIds are not provided or empty | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const volumeLoader = decimatedVolumeLoader('volumeId', { | ||
| * imageIds: ['dicomweb:...', 'dicomweb:...'], | ||
| * ijkDecimation: [2, 2, 2], // Half resolution in all dimensions | ||
| * progressiveRendering: true | ||
| * }); | ||
| * | ||
| * const volume = await volumeLoader.promise; | ||
| * // Later: volumeLoader.cancel() or volumeLoader.decache() | ||
| * ``` | ||
| */ | ||
| export function decimatedVolumeLoader( | ||
| volumeId: string, | ||
| options: { | ||
| imageIds: string[]; | ||
| progressiveRendering?: boolean | IRetrieveConfiguration; | ||
| ijkDecimation?: points.points3; | ||
| } | ||
| ): IVolumeLoader { | ||
| if (!options || !options.imageIds || !options.imageIds.length) { | ||
| throw new Error( | ||
| 'ImageIds must be provided to create a streaming image volume ' | ||
| ); | ||
| } | ||
|
|
||
| // Use provided decimation or default to [1, 1, 1] | ||
| // The hanging protocol service is responsible for providing decimation values | ||
| const [iDecimation = 1, jDecimation = iDecimation, kDecimation = 1] = | ||
| options.ijkDecimation ?? []; | ||
| const columnDecimation = Math.max(1, Math.floor(iDecimation)); | ||
| const rowDecimation = | ||
| jDecimation > 1 ? Math.max(1, Math.floor(jDecimation)) : columnDecimation; | ||
| const kAxisDecimation = Math.max(1, Math.floor(kDecimation)); | ||
| const hasInPlaneDecimation = columnDecimation > 1 || rowDecimation > 1; | ||
|
|
||
| const modifierOptions: DecimatedVolumeLoaderOptions = { | ||
| ijkDecimation: [ | ||
| columnDecimation, | ||
| rowDecimation, | ||
| kAxisDecimation, | ||
| ] as points.points3, | ||
| }; | ||
| const modifiers = [inPlaneDecimationModifier]; | ||
|
|
||
| // Function to add decimation parameter to imageId | ||
| function addDecimationToImageId(imageId: string, factor: number): string { | ||
| // Only add param if decimation is applied | ||
| if (factor === 1) { | ||
| return imageId; | ||
| } | ||
|
|
||
| // Check if imageId already has query params | ||
| const separator = imageId.includes('?') ? '&' : '?'; | ||
| return `${imageId}${separator}decimation=${factor}`; | ||
| } | ||
|
|
||
| // Check if k-decimation has already been applied at the displaySet level | ||
| const expectedDecimatedCount = Math.floor( | ||
| options.imageIds.length / kAxisDecimation | ||
| ); | ||
| const isAlreadyDecimated = | ||
| kAxisDecimation > 1 && | ||
| options.imageIds.length <= expectedDecimatedCount + 1; | ||
|
|
||
| if (kAxisDecimation > 1 && !isAlreadyDecimated) { | ||
| const decimatedResult = decimate(options.imageIds, kAxisDecimation); | ||
|
|
||
| const decimatedImageIds = | ||
| Array.isArray(decimatedResult) && | ||
| decimatedResult.length && | ||
| typeof decimatedResult[0] === 'number' | ||
| ? decimatedResult.map((idx) => options.imageIds[idx]) | ||
| : decimatedResult; | ||
|
|
||
| options.imageIds = decimatedImageIds as string[]; | ||
| } | ||
|
|
||
| // Apply in-plane decimation parameter to imageIds | ||
| // row and column decimation are always the same in this implementation so we use column | ||
| if (columnDecimation > 1) { | ||
mbellehumeur marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| options.imageIds = options.imageIds.map((imageId) => | ||
| addDecimationToImageId(imageId, columnDecimation) | ||
| ); | ||
| } | ||
|
|
||
| async function getStreamingImageVolume() { | ||
| const baseVolumeProps = generateVolumePropsFromImageIds( | ||
| options.imageIds, | ||
| volumeId | ||
| ); | ||
|
|
||
| const modifierContext: DecimatedVolumeModifierContext = { | ||
| volumeId, | ||
| imageIds: options.imageIds, | ||
| options: modifierOptions, | ||
| }; | ||
|
|
||
| const volumeProps = applyDecimatedVolumeModifiers( | ||
| baseVolumeProps, | ||
| modifiers, | ||
| modifierContext | ||
| ); | ||
|
|
||
| const { | ||
| dimensions, | ||
| spacing, | ||
| origin, | ||
| direction, | ||
| metadata, | ||
| imageIds, | ||
| dataType, | ||
| numberOfComponents, | ||
| } = volumeProps; | ||
|
|
||
| const streamingImageVolume = new StreamingImageVolume( | ||
| { | ||
| volumeId, | ||
| metadata, | ||
| dimensions, | ||
| spacing, | ||
| origin, | ||
| direction, | ||
| imageIds, | ||
| dataType, | ||
| numberOfComponents, | ||
| }, | ||
| { | ||
| imageIds, | ||
| loadStatus: { | ||
| loaded: false, | ||
| loading: false, | ||
| cancelled: false, | ||
| cachedFrames: [], | ||
| callbacks: [], | ||
| }, | ||
| } | ||
| ); | ||
|
|
||
| if (hasInPlaneDecimation) { | ||
| const vtkImageData = streamingImageVolume.imageData; | ||
| if (vtkImageData) { | ||
| vtkImageData.setDimensions(streamingImageVolume.dimensions); | ||
| vtkImageData.setSpacing(streamingImageVolume.spacing); | ||
| vtkImageData.modified(); | ||
| } | ||
|
|
||
| const newVoxelManager = VoxelManager.createImageVolumeVoxelManager({ | ||
| dimensions: streamingImageVolume.dimensions, | ||
| imageIds: streamingImageVolume.imageIds, | ||
| numberOfComponents: numberOfComponents, | ||
| }); | ||
|
|
||
| streamingImageVolume.voxelManager = newVoxelManager; | ||
|
|
||
| if (vtkImageData) { | ||
| vtkImageData.set({ | ||
| voxelManager: newVoxelManager, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| return streamingImageVolume; | ||
| } | ||
|
|
||
| const streamingImageVolumePromise = getStreamingImageVolume(); | ||
|
|
||
| return { | ||
| promise: streamingImageVolumePromise, | ||
| decache: () => { | ||
| streamingImageVolumePromise.then((streamingImageVolume) => { | ||
| streamingImageVolume.destroy(); | ||
| streamingImageVolume = null; | ||
| }); | ||
| }, | ||
| cancel: () => { | ||
| streamingImageVolumePromise.then((streamingImageVolume) => { | ||
| streamingImageVolume.cancelLoading(); | ||
| }); | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| export default decimatedVolumeLoader; | ||
19 changes: 19 additions & 0 deletions
19
packages/core/src/loaders/decimatedVolumeModifiers/applyDecimatedVolumeModifiers.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import type { ImageVolumeProps } from '../../types'; | ||
| import type { | ||
| DecimatedVolumeModifier, | ||
| DecimatedVolumeModifierContext, | ||
| } from './types'; | ||
|
|
||
| /** | ||
| * Runs every modifier sequentially and returns the final props. | ||
| */ | ||
| export function applyDecimatedVolumeModifiers( | ||
| baseProps: ImageVolumeProps, | ||
| modifiers: DecimatedVolumeModifier[], | ||
| context: DecimatedVolumeModifierContext | ||
| ): ImageVolumeProps { | ||
| return modifiers.reduce( | ||
| (currentProps, modifier) => modifier.apply(currentProps, context), | ||
mbellehumeur marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| baseProps | ||
| ); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.