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

fix Colormap Opacity #2

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions common/reviews/api/core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ declare namespace colormap {

// @public (undocumented)
type ColormapPublic = {
name: string;
opacityMapping?: OpacityMapping[];
name?: string;
opacity?: OpacityMapping[] | number;
};

// @public (undocumented)
Expand Down
13 changes: 11 additions & 2 deletions common/reviews/api/streaming-image-volume-loader.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,17 @@ type CameraModifiedEventDetail = {

// @public (undocumented)
type ColormapPublic = {
name: string;
opacityMapping?: OpacityMapping[];
name?: string;
opacity?: OpacityMapping[] | number;
/** midpoint mapping between values to opacity if the colormap
* is getting used for fusion, this is an array of arrays which
* each array containing 2 values, the first value is the value
* to map to opacity and the second value is the opacity value.
* By default, the minimum value is mapped to 0 opacity and the
* maximum value is mapped to 1 opacity, but you can configure
* the points in the middle to be mapped to different opacities
* instead of a linear mapping from 0 to 1.
*/
};

// @public (undocumented)
Expand Down
13 changes: 11 additions & 2 deletions common/reviews/api/tools.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -842,8 +842,17 @@ type ColorLUT = Array<Color>;

// @public (undocumented)
type ColormapPublic = {
name: string;
opacityMapping?: OpacityMapping[];
name?: string;
opacity?: OpacityMapping[] | number;
/** midpoint mapping between values to opacity if the colormap
* is getting used for fusion, this is an array of arrays which
* each array containing 2 values, the first value is the value
* to map to opacity and the second value is the opacity value.
* By default, the minimum value is mapped to 0 opacity and the
* maximum value is mapped to 1 opacity, but you can configure
* the points in the middle to be mapped to different opacities
* instead of a linear mapping from 0 to 1.
*/
};

// @public (undocumented)
Expand Down
69 changes: 53 additions & 16 deletions packages/core/src/RenderingEngine/BaseVolumeViewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,9 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
mapper.setSampleDistance(1.0);

const cfun = vtkColorTransferFunction.newInstance();
let colormapObj = colormapUtils.getColormap(colormap);
let colormapObj = colormapUtils.getColormap(colormap.name);

const { name, opacityMapping } = colormap;
const { name } = colormap;

if (!colormapObj) {
colormapObj = vtkColorMaps.getPresetByName(name);
Expand All @@ -264,20 +264,54 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
cfun.applyColorMap(colormapObj);
cfun.setMappingRange(range[0], range[1]);
volumeActor.getProperty().setRGBTransferFunction(0, cfun);
}

const ofun = vtkPiecewiseFunction.newInstance();
ofun.addPoint(range[0], 0.0);
ofun.addPoint(range[1], 1.0);
volumeActor.getProperty().setScalarOpacity(0, ofun);

if (!opacityMapping) {
/**
* Sets the opacity for the volume with the given ID.
*
* @param colormap - An object containing opacity that can be a number or an array of OpacityMapping
* @param volumeId - The ID of the volume to set the opacity for.
*
* @returns void
*/
private setOpacity(colormap: ColormapPublic, volumeId: string) {
const applicableVolumeActorInfo = this._getApplicableVolumeActor(volumeId);
if (!applicableVolumeActorInfo) {
return;
}
const { volumeActor } = applicableVolumeActorInfo;
const vtkImageData = volumeActor.getMapper().getInputData();
const interval = vtkImageData.getSpacing()[2];
/*
Opacity of a volume is nonlinear. Specifically in volume rendering opacity is interpreted in terms of opacity per unit distance.
When you need to take an interval of a volume (say 3mm of a ray) and convert that to opacity you use a power function to accumulate the opacity over the interval.
For example if your opacity is 0.5/mm and the interval is 3mm long then the opacity is 1.0 - pow(1.0 - 0.5, 3) or 0.875
https://github.com/Kitware/vtk-js/pull/2093
*/
const convertOpacityToVTKOPacity = (opacity: number, interval: number) => {
return 1.0 - Math.pow(1.0 - opacity, interval);
};
const ofun = vtkPiecewiseFunction.newInstance();
if (typeof colormap.opacity === 'number') {
const range = volumeActor
.getProperty()
.getRGBTransferFunction(0)
.getRange();

// add custom opacity points
opacityMapping.forEach(({ opacity, value }) => {
ofun.addPoint(value, opacity);
});
ofun.addPoint(
range[0],
convertOpacityToVTKOPacity(colormap.opacity, interval)
);
ofun.addPoint(
range[1],
convertOpacityToVTKOPacity(colormap.opacity, interval)
);
} else {
colormap.opacity.forEach(({ opacity, value }) => {
ofun.addPoint(value, convertOpacityToVTKOPacity(opacity, interval));
});
}
volumeActor.getProperty().setScalarOpacity(0, ofun);
}

/**
Expand Down Expand Up @@ -435,9 +469,13 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
): void {
// Note: colormap should always be done first, since we can then
// modify the voiRange
if (colormap !== undefined) {

if (colormap?.name) {
this.setColormap(colormap, volumeId, suppressEvents);
}
if (colormap?.opacity != null) {
this.setOpacity(colormap, volumeId);
}

if (voiRange !== undefined) {
this.setVOI(voiRange, volumeId, suppressEvents);
Expand Down Expand Up @@ -500,8 +538,7 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
const [lower, upper] =
this.VOILUTFunction === 'SIGMOID'
? getVoiFromSigmoidRGBTransferFunction(cfun)
: // @ts-ignore
cfun.getRange();
: cfun.getRange();
return { volumeId, voiRange: { lower, upper } };
})
.filter(Boolean);
Expand Down Expand Up @@ -599,7 +636,6 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
`imageVolume with id: ${firstImageVolume.volumeId} does not exist`
);
}

const volumeActors = [];

await this._isValidVolumeInputArray(
Expand Down Expand Up @@ -800,6 +836,7 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
const volume = cache.getVolume(volumeId);

const vtkImageData = actor.getMapper().getInputData();

return {
dimensions: vtkImageData.getDimensions(),
spacing: vtkImageData.getSpacing(),
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/types/Colormap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ type OpacityMapping = {

type ColormapPublic = {
/** name of the colormap */
name: string;
name?: string;
opacity?: OpacityMapping[] | number;
/** midpoint mapping between values to opacity if the colormap
* is getting used for fusion, this is an array of arrays which
* each array containing 2 values, the first value is the value
Expand All @@ -25,7 +26,6 @@ type ColormapPublic = {
* the points in the middle to be mapped to different opacities
* instead of a linear mapping from 0 to 1.
*/
opacityMapping?: OpacityMapping[];
};

export type { ColormapRegistration, ColormapPublic };
export type { ColormapRegistration, ColormapPublic, OpacityMapping };
Loading