Skip to content

Commit

Permalink
feat(transfer-function): show data CDF in transfer function (#607)
Browse files Browse the repository at this point in the history
* feat: mark transfer function as needing histogram

* feat: mark bounds for transfer function histogram

* refactor: pull histogram calculation into function

* feat: link up a transfer function CDF line shader

* fix: linting

* fix(log): correect console.log of histogram from VR

* fix: correct histogram index when invlerp and tfs are mixed

* feat: change transfer function line colours

* refactor: define CDF shader as a function
  • Loading branch information
seankmartin authored Jul 24, 2024
1 parent 4e378b4 commit aa9efda
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 79 deletions.
2 changes: 1 addition & 1 deletion src/volume_rendering/volume_render_layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1253,7 +1253,7 @@ outputValue = vec4(1.0, 1.0, 1.0, 1.0);
for (let j = 0; j < 256; ++j) {
tempBuffer2[j] = tempBuffer[j * 4];
}
console.log("histogram%d", i, tempBuffer2.join(" "));
console.log(`histogram${i}`, tempBuffer2.join(" "));
}
}
endHistogramShader();
Expand Down
11 changes: 9 additions & 2 deletions src/webgl/shader_ui_controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1418,7 +1418,11 @@ export class ShaderControlState
(state) => {
const channels: HistogramChannelSpecification[] = [];
for (const { control, trackable } of state.values()) {
if (control.type !== "imageInvlerp") continue;
if (
control.type !== "imageInvlerp" &&
control.type !== "transferFunction"
)
continue;
channels.push({ channel: trackable.value.channel });
}
return channels;
Expand All @@ -1444,7 +1448,10 @@ export class ShaderControlState
const histogramBounds = makeCachedLazyDerivedWatchableValue((state) => {
const bounds: DataTypeInterval[] = [];
for (const { control, trackable } of state.values()) {
if (control.type === "imageInvlerp") {
if (
control.type === "imageInvlerp" ||
control.type === "transferFunction"
) {
bounds.push(trackable.value.window);
} else if (control.type === "propertyInvlerp") {
const { dataType, range, window } =
Expand Down
105 changes: 52 additions & 53 deletions src/widget/invlerp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { Uint64 } from "#src/util/uint64.js";
import { getWheelZoomAmount } from "#src/util/wheel_zoom.js";
import type { WatchableVisibilityPriority } from "#src/visibility_priority/frontend.js";
import { getMemoizedBuffer } from "#src/webgl/buffer.js";
import type { GL } from "#src/webgl/context.js";
import type { ParameterizedEmitterDependentShaderGetter } from "#src/webgl/dynamic_shader.js";
import { parameterizedEmitterDependentShaderGetter } from "#src/webgl/dynamic_shader.js";
import type { HistogramSpecifications } from "#src/webgl/empirical_cdf.js";
Expand Down Expand Up @@ -78,6 +79,55 @@ const inputEventMap = EventActionMap.fromObject({
"shift?+wheel": { action: "zoom-via-wheel" },
});

export function createCDFLineShader(gl: GL, textureUnit: symbol) {
const builder = new ShaderBuilder(gl);
defineLineShader(builder);
builder.addTextureSampler("sampler2D", "uHistogramSampler", textureUnit);
builder.addOutputBuffer("vec4", "out_color", 0);
builder.addAttribute("uint", "aDataValue");
builder.addUniform("float", "uBoundsFraction");
builder.addVertexCode(`
float getCount(int i) {
return texelFetch(uHistogramSampler, ivec2(i, 0), 0).x;
}
vec4 getVertex(float cdf, int i) {
float x;
if (i == 0) {
x = -1.0;
} else if (i == 255) {
x = 1.0;
} else {
x = float(i) / 254.0 * uBoundsFraction * 2.0 - 1.0;
}
return vec4(x, cdf * (2.0 - uLineParams.y) - 1.0 + uLineParams.y * 0.5, 0.0, 1.0);
}
`);
builder.setVertexMain(`
int lineNumber = int(aDataValue);
int dataValue = lineNumber;
float cumSum = 0.0;
for (int i = 0; i <= dataValue; ++i) {
cumSum += getCount(i);
}
float total = cumSum + getCount(dataValue + 1);
float cumSumEnd = dataValue == ${NUM_CDF_LINES - 1} ? cumSum : total;
if (dataValue == ${NUM_CDF_LINES - 1}) {
cumSum + getCount(dataValue + 1);
}
for (int i = dataValue + 2; i < 256; ++i) {
total += getCount(i);
}
total = max(total, 1.0);
float cdf1 = cumSum / total;
float cdf2 = cumSumEnd / total;
emitLine(getVertex(cdf1, lineNumber), getVertex(cdf2, lineNumber + 1), 1.0);
`);
builder.setFragmentMain(`
out_color = vec4(0.0, 1.0, 1.0, getLineAlpha());
`);
return builder.build();
}

export class CdfController<
T extends RangeAndWindowIntervals,
> extends RefCounted {
Expand Down Expand Up @@ -287,7 +337,7 @@ export function getUpdatedRangeAndWindowParameters<
// 256 bins in total. The first and last bin are for values below the lower bound/above the upper
// bound.
const NUM_HISTOGRAM_BINS_IN_RANGE = 254;
const NUM_CDF_LINES = NUM_HISTOGRAM_BINS_IN_RANGE + 1;
export const NUM_CDF_LINES = NUM_HISTOGRAM_BINS_IN_RANGE + 1;

/**
* Panel that shows Cumulative Distribution Function (CDF) of visible data.
Expand Down Expand Up @@ -325,58 +375,7 @@ class CdfPanel extends IndirectRenderedPanel {
).value;

private lineShader = this.registerDisposer(
(() => {
const builder = new ShaderBuilder(this.gl);
defineLineShader(builder);
builder.addTextureSampler(
"sampler2D",
"uHistogramSampler",
histogramSamplerTextureUnit,
);
builder.addOutputBuffer("vec4", "out_color", 0);
builder.addAttribute("uint", "aDataValue");
builder.addUniform("float", "uBoundsFraction");
builder.addVertexCode(`
float getCount(int i) {
return texelFetch(uHistogramSampler, ivec2(i, 0), 0).x;
}
vec4 getVertex(float cdf, int i) {
float x;
if (i == 0) {
x = -1.0;
} else if (i == 255) {
x = 1.0;
} else {
x = float(i) / 254.0 * uBoundsFraction * 2.0 - 1.0;
}
return vec4(x, cdf * (2.0 - uLineParams.y) - 1.0 + uLineParams.y * 0.5, 0.0, 1.0);
}
`);
builder.setVertexMain(`
int lineNumber = int(aDataValue);
int dataValue = lineNumber;
float cumSum = 0.0;
for (int i = 0; i <= dataValue; ++i) {
cumSum += getCount(i);
}
float total = cumSum + getCount(dataValue + 1);
float cumSumEnd = dataValue == ${NUM_CDF_LINES - 1} ? cumSum : total;
if (dataValue == ${NUM_CDF_LINES - 1}) {
cumSum + getCount(dataValue + 1);
}
for (int i = dataValue + 2; i < 256; ++i) {
total += getCount(i);
}
total = max(total, 1.0);
float cdf1 = cumSum / total;
float cdf2 = cumSumEnd / total;
emitLine(getVertex(cdf1, lineNumber), getVertex(cdf2, lineNumber + 1), 1.0);
`);
builder.setFragmentMain(`
out_color = vec4(0.0, 1.0, 1.0, getLineAlpha());
`);
return builder.build();
})(),
(() => createCDFLineShader(this.gl, histogramSamplerTextureUnit))(),
);

private regionCornersBuffer = getSquareCornersBuffer(this.gl, 0, -1, 1, 1);
Expand Down
55 changes: 33 additions & 22 deletions src/widget/shader_controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,43 +75,23 @@ function getShaderLayerControlFactory<LayerType extends UserLayer>(
case "checkbox":
return checkboxLayerControl(() => controlState.trackable);
case "imageInvlerp": {
let histogramIndex = 0;
for (const [
otherName,
{
control: { type: otherType },
},
] of shaderControlState.state) {
if (otherName === controlId) break;
if (otherType === "imageInvlerp") ++histogramIndex;
}
return channelInvlerpLayerControl(() => ({
dataType: control.dataType,
defaultChannel: control.default.channel,
watchableValue: controlState.trackable,
channelCoordinateSpaceCombiner:
shaderControlState.channelCoordinateSpaceCombiner,
histogramSpecifications: shaderControlState.histogramSpecifications,
histogramIndex,
histogramIndex: calculateHistogramIndex(),
legendShaderOptions: layerShaderControls.legendShaderOptions,
}));
}
case "propertyInvlerp": {
let histogramIndex = 0;
for (const [
otherName,
{
control: { type: otherType },
},
] of shaderControlState.state) {
if (otherName === controlId) break;
if (otherType === "propertyInvlerp") ++histogramIndex;
}
return propertyInvlerpLayerControl(() => ({
properties: control.properties,
watchableValue: controlState.trackable,
histogramSpecifications: shaderControlState.histogramSpecifications,
histogramIndex,
histogramIndex: calculateHistogramIndex(),
legendShaderOptions: layerShaderControls.legendShaderOptions,
}));
}
Expand All @@ -122,9 +102,40 @@ function getShaderLayerControlFactory<LayerType extends UserLayer>(
channelCoordinateSpaceCombiner:
shaderControlState.channelCoordinateSpaceCombiner,
defaultChannel: control.default.channel,
histogramSpecifications: shaderControlState.histogramSpecifications,
histogramIndex: calculateHistogramIndex(),
}));
}
}

function calculateHistogramIndex(controlType: string = control.type) {
const isMatchingControlType = (otherControlType: string) => {
if (
controlType === "imageInvlerp" ||
controlType === "transferFunction"
) {
return (
otherControlType === "imageInvlerp" ||
otherControlType === "transferFunction"
);
} else if (controlType === "propertyInvlerp") {
return otherControlType === "propertyInvlerp";
} else {
throw new Error(`${controlType} does not support histogram index.`);
}
};
let histogramIndex = 0;
for (const [
otherName,
{
control: { type: otherType },
},
] of shaderControlState.state) {
if (otherName === controlId) break;
if (isMatchingControlType(otherType)) histogramIndex++;
}
return histogramIndex;
}
}

function getShaderLayerControlDefinition<LayerType extends UserLayer>(
Expand Down
Loading

0 comments on commit aa9efda

Please sign in to comment.