Skip to content

Commit

Permalink
fix(colormap): Resolve bugs related to colormap reset and colorbar (#…
Browse files Browse the repository at this point in the history
…1225)

* Refactor eventTarget.ts, loadImage.ts, BaseStreamingImageVolume.ts, and Synchronizer.ts

* Fix duplicate code in web-worker registration

* Add debounced event listener to CornerstoneEventTarget

* Refactor init.ts to improve isIOS() function and handle norm16 texture support

* Refactor canRenderFloatTextures() function in init.ts to handle iOS devices

* Fix issue with workerProperties not being updated correctly in CentralizedWorkerManager

* Refactor code to handle colormaps in various viewport tools

* Refactor code to handle colormaps in various viewport tools

* Fix blend mode setter in VolumeViewport

* Update @kitware/vtk.js dependency to version 30.4.1

* Refactor code to handle colormaps in various viewport tools

* skip cpu tests
  • Loading branch information
sedghi committed Apr 29, 2024
1 parent cab8855 commit aaac143
Show file tree
Hide file tree
Showing 29 changed files with 292 additions and 192 deletions.
13 changes: 10 additions & 3 deletions common/reviews/api/core.api.md
Expand Up @@ -49,7 +49,7 @@ type ActorEntry = {
};

// @public (undocumented)
function actorIsA(actorEntry: Types.ActorEntry, actorType: actorTypes): boolean;
function actorIsA(actorEntry: Types.ActorEntry | Types.Actor, actorType: actorTypes): boolean;

