Skip to content

Commit aa9efda

Browse files
authored
feat(transfer-function): show data CDF in transfer function (#607)
* 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
1 parent 4e378b4 commit aa9efda

File tree

5 files changed

+171
-79
lines changed

5 files changed

+171
-79
lines changed

src/volume_rendering/volume_render_layer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1253,7 +1253,7 @@ outputValue = vec4(1.0, 1.0, 1.0, 1.0);
12531253
for (let j = 0; j < 256; ++j) {
12541254
tempBuffer2[j] = tempBuffer[j * 4];
12551255
}
1256-
console.log("histogram%d", i, tempBuffer2.join(" "));
1256+
console.log(`histogram${i}`, tempBuffer2.join(" "));
12571257
}
12581258
}
12591259
endHistogramShader();

src/webgl/shader_ui_controls.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,7 +1418,11 @@ export class ShaderControlState
14181418
(state) => {
14191419
const channels: HistogramChannelSpecification[] = [];
14201420
for (const { control, trackable } of state.values()) {
1421-
if (control.type !== "imageInvlerp") continue;
1421+
if (
1422+
control.type !== "imageInvlerp" &&
1423+
control.type !== "transferFunction"
1424+
)
1425+
continue;
14221426
channels.push({ channel: trackable.value.channel });
14231427
}
14241428
return channels;
@@ -1444,7 +1448,10 @@ export class ShaderControlState
14441448
const histogramBounds = makeCachedLazyDerivedWatchableValue((state) => {
14451449
const bounds: DataTypeInterval[] = [];
14461450
for (const { control, trackable } of state.values()) {
1447-
if (control.type === "imageInvlerp") {
1451+
if (
1452+
control.type === "imageInvlerp" ||
1453+
control.type === "transferFunction"
1454+
) {
14481455
bounds.push(trackable.value.window);
14491456
} else if (control.type === "propertyInvlerp") {
14501457
const { dataType, range, window } =

src/widget/invlerp.ts

Lines changed: 52 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import { Uint64 } from "#src/util/uint64.js";
4949
import { getWheelZoomAmount } from "#src/util/wheel_zoom.js";
5050
import type { WatchableVisibilityPriority } from "#src/visibility_priority/frontend.js";
5151
import { getMemoizedBuffer } from "#src/webgl/buffer.js";
52+
import type { GL } from "#src/webgl/context.js";
5253
import type { ParameterizedEmitterDependentShaderGetter } from "#src/webgl/dynamic_shader.js";
5354
import { parameterizedEmitterDependentShaderGetter } from "#src/webgl/dynamic_shader.js";
5455
import type { HistogramSpecifications } from "#src/webgl/empirical_cdf.js";
@@ -78,6 +79,55 @@ const inputEventMap = EventActionMap.fromObject({
7879
"shift?+wheel": { action: "zoom-via-wheel" },
7980
});
8081

82+
export function createCDFLineShader(gl: GL, textureUnit: symbol) {
83+
const builder = new ShaderBuilder(gl);
84+
defineLineShader(builder);
85+
builder.addTextureSampler("sampler2D", "uHistogramSampler", textureUnit);
86+
builder.addOutputBuffer("vec4", "out_color", 0);
87+
builder.addAttribute("uint", "aDataValue");
88+
builder.addUniform("float", "uBoundsFraction");
89+
builder.addVertexCode(`
90+
float getCount(int i) {
91+
return texelFetch(uHistogramSampler, ivec2(i, 0), 0).x;
92+
}
93+
vec4 getVertex(float cdf, int i) {
94+
float x;
95+
if (i == 0) {
96+
x = -1.0;
97+
} else if (i == 255) {
98+
x = 1.0;
99+
} else {
100+
x = float(i) / 254.0 * uBoundsFraction * 2.0 - 1.0;
101+
}
102+
return vec4(x, cdf * (2.0 - uLineParams.y) - 1.0 + uLineParams.y * 0.5, 0.0, 1.0);
103+
}
104+
`);
105+
builder.setVertexMain(`
106+
int lineNumber = int(aDataValue);
107+
int dataValue = lineNumber;
108+
float cumSum = 0.0;
109+
for (int i = 0; i <= dataValue; ++i) {
110+
cumSum += getCount(i);
111+
}
112+
float total = cumSum + getCount(dataValue + 1);
113+
float cumSumEnd = dataValue == ${NUM_CDF_LINES - 1} ? cumSum : total;
114+
if (dataValue == ${NUM_CDF_LINES - 1}) {
115+
cumSum + getCount(dataValue + 1);
116+
}
117+
for (int i = dataValue + 2; i < 256; ++i) {
118+
total += getCount(i);
119+
}
120+
total = max(total, 1.0);
121+
float cdf1 = cumSum / total;
122+
float cdf2 = cumSumEnd / total;
123+
emitLine(getVertex(cdf1, lineNumber), getVertex(cdf2, lineNumber + 1), 1.0);
124+
`);
125+
builder.setFragmentMain(`
126+
out_color = vec4(0.0, 1.0, 1.0, getLineAlpha());
127+
`);
128+
return builder.build();
129+
}
130+
81131
export class CdfController<
82132
T extends RangeAndWindowIntervals,
83133
> extends RefCounted {
@@ -287,7 +337,7 @@ export function getUpdatedRangeAndWindowParameters<
287337
// 256 bins in total. The first and last bin are for values below the lower bound/above the upper
288338
// bound.
289339
const NUM_HISTOGRAM_BINS_IN_RANGE = 254;
290-
const NUM_CDF_LINES = NUM_HISTOGRAM_BINS_IN_RANGE + 1;
340+
export const NUM_CDF_LINES = NUM_HISTOGRAM_BINS_IN_RANGE + 1;
291341

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

327377
private lineShader = this.registerDisposer(
328-
(() => {
329-
const builder = new ShaderBuilder(this.gl);
330-
defineLineShader(builder);
331-
builder.addTextureSampler(
332-
"sampler2D",
333-
"uHistogramSampler",
334-
histogramSamplerTextureUnit,
335-
);
336-
builder.addOutputBuffer("vec4", "out_color", 0);
337-
builder.addAttribute("uint", "aDataValue");
338-
builder.addUniform("float", "uBoundsFraction");
339-
builder.addVertexCode(`
340-
float getCount(int i) {
341-
return texelFetch(uHistogramSampler, ivec2(i, 0), 0).x;
342-
}
343-
vec4 getVertex(float cdf, int i) {
344-
float x;
345-
if (i == 0) {
346-
x = -1.0;
347-
} else if (i == 255) {
348-
x = 1.0;
349-
} else {
350-
x = float(i) / 254.0 * uBoundsFraction * 2.0 - 1.0;
351-
}
352-
return vec4(x, cdf * (2.0 - uLineParams.y) - 1.0 + uLineParams.y * 0.5, 0.0, 1.0);
353-
}
354-
`);
355-
builder.setVertexMain(`
356-
int lineNumber = int(aDataValue);
357-
int dataValue = lineNumber;
358-
float cumSum = 0.0;
359-
for (int i = 0; i <= dataValue; ++i) {
360-
cumSum += getCount(i);
361-
}
362-
float total = cumSum + getCount(dataValue + 1);
363-
float cumSumEnd = dataValue == ${NUM_CDF_LINES - 1} ? cumSum : total;
364-
if (dataValue == ${NUM_CDF_LINES - 1}) {
365-
cumSum + getCount(dataValue + 1);
366-
}
367-
for (int i = dataValue + 2; i < 256; ++i) {
368-
total += getCount(i);
369-
}
370-
total = max(total, 1.0);
371-
float cdf1 = cumSum / total;
372-
float cdf2 = cumSumEnd / total;
373-
emitLine(getVertex(cdf1, lineNumber), getVertex(cdf2, lineNumber + 1), 1.0);
374-
`);
375-
builder.setFragmentMain(`
376-
out_color = vec4(0.0, 1.0, 1.0, getLineAlpha());
377-
`);
378-
return builder.build();
379-
})(),
378+
(() => createCDFLineShader(this.gl, histogramSamplerTextureUnit))(),
380379
);
381380

382381
private regionCornersBuffer = getSquareCornersBuffer(this.gl, 0, -1, 1, 1);

src/widget/shader_controls.ts

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -75,43 +75,23 @@ function getShaderLayerControlFactory<LayerType extends UserLayer>(
7575
case "checkbox":
7676
return checkboxLayerControl(() => controlState.trackable);
7777
case "imageInvlerp": {
78-
let histogramIndex = 0;
79-
for (const [
80-
otherName,
81-
{
82-
control: { type: otherType },
83-
},
84-
] of shaderControlState.state) {
85-
if (otherName === controlId) break;
86-
if (otherType === "imageInvlerp") ++histogramIndex;
87-
}
8878
return channelInvlerpLayerControl(() => ({
8979
dataType: control.dataType,
9080
defaultChannel: control.default.channel,
9181
watchableValue: controlState.trackable,
9282
channelCoordinateSpaceCombiner:
9383
shaderControlState.channelCoordinateSpaceCombiner,
9484
histogramSpecifications: shaderControlState.histogramSpecifications,
95-
histogramIndex,
85+
histogramIndex: calculateHistogramIndex(),
9686
legendShaderOptions: layerShaderControls.legendShaderOptions,
9787
}));
9888
}
9989
case "propertyInvlerp": {
100-
let histogramIndex = 0;
101-
for (const [
102-
otherName,
103-
{
104-
control: { type: otherType },
105-
},
106-
] of shaderControlState.state) {
107-
if (otherName === controlId) break;
108-
if (otherType === "propertyInvlerp") ++histogramIndex;
109-
}
11090
return propertyInvlerpLayerControl(() => ({
11191
properties: control.properties,
11292
watchableValue: controlState.trackable,
11393
histogramSpecifications: shaderControlState.histogramSpecifications,
114-
histogramIndex,
94+
histogramIndex: calculateHistogramIndex(),
11595
legendShaderOptions: layerShaderControls.legendShaderOptions,
11696
}));
11797
}
@@ -122,9 +102,40 @@ function getShaderLayerControlFactory<LayerType extends UserLayer>(
122102
channelCoordinateSpaceCombiner:
123103
shaderControlState.channelCoordinateSpaceCombiner,
124104
defaultChannel: control.default.channel,
105+
histogramSpecifications: shaderControlState.histogramSpecifications,
106+
histogramIndex: calculateHistogramIndex(),
125107
}));
126108
}
127109
}
110+
111+
function calculateHistogramIndex(controlType: string = control.type) {
112+
const isMatchingControlType = (otherControlType: string) => {
113+
if (
114+
controlType === "imageInvlerp" ||
115+
controlType === "transferFunction"
116+
) {
117+
return (
118+
otherControlType === "imageInvlerp" ||
119+
otherControlType === "transferFunction"
120+
);
121+
} else if (controlType === "propertyInvlerp") {
122+
return otherControlType === "propertyInvlerp";
123+
} else {
124+
throw new Error(`${controlType} does not support histogram index.`);
125+
}
126+
};
127+
let histogramIndex = 0;
128+
for (const [
129+
otherName,
130+
{
131+
control: { type: otherType },
132+
},
133+
] of shaderControlState.state) {
134+
if (otherName === controlId) break;
135+
if (isMatchingControlType(otherType)) histogramIndex++;
136+
}
137+
return histogramIndex;
138+
}
128139
}
129140

130141
function getShaderLayerControlDefinition<LayerType extends UserLayer>(

0 commit comments

Comments
 (0)