Skip to content

Commit

Permalink
feat(labelmap): render properly on mip viewports (#1693)
Browse files Browse the repository at this point in the history
  • Loading branch information
sedghi authored Dec 11, 2024
1 parent 06611b5 commit 701f44c
Show file tree
Hide file tree
Showing 33 changed files with 1,678 additions and 440 deletions.
Binary file modified bun.lockb
Binary file not shown.
16 changes: 15 additions & 1 deletion common/reviews/api/core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import type vtkOpenGLTexture from '@kitware/vtk.js/Rendering/OpenGL/Texture';
import vtkPlane from '@kitware/vtk.js/Common/DataModel/Plane';
import type vtkRenderer from '@kitware/vtk.js/Rendering/Core/Renderer';
import type vtkVolume from '@kitware/vtk.js/Rendering/Core/Volume';
import type vtkVolumeMapper from '@kitware/vtk.js/Rendering/Core/VolumeMapper';
import vtkVolumeMapper from '@kitware/vtk.js/Rendering/Core/VolumeMapper';

// @public (undocumented)
interface AABB2 {
Expand Down Expand Up @@ -59,6 +59,13 @@ interface ActorEntry {
// (undocumented)
actor: Actor | VolumeActor | ImageActor | ICanvasActor;
// (undocumented)
blendMode?: BlendModes;
// (undocumented)
callbacks?: ({ volumeActor, volumeId, }: {
volumeActor: VolumeActor;
volumeId: string;
}) => void;
// (undocumented)
clippingFilter?: any;
// (undocumented)
referencedId?: string;
Expand Down Expand Up @@ -252,6 +259,8 @@ enum BlendModes {
// (undocumented)
COMPOSITE,
// (undocumented)
LABELMAP_EDGE_PROJECTION_BLEND,
// (undocumented)
MAXIMUM_INTENSITY_BLEND,
// (undocumented)
MINIMUM_INTENSITY_BLEND
Expand Down Expand Up @@ -439,6 +448,9 @@ enum ContourType {
OPEN_PLANAR = "OPEN_PLANAR"
}

// @public (undocumented)
export function convertMapperToNotSharedMapper(sharedMapper: vtkVolumeMapper): vtkVolumeMapper;

// @public (undocumented)
function convertStackToVolumeViewport({ viewport, options, }: {
viewport: StackViewport;
Expand Down Expand Up @@ -4778,6 +4790,8 @@ export class VolumeViewport extends BaseVolumeViewport {
// (undocumented)
addVolumes(volumeInputArray: IVolumeInput[], immediate?: boolean, suppressEvents?: boolean): Promise<void>;
// (undocumented)
getBlendMode(filterActorUIDs?: string[]): BlendModes;
// (undocumented)
getCurrentImageId: () => string | undefined;
// (undocumented)
getCurrentImageIdIndex: (volumeId?: string, useSlabThickness?: boolean) => number;
Expand Down
10 changes: 9 additions & 1 deletion common/reviews/api/tools.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1895,6 +1895,7 @@ const _default: {
getPointInLineOfSightWithCriteria: typeof getPointInLineOfSightWithCriteria;
isPlaneIntersectingAABB: (origin: any, normal: any, minX: any, minY: any, minZ: any, maxX: any, maxY: any, maxZ: any) => boolean;
filterAnnotationsWithinSamePlane: typeof filterAnnotationsWithinSamePlane;
getPointsInLineOfSight: typeof getPointsInLineOfSight;
};

// @public (undocumented)
Expand Down Expand Up @@ -2769,6 +2770,12 @@ function getPoint(points: any, idx: any): Types_2.Point3;
// @public (undocumented)
function getPointInLineOfSightWithCriteria(viewport: Types_2.IVolumeViewport, worldPos: Types_2.Point3, targetVolumeId: string, criteriaFunction: (intensity: number, point: Types_2.Point3) => Types_2.Point3, stepSize?: number): Types_2.Point3;

// @public (undocumented)
function getPointsInLineOfSight(viewport: Types_2.IVolumeViewport, worldPos: Types_2.Point3, { targetVolumeId, stepSize }: {
targetVolumeId: string;
stepSize: number;
}): Types_2.Point3[];

// @public (undocumented)
function getPolyDataPointIndexes(polyData: vtkPolyData): any[];

Expand Down Expand Up @@ -4140,7 +4147,8 @@ declare namespace planar_2 {
filterAnnotationsForDisplay,
getPointInLineOfSightWithCriteria,
isPlaneIntersectingAABB,
filterAnnotationsWithinSamePlane
filterAnnotationsWithinSamePlane,
getPointsInLineOfSight
}
}

Expand Down
3 changes: 2 additions & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"packages/tools",
"packages/adapters",
"packages/nifti-volume-loader",
"packages/dicomImageLoader"
"packages/dicomImageLoader",
"packages/ai"
],
"npmClient": "yarn"
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"@babel/runtime": "7.21.5",
"@babel/runtime-corejs3": "^7.15.4",
"@cornerstonejs/calculate-suv": "1.0.3",
"@microsoft/api-extractor": "^7.47.2",
"@microsoft/api-extractor": "7.48.0",
"@microsoft/tsdoc": "^0.15.0",
"@playwright/test": "^1.48.0",
"@rollup/plugin-babel": "^6.0.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/ai/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
"outDir": "./dist/esm",
"rootDir": "./src"
},
"include": ["./src/**/*"],
"include": ["./src/**/*"]
}
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@cornerstonejs/core",
"version": "2.9.0",
"description": "",
"description": "Cornerstone3D Core",
"module": "./dist/esm/index.js",
"types": "./dist/esm/index.d.ts",
"repository": "https://github.com/cornerstonejs/cornerstone3D",
Expand Down
2 changes: 0 additions & 2 deletions packages/core/src/RenderingEngine/BaseVolumeViewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -842,8 +842,6 @@ abstract class BaseVolumeViewport extends Viewport {

if (slabThickness !== undefined) {
this.setSlabThickness(slabThickness);
//We need to set the current slab thickness here since setSlabThickness is define in VolumeViewport
this.viewportProperties.slabThickness = slabThickness;
}
}

Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/RenderingEngine/CanvasActor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ export default class CanvasActor {
);
}

public setMapper(mapper: CanvasMapper) {
this.mapper = mapper;
}

public render(viewport: IViewport, context: CanvasRenderingContext2D): void {
if (!this.visibility) {
return;
Expand Down
37 changes: 35 additions & 2 deletions packages/core/src/RenderingEngine/VolumeViewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,14 +273,46 @@ class VolumeViewport extends BaseVolumeViewport {
this.resetCamera();
}

/**
* Gets the blend mode for the volume viewport. If filterActorUIDs is provided,
* it will return the blend mode for the first matching actor. Otherwise, it returns
* the blend mode of the first actor.
*
* @param filterActorUIDs - Optional array of actor UIDs to filter by
* @returns The blend mode of the matched actor
*/
public getBlendMode(filterActorUIDs?: string[]): BlendModes {
const actorEntries = this.getActors();
const actorForBlend =
filterActorUIDs?.length > 0
? actorEntries.find((actorEntry) =>
filterActorUIDs.includes(actorEntry.uid)
)
: actorEntries[0];

return (
actorForBlend?.blendMode ||
// @ts-ignore vtk incorrect typing
actorForBlend?.actor.getMapper().getBlendMode()
);
}

/**
* Sets the blend mode for actors in the volume viewport. Can optionally filter which
* actors to apply the blend mode to using filterActorUIDs.
*
* @param blendMode - The blend mode to set
* @param filterActorUIDs - Optional array of actor UIDs to filter which actors to update
* @param immediate - Whether to render the viewport immediately after setting the blend mode
*/
public setBlendMode(
blendMode: BlendModes,
filterActorUIDs = [],
immediate = false
): void {
let actorEntries = this.getActors();

if (filterActorUIDs && filterActorUIDs.length > 0) {
if (filterActorUIDs?.length > 0) {
actorEntries = actorEntries.filter((actorEntry: ActorEntry) => {
return filterActorUIDs.includes(actorEntry.uid);
});
Expand All @@ -292,6 +324,7 @@ class VolumeViewport extends BaseVolumeViewport {
const mapper = actor.getMapper();
// @ts-ignore vtk incorrect typing
mapper.setBlendMode?.(blendMode);
actorEntry.blendMode = blendMode;
});

if (immediate) {
Expand Down Expand Up @@ -407,7 +440,7 @@ class VolumeViewport extends BaseVolumeViewport {

let actorEntries = this.getActors();

if (filterActorUIDs && filterActorUIDs.length > 0) {
if (filterActorUIDs?.length > 0) {
actorEntries = actorEntries.filter((actorEntry) => {
return filterActorUIDs.includes(actorEntry.uid);
});
Expand Down
13 changes: 8 additions & 5 deletions packages/core/src/RenderingEngine/helpers/createVolumeActor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ interface createVolumeActorInterface {
}

/**
* Given a volumeId, it creates a vtk volume actor and returns it. If
* callback is provided, it will be called with the volume actor and the
* volumeId. If blendMode is provided, it will be set on the volume actor.
* Creates a volume actor based on the provided properties.
*
* @param props - createVolumeActorInterface
* @returns A promise that resolves to a VolumeActor.
* @param props - The properties for creating the volume actor.
* @param element - The HTMLDivElement where the volume actor will be rendered.
* @param viewportId - The ID of the viewport where the volume actor will be displayed.
* @param suppressEvents - Optional. Specifies whether to suppress triggering events. Default is false.
* @param useNativeDataType - Optional. Specifies whether to use the native data type. Default is false.
* @returns A promise that resolves to the created volume actor.
* @throws An error if the imageVolume with the specified ID does not exist.
*/
async function createVolumeActor(
props: createVolumeActorInterface,
Expand Down
33 changes: 32 additions & 1 deletion packages/core/src/RenderingEngine/helpers/createVolumeMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { vtkSharedVolumeMapper } from '../vtkClasses';
import { getConfiguration } from '../../init';
import type vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
import type vtkOpenGLTexture from '@kitware/vtk.js/Rendering/OpenGL/Texture';
import type vtkVolumeMapper from '@kitware/vtk.js/Rendering/Core/VolumeMapper';
import vtkVolumeMapper from '@kitware/vtk.js/Rendering/Core/VolumeMapper';
import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';

/**
* Given an imageData and a vtkOpenGLTexture, it creates a "shared" vtk volume mapper
* from which various volume actors can be created.
Expand Down Expand Up @@ -38,3 +40,32 @@ export default function createVolumeMapper(

return volumeMapper;
}

/**
* Converts a shared mapper to a non-shared mapper. Sometimes we need to detach
* a shared mapper and apply some changes to it, since otherwise, the changes
* will be applied to all the mappers that share the same data.
*
* @param sharedMapper - The shared mapper to convert.
* @returns The converted volume mapper.
*/
export function convertMapperToNotSharedMapper(sharedMapper: vtkVolumeMapper) {
const volumeMapper = vtkVolumeMapper.newInstance();
volumeMapper.setBlendMode(sharedMapper.getBlendMode());

const imageData = sharedMapper.getInputData();
const { voxelManager } = imageData.get('voxelManager');
const values = voxelManager.getCompleteScalarDataArray();

const scalarArray = vtkDataArray.newInstance({
name: `Pixels`,
values,
});

imageData.getPointData().setScalars(scalarArray);

volumeMapper.setInputData(imageData);
volumeMapper.setMaximumSamplesPerRay(sharedMapper.getMaximumSamplesPerRay());
volumeMapper.setSampleDistance(sharedMapper.getSampleDistance());
return volumeMapper;
}
4 changes: 3 additions & 1 deletion packages/core/src/enums/BlendModes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const { BlendMode } = vtkConstants;
* then it will not show any difference unless MAXIMUM_INTENSITY_BLEND is set on the viewport
* as the blend.
*/
enum BlendModes {
export enum BlendModes {
/** composite blending - suitable for compositing multiple images */
COMPOSITE = BlendMode.COMPOSITE_BLEND,
/** maximum intensity projection */
Expand All @@ -18,6 +18,8 @@ enum BlendModes {
MINIMUM_INTENSITY_BLEND = BlendMode.MINIMUM_INTENSITY_BLEND,
/** average intensity projection */
AVERAGE_INTENSITY_BLEND = BlendMode.AVERAGE_INTENSITY_BLEND,
/** MIP labelmap */
LABELMAP_EDGE_PROJECTION_BLEND = BlendMode.LABELMAP_EDGE_PROJECTION_BLEND,
}

export default BlendModes;
6 changes: 5 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import * as CONSTANTS from './constants';
import { Events } from './enums';
import RenderingEngine from './RenderingEngine';
import createVolumeActor from './RenderingEngine/helpers/createVolumeActor';
import createVolumeMapper from './RenderingEngine/helpers/createVolumeMapper';
import createVolumeMapper, {
convertMapperToNotSharedMapper,
} from './RenderingEngine/helpers/createVolumeMapper';
import getOrCreateCanvas from './RenderingEngine/helpers/getOrCreateCanvas';
import VolumeViewport from './RenderingEngine/VolumeViewport';
import VolumeViewport3D from './RenderingEngine/VolumeViewport3D';
Expand All @@ -13,6 +15,7 @@ import VideoViewport from './RenderingEngine/VideoViewport';
import WSIViewport from './RenderingEngine/WSIViewport';
import Viewport from './RenderingEngine/Viewport';
import eventTarget from './eventTarget';

import {
getRenderingEngine,
getRenderingEngines,
Expand Down Expand Up @@ -158,4 +161,5 @@ export {
cornerstoneStreamingDynamicImageVolumeLoader,
StreamingDynamicImageVolume,
StreamingImageVolume,
convertMapperToNotSharedMapper,
};
11 changes: 11 additions & 0 deletions packages/core/src/types/IActor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';
import type vtkImageSlice from '@kitware/vtk.js/Rendering/Core/ImageSlice';
import type vtkVolume from '@kitware/vtk.js/Rendering/Core/Volume';
import type CanvasActor from '../RenderingEngine/CanvasActor';
import type { BlendModes } from '../enums';

export type Actor = vtkActor;
export type VolumeActor = vtkVolume;
Expand All @@ -26,6 +27,16 @@ export interface ActorEntry {
/** clipping filter applied to actor surfaces*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
clippingFilter?: any;
/** blend mode */
blendMode?: BlendModes;
/** callbacks that run after actor creation */
callbacks?: ({
volumeActor,
volumeId,
}: {
volumeActor: VolumeActor;
volumeId: string;
}) => void;
/** */
[key: string]: unknown;
}
24 changes: 0 additions & 24 deletions packages/docs/docs/migration-guides/intro.md

This file was deleted.

2 changes: 1 addition & 1 deletion packages/docs/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ module.exports = {
],
},
{
to: '/docs/migration-guides/intro',
to: '/docs/migration-guides/2x/general',
label: '2.0 Migration Guides',
position: 'left',
className: 'new-badge',
Expand Down
12 changes: 6 additions & 6 deletions packages/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"serve": "docusaurus serve",
"start": "yarn run docs",
"start:api": "TYPEDOC_WATCH=true docusaurus start",
"dev": "yarn run docs",
"dev": "cd ../../ && yarn run build-all-examples && cd packages/docs && yarn run docs",
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
Expand All @@ -38,11 +38,11 @@
"@cornerstonejs/dicom-image-loader": "^2.9.0",
"@cornerstonejs/nifti-volume-loader": "^2.9.0",
"@cornerstonejs/tools": "^2.9.0",
"@docusaurus/core": "3.6.1",
"@docusaurus/faster": "3.6.1",
"@docusaurus/module-type-aliases": "3.6.1",
"@docusaurus/plugin-google-gtag": "3.6.1",
"@docusaurus/preset-classic": "3.6.1",
"@docusaurus/core": "3.6.3",
"@docusaurus/faster": "3.6.3",
"@docusaurus/module-type-aliases": "3.6.3",
"@docusaurus/plugin-google-gtag": "3.6.3",
"@docusaurus/preset-classic": "3.6.3",
"@kitware/vtk.js": "32.1.1",
"@mdx-js/react": "^3.0.1",
"@svgr/webpack": "^8.1.0",
Expand Down
Loading

0 comments on commit 701f44c

Please sign in to comment.