// @public (undocumented)
type ActorSliceRange = {
Expand Down Expand Up @@ -146,6 +146,8 @@ export abstract class BaseVolumeViewport extends Viewport implements IVolumeView
// (undocumented)
getViewReference(viewRefSpecifier?: ViewReferenceSpecifier): ViewReference;
// (undocumented)
protected getVOIModifiedEventDetail(volumeId: string): VoiModifiedEventDetail;
// (undocumented)
protected getVolumeId(specifier: ViewReferenceSpecifier): string;
// (undocumented)
hasImageURI: (imageURI: string) => boolean;
Expand Down Expand Up @@ -287,7 +289,8 @@ declare namespace colormap {
export {
getColormap,
getColormapNames,
registerColormap
registerColormap,
findMatchingColormap
}
}

Expand All @@ -311,7 +314,7 @@ type ColormapPublic = {
type ColormapRegistration = {
ColorSpace: string;
Name: string;
RGBPoints: RGB[];
RGBPoints: RGB[] | number[];
};

// @public (undocumented)
Expand Down Expand Up @@ -929,6 +932,9 @@ declare namespace EventTypes {
}
}

// @public (undocumented)
function findMatchingColormap(rgbPoints: any, actor: any): ColormapPublic | null;

// @public (undocumented)
type FlipDirection = {
flipHorizontal?: boolean;
Expand Down Expand Up @@ -4037,6 +4043,7 @@ type VoiModifiedEventDetail = {
VOILUTFunction?: VOILUTFunctionType;
invert?: boolean;
invertStateChanged?: boolean;
colormap?: ColormapPublic;
};

// @public (undocumented)
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Expand Up @@ -30,7 +30,7 @@
"webpack:watch": "webpack --mode development --progress --watch --config ./.webpack/webpack.dev.js"
},
"dependencies": {
"@kitware/vtk.js": "30.3.3",
"@kitware/vtk.js": "30.4.1",
"comlink": "^4.4.1",
"detect-gpu": "^5.0.22",
"gl-matrix": "^3.4.3",
Expand Down
137 changes: 69 additions & 68 deletions packages/core/src/RenderingEngine/BaseVolumeViewport.ts
Expand Up @@ -51,7 +51,6 @@ import {
invertRgbTransferFunction,
triggerEvent,
colormap as colormapUtils,
isEqual,
} from '../utilities';
import { createVolumeActor } from './helpers';
import volumeNewImageEventDispatcher, {
Expand All @@ -61,8 +60,8 @@ import Viewport from './Viewport';
import type { vtkSlabCamera as vtkSlabCameraType } from './vtkClasses/vtkSlabCamera';
import vtkSlabCamera from './vtkClasses/vtkSlabCamera';
import transformWorldToIndex from '../utilities/transformWorldToIndex';
import { findMatchingColormap } from '../utilities/colormap';
import { getTransferFunctionNodes } from '../utilities/transferFunctionUtils';
import { getColormap, getColormapNames } from '../utilities/colormap';
/**
* Abstract base class for volume viewports. VolumeViewports are used to render
* 3D volumes from which various orientations can be viewed. Since VolumeViewports
Expand Down Expand Up @@ -357,24 +356,51 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
const cfun = this._getOrCreateColorTransferFunction(volumeIdToUse);
invertRgbTransferFunction(cfun);

const { voiRange, VOILUTFunction } = this.getProperties(volumeIdToUse);

this.viewportProperties.invert = inverted;

if (!suppressEvents) {
const eventDetail: VoiModifiedEventDetail = {
viewportId: this.id,
range: voiRange,
volumeId: volumeIdToUse,
VOILUTFunction: VOILUTFunction,
invert: inverted,
...this.getVOIModifiedEventDetail(volumeIdToUse),
invertStateChanged: true,
};

triggerEvent(this.element, Events.VOI_MODIFIED, eventDetail);
}
}

protected getVOIModifiedEventDetail(
volumeId: string
): VoiModifiedEventDetail {
const applicableVolumeActorInfo = this._getApplicableVolumeActor(volumeId);

if (!applicableVolumeActorInfo) {
throw new Error(`No actor found for the given volumeId: ${volumeId}`);
}

const volumeActor = applicableVolumeActorInfo.volumeActor;

const transferFunction = volumeActor
.getProperty()
.getRGBTransferFunction(0);

const range = transferFunction.getMappingRange();

const matchedColormap = this.getColormap(volumeId);
const { VOILUTFunction, invert } = this.getProperties(volumeId);

return {
viewportId: this.id,
range: {
lower: range[0],
upper: range[1],
},
volumeId: applicableVolumeActorInfo.volumeId,
VOILUTFunction: VOILUTFunction,
colormap: matchedColormap,
invert,
};
}

private _getOrCreateColorTransferFunction(
volumeId: string
): vtkColorTransferFunction {
Expand Down Expand Up @@ -468,22 +494,11 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
.getProperty()
.getRGBTransferFunction(0)
.setRange(lower, upper);

if (!this.initialTransferFunctionNodes) {
const transferFunction = volumeActor
.getProperty()
.getRGBTransferFunction(0);
this.initialTransferFunctionNodes =
getTransferFunctionNodes(transferFunction);
}
}

if (!suppressEvents) {
const eventDetail: VoiModifiedEventDetail = {
viewportId: this.id,
range: voiRange,
volumeId: volumeIdToUse,
VOILUTFunction: VOILUTFunction,
...this.getVOIModifiedEventDetail(volumeIdToUse),
};

triggerEvent(this.element, Events.VOI_MODIFIED, eventDetail);
Expand Down Expand Up @@ -850,7 +865,7 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
? voiRanges.find((range) => range.volumeId === volumeId)?.voiRange
: voiRanges[0]?.voiRange;

const volumeColormap = this.getColormap(applicableVolumeActorInfo);
const volumeColormap = this.getColormap(volumeId);

const colormap =
volumeId && volumeColormap ? volumeColormap : latestColormap;
Expand Down Expand Up @@ -880,61 +895,24 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
* @param applicableVolumeActorInfo - The volume actor information for the volume
* @returns colormap information for the volume if identified
*/
private getColormap = (applicableVolumeActorInfo) => {
private getColormap = (volumeId) => {
const applicableVolumeActorInfo = this._getApplicableVolumeActor(volumeId);

if (!applicableVolumeActorInfo) {
return;
}

const { volumeActor } = applicableVolumeActorInfo;
const cfun = volumeActor.getProperty().getRGBTransferFunction(0);
const { nodes } = cfun.getState();
const RGBPoints = nodes.reduce((acc, node) => {
acc.push(node.x, node.r, node.g, node.b);
return acc;
}, []);
const colormapsVTK = vtkColorMaps.rgbPresetNames.map((presetName) =>
vtkColorMaps.getPresetByName(presetName)
);
const colormapsCS3D = getColormapNames().map((colormapName) =>
getColormap(colormapName)
);
const colormaps = colormapsVTK.concat(colormapsCS3D);
const matchedColormap = colormaps.find((colormap) => {
const { RGBPoints: presetRGBPoints } = colormap;
if (presetRGBPoints.length !== RGBPoints.length) {
return false;
}

for (let i = 0; i < presetRGBPoints.length; i += 4) {
if (
!isEqual(
presetRGBPoints.slice(i + 1, i + 4),
RGBPoints.slice(i + 1, i + 4)
)
) {
return false;
}
}

return true;
});

if (!matchedColormap) {
return null;
}

const opacityPoints = volumeActor
.getProperty()
.getScalarOpacity(0)
.getDataPointer();

const opacity = [];
for (let i = 0; i < opacityPoints.length; i += 2) {
opacity.push({ value: opacityPoints[i], opacity: opacityPoints[i + 1] });
}

const colormap = {
name: matchedColormap.Name,
opacity: opacity,
};
const matchedColormap = findMatchingColormap(RGBPoints, volumeActor);

return colormap;
return matchedColormap;
};

/**
Expand Down Expand Up @@ -996,6 +974,8 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
this._setVolumeActors(volumeActors);
this.viewportStatus = ViewportStatus.PRE_RENDER;

this.initializeColorTransferFunction(volumeInputArray);

triggerEvent(this.element, Events.VOLUME_VIEWPORT_NEW_VOLUME, {
viewportId: this.id,
volumeActors,
Expand Down Expand Up @@ -1070,6 +1050,8 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {

this.addActors(volumeActors);

this.initializeColorTransferFunction(volumeInputArray);

if (immediate) {
// render
this.render();
Expand Down Expand Up @@ -1106,6 +1088,25 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
console.warn('Method "setOrientation" needs implementation');
}

/**
* Initializes the color transfer function nodes for a given volume.
*
* @param volumeInputArray - Array of volume inputs.
* @param getTransferFunctionNodes - Function to get the transfer function nodes.
* @returns void
*/
private initializeColorTransferFunction(volumeInputArray) {
const selectedVolumeId = volumeInputArray[0].volumeId;
const colorTransferFunction =
this._getOrCreateColorTransferFunction(selectedVolumeId);

if (!this.initialTransferFunctionNodes) {
this.initialTransferFunctionNodes = getTransferFunctionNodes(
colorTransferFunction
);
}
}

private _getApplicableVolumeActor(volumeId?: string) {
if (volumeId !== undefined && !this.getActor(volumeId)) {
return;
Expand Down
13 changes: 13 additions & 0 deletions packages/core/src/RenderingEngine/StackViewport.ts
Expand Up @@ -97,6 +97,7 @@ import {
import correctShift from './helpers/cpuFallback/rendering/correctShift';
import resetCamera from './helpers/cpuFallback/rendering/resetCamera';
import { Transform } from './helpers/cpuFallback/rendering/transform';
import { findMatchingColormap } from '../utilities/colormap';

const EPSILON = 1; // Slice Thickness

Expand Down Expand Up @@ -867,6 +868,18 @@ class StackViewport extends Viewport implements IStackViewport, IImagesLoader {
transferFunction,
this.initialTransferFunctionNodes
);

const nodes = getTransferFunctionNodes(transferFunction);

const RGBPoints = nodes.reduce((acc, node) => {
acc.push(node[0], node[1], node[2], node[3]);
return acc;
}, []);

const defaultActor = this.getDefaultActor();
const matchedColormap = findMatchingColormap(RGBPoints, defaultActor.actor);

this.setColormap(matchedColormap);
}

public resetToDefaultProperties(): void {
Expand Down
22 changes: 7 additions & 15 deletions packages/core/src/RenderingEngine/VolumeViewport.ts
Expand Up @@ -238,7 +238,7 @@ class VolumeViewport extends BaseVolumeViewport {

const mapper = actor.getMapper();
// @ts-ignore vtk incorrect typing
mapper.setBlendMode(blendMode);
mapper.setBlendMode?.(blendMode);
});

if (immediate) {
Expand Down Expand Up @@ -443,26 +443,18 @@ class VolumeViewport extends BaseVolumeViewport {
setDefaultVolumeVOI(volumeActor.actor as vtkVolume, imageVolume, false);

if (isImageActor(volumeActor)) {
const transferFunction = (volumeActor.actor as ImageActor)
.getProperty()
.getRGBTransferFunction(0);

setTransferFunctionNodes(
(volumeActor.actor as ImageActor)
.getProperty()
.getRGBTransferFunction(0),
transferFunction,
this.initialTransferFunctionNodes
);
}

const range = (volumeActor.actor as vtkVolume)
.getProperty()
.getRGBTransferFunction(0)
.getMappingRange();

const eventDetails = {
viewportId: volumeActor.uid,
range: {
lower: range[0],
upper: range[1],
},
volumeId: volumeActor.uid,
...super.getVOIModifiedEventDetail(volumeId),
};

const resetPan = true;
Expand Down
@@ -1,9 +1,11 @@
import macro from '@kitware/vtk.js/macros';
import vtkOpenGLRenderWindow from '@kitware/vtk.js/Rendering/OpenGL/RenderWindow';
import vtkStreamingOpenGLViewNodeFactory from './vtkStreamingOpenGLViewNodeFactory';
import vtkStreamingOpenGLViewNodeFactory, {
registerOverride,
} from './vtkStreamingOpenGLViewNodeFactory';

/**
* vtkStreamingOpenGLRenderWindow - A dervied class of the core vtkOpenGLRenderWindow class.
* vtkStreamingOpenGLRenderWindow - A derived class of the core vtkOpenGLRenderWindow class.
* The main purpose for this class extension is to add in our own node factory, so we can use
* our extended "streaming" classes for progressive texture loading.
*
Expand All @@ -27,9 +29,8 @@ export function extend(publicAPI, model, initialValues = {}) {
vtkOpenGLRenderWindow.extend(publicAPI, model, initialValues);

model.myFactory = vtkStreamingOpenGLViewNodeFactory.newInstance();
/* eslint-disable no-use-before-define */
model.myFactory.registerOverride('vtkRenderWindow', newInstance);
/* eslint-enable no-use-before-define */

registerOverride('vtkRenderWindow', newInstance);

// Object methods
vtkStreamingOpenGLRenderWindow(publicAPI, model);
Expand Down

0 comments on commit aaac143

Please sign in to comment.