-
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 15 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,196 @@ | ||
| import cache from '../cache/cache'; | ||
| import StreamingImageVolume from '../cache/classes/StreamingImageVolume'; | ||
| import { RequestType } from '../enums'; | ||
| import imageLoadPoolManager from '../requestPool/imageLoadPoolManager'; | ||
| import type { IRetrieveConfiguration } from '../types'; | ||
| import { generateVolumePropsFromImageIds } from '../utilities/generateVolumePropsFromImageIds'; | ||
| import { loadImage } from './imageLoader'; | ||
| import decimate from '../utilities/decimate'; | ||
| import decimateImagePixels from '../utilities/decimateImagePixels'; | ||
|
|
||
| interface IVolumeLoader { | ||
| promise: Promise<StreamingImageVolume>; | ||
| cancel: () => void; | ||
| decache: () => void; | ||
| } | ||
|
|
||
| /** | ||
| * It handles loading of a image by streaming in its imageIds. It will be the | ||
| * volume loader if the schema for the volumeID is `decimateImageVolume`. | ||
| * This function returns a promise that resolves to the StreamingImageVolume instance. | ||
| * | ||
| * | ||
| * @param volumeId - The ID of the volume | ||
| * @param options - options for loading, imageIds | ||
| * @returns a promise that resolves to a StreamingImageVolume | ||
| */ | ||
| export function decimateVolumeLoader( | ||
| volumeId: string, | ||
| options: { | ||
| imageIds: string[]; | ||
| progressiveRendering?: boolean | IRetrieveConfiguration; | ||
| kDecimation?: number; | ||
| iDecimation?: number; | ||
| } | ||
| ): IVolumeLoader { | ||
| if (!options || !options.imageIds || !options.imageIds.length) { | ||
| throw new Error( | ||
| 'ImageIds must be provided to create a streaming image volume' | ||
| ); | ||
| } | ||
|
|
||
| const inPlaneDecimation = | ||
| options.iDecimation && options.iDecimation > 1 ? options.iDecimation : 1; | ||
| const kAxisDecimation = | ||
| options.kDecimation && options.kDecimation > 1 ? options.kDecimation : 1; | ||
|
|
||
| const originalImageIds = options.imageIds.slice(); | ||
| const decimatedResult = decimate(originalImageIds, kAxisDecimation); | ||
|
|
||
| const decimatedImageIds = | ||
| Array.isArray(decimatedResult) && | ||
| decimatedResult.length && | ||
| typeof decimatedResult[0] === 'number' | ||
| ? decimatedResult.map((idx) => originalImageIds[idx]) | ||
| : decimatedResult; | ||
|
|
||
| options.imageIds = decimatedImageIds as string[]; | ||
|
|
||
| async function getStreamingImageVolume() { | ||
| /** | ||
| * Check if we are using the `wadouri:` scheme, and if so, preload first, | ||
| * middle, and last image metadata as these are the images the current | ||
| * streaming image loader may explicitly request metadata from. The last image | ||
| * metadata would only be specifically requested if the imageId array order is | ||
| * reversed in the `sortImageIdsAndGetSpacing.ts` file. | ||
| */ | ||
| if (options.imageIds[0].split(':')[0] === 'wadouri') { | ||
| const [middleImageIndex, lastImageIndex] = [ | ||
| Math.floor(options.imageIds.length / 2), | ||
| options.imageIds.length - 1, | ||
| ]; | ||
| const indexesToPrefetch = [0, middleImageIndex, lastImageIndex]; | ||
| await Promise.all( | ||
| indexesToPrefetch.map((index) => { | ||
| // check if image is cached | ||
| if (cache.isLoaded(options.imageIds[index])) { | ||
| return Promise.resolve(true); | ||
| } | ||
| return new Promise((resolve, reject) => { | ||
| const imageId = options.imageIds[index]; | ||
| imageLoadPoolManager.addRequest( | ||
| async () => { | ||
| loadImage(imageId) | ||
| .then(() => { | ||
| console.log(`Prefetched imageId: ${imageId}`); | ||
| resolve(true); | ||
| }) | ||
| .catch((err) => { | ||
| reject(err); | ||
| }); | ||
| }, | ||
| RequestType.Prefetch, | ||
| { volumeId }, | ||
| 1 // priority | ||
| ); | ||
| }); | ||
| }) | ||
| ).catch(console.error); | ||
| } | ||
|
|
||
| const volumeProps = generateVolumePropsFromImageIds( | ||
| options.imageIds, | ||
| volumeId | ||
| ); | ||
|
|
||
| let { | ||
| dimensions, | ||
| spacing, | ||
| origin, | ||
| direction, | ||
| metadata, | ||
| imageIds, | ||
| dataType, | ||
| numberOfComponents, | ||
| } = volumeProps; | ||
|
|
||
| // Start from current props and apply decimations independently | ||
| let newDimensions = [...dimensions] as typeof dimensions; | ||
| let newSpacing = [...spacing] as typeof spacing; | ||
|
|
||
| // Apply in‑plane decimation (columns = x = index 0, rows = y = index 1) | ||
| if (inPlaneDecimation > 1) { | ||
| newDimensions[0] = Math.ceil(newDimensions[0] / inPlaneDecimation); | ||
| newDimensions[1] = Math.ceil(newDimensions[1] / inPlaneDecimation); | ||
| newSpacing[0] = newSpacing[0] * inPlaneDecimation; // column spacing (x) | ||
| newSpacing[1] = newSpacing[1] * inPlaneDecimation; // row spacing (y) | ||
|
|
||
| // DICOM: Rows = Y, Columns = X | ||
| metadata.Rows = newDimensions[1]; | ||
| metadata.Columns = newDimensions[0]; | ||
| // DICOM PixelSpacing = [row, column] = [y, x] | ||
| metadata.PixelSpacing = [newSpacing[1], newSpacing[0]]; | ||
| } | ||
|
|
||
| // Do NOT scale Z spacing here. We decimated imageIds before | ||
| // generating volume props, so sortImageIdsAndGetSpacing already | ||
| // computed the effective z-spacing between the kept frames. | ||
|
|
||
| // Commit any updates | ||
| dimensions = newDimensions; | ||
| spacing = newSpacing; | ||
| const streamingImageVolume = new StreamingImageVolume( | ||
| // ImageVolume properties | ||
| { | ||
| volumeId, | ||
| metadata, | ||
| dimensions, | ||
| spacing, | ||
| origin, | ||
| direction, | ||
| imageIds, | ||
| dataType, | ||
| numberOfComponents, | ||
| }, | ||
| // Streaming properties | ||
| { | ||
| imageIds, | ||
| loadStatus: { | ||
| loaded: false, | ||
| loading: false, | ||
| cancelled: false, | ||
| cachedFrames: [], | ||
| callbacks: [], | ||
| }, | ||
| } | ||
| ); | ||
| streamingImageVolume.setImagePostProcess( | ||
| (image) => | ||
| decimateImagePixels( | ||
| image as unknown as import('../types').IImage, | ||
| inPlaneDecimation | ||
| ) as unknown as import('../types').PixelDataTypedArray | ||
| ); | ||
| console.debug(streamingImageVolume); | ||
| return streamingImageVolume; | ||
| } | ||
|
|
||
| const streamingImageVolumePromise = getStreamingImageVolume(); | ||
|
|
||
| return { | ||
| promise: streamingImageVolumePromise, | ||
| decache: () => { | ||
| streamingImageVolumePromise.then((streamingImageVolume) => { | ||
| streamingImageVolume.destroy(); | ||
| streamingImageVolume = null; | ||
| }); | ||
| }, | ||
| cancel: () => { | ||
| streamingImageVolumePromise.then((streamingImageVolume) => { | ||
| streamingImageVolume.cancelLoading(); | ||
| }); | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| export default decimateVolumeLoader; |
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,70 @@ | ||
| import type { IImage, PixelDataTypedArray } from '../types'; | ||
| export default function decimateImagePixels(image: IImage, factor: number) { | ||
mbellehumeur marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Trivial case: no decimation requested | ||
| if (!factor || factor <= 1) { | ||
| return image; | ||
| } | ||
|
|
||
| const rows = image.rows ?? image.height; | ||
| const cols = image.columns ?? image.width; | ||
| const newRows = Math.ceil(rows / factor); | ||
| const newCols = Math.ceil(cols / factor); | ||
|
|
||
| const pixelData = image.getPixelData(); | ||
| const numComponents = image.numberOfComponents || 1; | ||
| const OutArrayCtor = pixelData.constructor as unknown as new ( | ||
| length: number | ||
| ) => PixelDataTypedArray; | ||
| const out = new OutArrayCtor(newRows * newCols * numComponents); | ||
|
|
||
| let outIndex = 0; | ||
| for (let r = 0; r < newRows; r++) { | ||
| const inR = r * factor; | ||
| if (inR >= rows) break; | ||
mbellehumeur marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| for (let c = 0; c < newCols; c++) { | ||
| const inC = c * factor; | ||
| if (inC >= cols) break; | ||
| const src = (inR * cols + inC) * numComponents; | ||
| for (let k = 0; k < numComponents; k++) { | ||
| out[outIndex++] = pixelData[src + k]; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Spacing: prefer explicit row/column spacing; preserve Z | ||
| const s = (image as unknown as { spacing?: [number, number, number] }) | ||
| .spacing; | ||
| const newSpacing: [number, number, number] = [ | ||
| (image.columnPixelSpacing ?? s?.[0] ?? 1) * factor, | ||
| (image.rowPixelSpacing ?? s?.[1] ?? 1) * factor, | ||
| s?.[2] ?? image.sliceThickness ?? 1, | ||
| ]; | ||
| const colSpacing = newSpacing[0]; | ||
| const rowSpacing = newSpacing[1]; | ||
|
|
||
| // Carry over imageFrame when present | ||
| let imageFrame = image.imageFrame | ||
| ? { | ||
| ...image.imageFrame, | ||
| rows: newRows, | ||
| columns: newCols, | ||
| pixelData: out, | ||
| pixelDataLength: out.length, | ||
| } | ||
| : undefined; | ||
| // Note: some loaders attach a non-standard imageInfo; skip updating to avoid type casting. | ||
|
|
||
| return { | ||
| ...image, | ||
| rows: newRows, | ||
| columns: newCols, | ||
| width: newCols, | ||
| height: newRows, | ||
| rowPixelSpacing: rowSpacing, | ||
| columnPixelSpacing: colSpacing, | ||
| spacing: newSpacing, | ||
| sizeInBytes: out.byteLength, | ||
| getPixelData: () => imageFrame?.pixelData ?? out, | ||
| imageFrame, | ||
| }; | ||
| } | ||
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.