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

feat(seg): Improve Labelmap Statistics, Interpolation, and Threshold Configuration #1820

Closed
wants to merge 35 commits into from
Closed
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
bbff42b
refactor(segmentation): Improve strategy configuration and handling f…
sedghi Feb 6, 2025
60b7b68
fix weridness
sedghi Feb 6, 2025
7a0f940
wip
sedghi Feb 6, 2025
9bb438e
Merge branch 'main' of github.com:cornerstonejs/cornerstone3D into fe…
sedghi Feb 6, 2025
eed9bb6
feat(segmentation): Add labelmap interpolation utility and related im…
sedghi Feb 7, 2025
6cee10e
wip
sedghi Feb 7, 2025
0fedb92
feat(volume): Add utility for calculating spacing between image IDs
sedghi Feb 7, 2025
ffb1a33
feat(cache): Add volume ID generation and image ID lookup utilities
sedghi Feb 7, 2025
3c6494a
refactor(segmentation): Improve segmentation volume retrieval and ID …
sedghi Feb 7, 2025
5a7a45c
fix labelmapstatistics for stack viewport and make it in webworker
sedghi Feb 7, 2025
351ea3e
interpolation with worker
sedghi Feb 10, 2025
1e31f70
publish events
sedghi Feb 10, 2025
f02f557
api
sedghi Feb 10, 2025
179dae6
update
sedghi Feb 10, 2025
58c8b84
fix suv peak stuff
sedghi Feb 10, 2025
c31c92a
wip
sedghi Feb 10, 2025
247441e
fix
sedghi Feb 10, 2025
2956942
fix
sedghi Feb 10, 2025
7a78c0a
feat: Implement peer import utility for dynamic module loading
sedghi Feb 11, 2025
2e93fb4
update
sedghi Feb 11, 2025
f07e2fa
fix: Peer import using split to load wasm
wayfarer3130 Feb 13, 2025
5d7e103
Merge branch 'main' of github.com:cornerstonejs/cornerstone3D into fe…
sedghi Feb 18, 2025
dfacfd6
Merge branch 'feat/new-seg-stuff' of github.com:cornerstonejs/corners…
sedghi Feb 18, 2025
a6da958
fix: Improve volume ID generation and voxel data handling
sedghi Feb 18, 2025
78986a7
fix
sedghi Feb 18, 2025
90d9fce
remove composition
sedghi Feb 18, 2025
4551509
fix dynamic import
sedghi Feb 18, 2025
78e1eb0
fix vite
sedghi Feb 18, 2025
ace0183
fix bundler issues
sedghi Feb 18, 2025
289cf2d
Merge branch 'main' of github.com:cornerstonejs/cornerstone3D into fe…
sedghi Feb 20, 2025
20a6d88
silent warning
sedghi Feb 20, 2025
880d3dc
works
sedghi Feb 20, 2025
93342b1
feat: Add configuration support for optional module imports
sedghi Feb 20, 2025
aa96ee4
fix
sedghi Feb 21, 2025
2db10c4
fix
sedghi Feb 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
},
"packages/adapters": {
"name": "@cornerstonejs/adapters",
"version": "2.19.9",
"version": "2.19.10",
"dependencies": {
"@babel/runtime-corejs2": "^7.17.8",
"buffer": "^6.0.3",
Expand All @@ -147,13 +147,13 @@
"ndarray": "^1.0.19",
},
"peerDependencies": {
"@cornerstonejs/core": "^2.19.9",
"@cornerstonejs/tools": "^2.19.9",
"@cornerstonejs/core": "^2.19.10",
"@cornerstonejs/tools": "^2.19.10",
},
},
"packages/ai": {
"name": "@cornerstonejs/ai",
"version": "2.19.9",
"version": "2.19.10",
"dependencies": {
"@babel/runtime-corejs2": "^7.17.8",
"buffer": "^6.0.3",
Expand All @@ -171,7 +171,7 @@
},
"packages/core": {
"name": "@cornerstonejs/core",
"version": "2.19.9",
"version": "2.19.10",
"dependencies": {
"@kitware/vtk.js": "32.9.0",
"comlink": "^4.4.1",
Expand All @@ -180,7 +180,7 @@
},
"packages/dicomImageLoader": {
"name": "@cornerstonejs/dicom-image-loader",
"version": "2.19.9",
"version": "2.19.10",
"dependencies": {
"@cornerstonejs/codec-charls": "^1.2.3",
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
Expand All @@ -192,19 +192,19 @@
"uuid": "^9.0.0",
},
"peerDependencies": {
"@cornerstonejs/core": "^2.19.9",
"@cornerstonejs/core": "^2.19.10",
"dicom-parser": "^1.8.9",
},
},
"packages/docs": {
"name": "docs",
"version": "2.1.10",
"dependencies": {
"@cornerstonejs/adapters": "^2.19.9",
"@cornerstonejs/core": "^2.19.9",
"@cornerstonejs/dicom-image-loader": "^2.19.9",
"@cornerstonejs/nifti-volume-loader": "^2.19.9",
"@cornerstonejs/tools": "^2.19.9",
"@cornerstonejs/adapters": "^2.19.10",
"@cornerstonejs/core": "^2.19.10",
"@cornerstonejs/dicom-image-loader": "^2.19.10",
"@cornerstonejs/nifti-volume-loader": "^2.19.10",
"@cornerstonejs/tools": "^2.19.10",
"@docusaurus/core": "3.6.3",
"@docusaurus/faster": "3.6.3",
"@docusaurus/module-type-aliases": "3.6.3",
Expand Down Expand Up @@ -242,17 +242,17 @@
},
"packages/nifti-volume-loader": {
"name": "@cornerstonejs/nifti-volume-loader",
"version": "2.19.9",
"version": "2.19.10",
"dependencies": {
"nifti-reader-js": "^0.6.8",
},
"peerDependencies": {
"@cornerstonejs/core": "^2.19.9",
"@cornerstonejs/core": "^2.19.10",
},
},
"packages/tools": {
"name": "@cornerstonejs/tools",
"version": "2.19.9",
"version": "2.19.10",
"dependencies": {
"@types/offscreencanvas": "2019.7.3",
"comlink": "^4.4.1",
Expand All @@ -262,7 +262,7 @@
"canvas": "^2.11.2",
},
"peerDependencies": {
"@cornerstonejs/core": "^2.19.9",
"@cornerstonejs/core": "^2.19.10",
"@kitware/vtk.js": "32.9.0",
"@types/d3-array": "^3.0.4",
"@types/d3-interpolate": "^3.0.1",
Expand Down
6 changes: 5 additions & 1 deletion common/reviews/api/core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,9 @@ type BoundsLPS = [Point3, Point3, Point3];
// @public (undocumented)
export const cache: Cache_2;

// @public (undocumented)
function calculateSpacingBetweenImageIds(imageIds: string[]): number;

// @public (undocumented)
function calculateViewportsSpatialRegistration(viewport1: StackViewport | IVolumeViewport, viewport2: StackViewport | IVolumeViewport): void;

Expand Down Expand Up @@ -4047,7 +4050,8 @@ declare namespace utilities {
clip,
transformWorldToIndexContinuous,
createSubVolume,
getVolumeDirectionVectors
getVolumeDirectionVectors,
calculateSpacingBetweenImageIds
}
}
export { utilities }
Expand Down
45 changes: 41 additions & 4 deletions common/reviews/api/tools.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -757,9 +757,10 @@ class BasicStatsCalculator_2 extends Calculator {
unit: string;
}) => NamedStatistics;
// (undocumented)
static statsCallback: ({ value: newValue, pointLPS }: {
static statsCallback: ({ value: newValue, pointLPS, pointIJK, }: {
value: any;
pointLPS?: any;
pointIJK?: any;
}) => void;
// (undocumented)
static statsInit(options: {
Expand Down Expand Up @@ -1004,6 +1005,10 @@ enum ChangeTypes {

// @public (undocumented)
enum ChangeTypes_2 {
// (undocumented)
COMPUTE_STATISTICS = "Computing Statistics",
// (undocumented)
INTERPOLATE_LABELMAP = "Interpolating Labelmap",
// (undocumented)
POLYSEG_CONTOUR_TO_LABELMAP = "Converting Contour to Labelmap",
// (undocumented)
Expand Down Expand Up @@ -2819,12 +2824,18 @@ function getNormal2(polyline: Types_2.Point2[]): Types_2.Point3;
// @public (undocumented)
function getNormal3(polyline: Types_2.Point3[]): Types_2.Point3;

// @public (undocumented)
function getOrCreateSegmentationVolume(segmentationId: any): any;

// @public (undocumented)
function getOrientationStringLPS(vector: Types_2.Point3): string;

// @public (undocumented)
function getPixelValueUnits(modality: string, imageId: string, options: pixelUnitsOptions): string;

// @public (undocumented)
function getPixelValueUnitsImageId(imageId: string, options: pixelUnitsOptions): string;

// @public (undocumented)
function getPoint(points: any, idx: any): Types_2.Point3;

Expand Down Expand Up @@ -2903,6 +2914,13 @@ function getStackSegmentationImageIdsForViewport(viewportId: string, segmentatio
// @public (undocumented)
function getState(annotation?: Annotation): AnnotationStyleStates;

// @public (undocumented)
function getStatistics({ segmentationId, segmentIndices, viewportId, }: {
segmentationId: string;
segmentIndices: number[] | number;
viewportId: string;
}): Promise<any>;

// @public (undocumented)
function getStyle<T extends SegmentationRepresentations>(specifier: SpecifierWithType<T>): StyleForType<T>;

Expand Down Expand Up @@ -3154,6 +3172,15 @@ type InteractionTypes = 'Mouse' | 'Touch';
// @public (undocumented)
function internalAddRepresentationData({ segmentationId, type, data, }: AddRepresentationData): void;

// @public (undocumented)
function interpolateLabelmap({ segmentationId, segmentIndex, configuration, }: {
segmentationId: string;
segmentIndex: number;
configuration?: MorphologicalContourInterpolationOptions & {
preview: boolean;
};
}): Promise<void>;

// @public (undocumented)
type InterpolationROIAnnotation = ContourAnnotation & ContourSegmentationAnnotationData & {
metadata: {
Expand Down Expand Up @@ -3512,12 +3539,11 @@ export class LabelmapBaseTool extends BaseTool {
segmentColor: Types_2.Color;
};
// (undocumented)
protected getEditData({ viewport, representationData, segmentsLocked, segmentationId, volumeOperation, }: {
protected getEditData({ viewport, representationData, segmentsLocked, segmentationId, }: {
viewport: any;
representationData: any;
segmentsLocked: any;
segmentationId: any;
volumeOperation?: boolean;
}): EditDataReturnType;
// (undocumented)
protected getOperationData(element?: any): ModifiedLabelmapToolOperationData;
Expand Down Expand Up @@ -3574,6 +3600,7 @@ type LabelmapToolOperationData = {
viewPlaneNormal: number[];
viewUp: number[];
strategySpecificConfiguration: any;
activeStrategy: string;
points: Types_2.Point3[];
voxelManager: any;
override: {
Expand Down Expand Up @@ -5281,7 +5308,10 @@ declare namespace segmentation_2 {
getBrushToolInstances,
growCut,
LabelmapMemo,
IslandRemoval
IslandRemoval,
interpolateLabelmap,
getOrCreateSegmentationVolume,
getStatistics
}
}

Expand Down Expand Up @@ -5889,6 +5919,8 @@ type Statistics = {
label?: string;
value: number | number[];
unit: null | string;
pointIJK?: Types_2.Point3;
pointLPS?: Types_2.Point3;
};

// @public (undocumented)
Expand Down Expand Up @@ -5931,6 +5963,10 @@ enum StrategyCallbacks {
// (undocumented)
CreateIsInThreshold = "createIsInThreshold",
// (undocumented)
EnsureImageVolumeFor3DManipulation = "ensureImageVolumeFor3DManipulation",
// (undocumented)
EnsureSegmentationVolumeFor3DManipulation = "ensureSegmentationVolumeFor3DManipulation",
// (undocumented)
Fill = "fill",
// (undocumented)
GetStatistics = "getStatistics",
Expand Down Expand Up @@ -6711,6 +6747,7 @@ declare namespace utilities {
getCalibratedProbeUnitsAndValue,
getCalibratedAspect,
getPixelValueUnits,
getPixelValueUnitsImageId,
segmentation_2 as segmentation,
contours,
triggerAnnotationRenderForViewportIds,
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -184,5 +184,6 @@
"not ie < 11",
"not op_mini all"
],
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
"dependencies": {}
}
4 changes: 1 addition & 3 deletions packages/ai/src/ONNXSegmentationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ export default class ONNXSegmentationController {
this.currentImage = null;
this.viewport = viewport;

const brushInstance = new LabelmapBaseTool(
this.tool = new LabelmapBaseTool(
{},
{
configuration: {
Expand All @@ -352,8 +352,6 @@ export default class ONNXSegmentationController {
}
);

this.tool = brushInstance;

desiredImage.imageId =
viewport.getCurrentImageId?.() || viewport.getViewReferenceId();
if (desiredImage.imageId.startsWith('volumeId:')) {
Expand Down
47 changes: 47 additions & 0 deletions packages/core/src/cache/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,45 @@ class Cache {
private readonly _imageCache = new Map<string, ICachedImage>();
// used to store volume data (3d)
private readonly _volumeCache = new Map<string, ICachedVolume>();
// used to store the reverse lookup from imageIds to volumeId
private readonly _imageIdsToVolumeIdCache = new Map<string, string>();
// Todo: contour for now, but will be used for surface, etc.
private readonly _geometryCache = new Map<string, ICachedGeometry>();

private _imageCacheSize = 0;
private _maxCacheSize = 3 * ONE_GB;
private _geometryCacheSize = 0;

/**
* Generates a deterministic volume ID from a list of image IDs
* @param imageIds - Array of image IDs
* @returns A deterministic volume ID
*/
public generateVolumeId(imageIds: string[]): string {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I love the idea, however I wonder if the hash length is sufficiently unique that we won't ever generate two hashes containing the same hash code? Would sha1 to 8 digits be better? That is natively encoded and is reasonably fast.
async function sha1(str) {
const enc = new TextEncoder();
const hash = await crypto.subtle.digest('SHA-1', enc.encode(str));
return Array.from(new Uint8Array(hash))
.map(v => v.toString(16).padStart(2, '0'))
.join('');
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, does it need to be order invariant? Should the entries be sorted first?

Copy link
Member Author

Choose a reason for hiding this comment

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

Seems like crypto apis rely on secure context, so i guess we can't

const imageURIs = imageIds.map(imageIdToURI).sort();

let combinedHash = 0x811c9dc5;
for (const id of imageURIs) {
const idHash = this._fnv1aHash(id);
for (let i = 0; i < idHash.length; i++) {
combinedHash ^= idHash.charCodeAt(i);
combinedHash +=
(combinedHash << 1) +
(combinedHash << 4) +
(combinedHash << 7) +
(combinedHash << 8) +
(combinedHash << 24);
}
}
return `volume-${(combinedHash >>> 0).toString(36)}`;
}

public getImageIdsForVolumeId(volumeId: string): string[] {
return Array.from(this._imageIdsToVolumeIdCache.entries())
.filter(([_, id]) => id === volumeId)
.map(([key]) => key);
}

/**
* Set the maximum cache Size
*
Expand Down Expand Up @@ -1279,6 +1311,21 @@ class Cache {

return cachedGeometry.geometryLoadObject;
};

/**
* Helper function to generate a hash for a string using FNV-1a algorithm
* @param str - string to hash
* @returns the hashed string
*/
private _fnv1aHash(str: string): string {
let hash = 0x811c9dc5;
for (let i = 0; i < str.length; i++) {
hash ^= str.charCodeAt(i);
hash +=
(hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
return (hash >>> 0).toString(36);
}
}

/**
Expand Down
15 changes: 14 additions & 1 deletion packages/core/src/utilities/VoxelManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,20 @@ export default class VoxelManager<T> {
const sliceData = new SliceDataConstructor(sliceSize);
// @ts-ignore
sliceData.set(scalarData.subarray(sliceStart, sliceEnd));
imageVoxelManager.scalarData = sliceData;

// Instead of directly assigning scalarData, use TypedArray's set method
// previously here we were using imageVoxelManager.scalarData = sliceData
// which had some weird side effects
if (imageVoxelManager.scalarData) {
imageVoxelManager.scalarData.set(sliceData);
// Ensure the voxel manager knows about the changes
imageVoxelManager.modifiedSlices.add(sliceIndex);
} else {
// Fallback to individual updates if scalarData is not directly accessible
for (let i = 0; i < sliceSize; i++) {
imageVoxelManager.setAtIndex(i, sliceData[i]);
}
}

// Update min/max values for this slice
for (let i = 0; i < sliceData.length; i++) {
Expand Down
Loading
Loading