Skip to content

Commit

Permalink
fix: transfer function correct interpolation
Browse files Browse the repository at this point in the history
  • Loading branch information
seankmartin committed Apr 9, 2024
1 parent fc06afc commit 5de6aa3
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 198 deletions.
62 changes: 38 additions & 24 deletions src/widget/transfer_function.browser_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,16 @@ import { defaultDataTypeRange } from "#src/util/lerp.js";
import { Uint64 } from "#src/util/uint64.js";
import { getShaderType } from "#src/webgl/shader_lib.js";
import { fragmentShaderTest } from "#src/webgl/shader_testing.js";
import type {
TransferFunctionParameters} from "#src/widget/transfer_function.js";
import type { TransferFunctionParameters } from "#src/widget/transfer_function.js";
import {
SortedControlPoints,
ControlPoint,
LookupTable,
TransferFunction,
NUM_COLOR_CHANNELS,

defineTransferFunctionShader,
enableTransferFunctionShader} from "#src/widget/transfer_function.js";
enableTransferFunctionShader,
} from "#src/widget/transfer_function.js";

const TRANSFER_FUNCTION_LENGTH = 512;

Expand All @@ -57,10 +56,10 @@ function makeTransferFunction(controlPoints: ControlPoint[]) {
}

describe("lerpBetweenControlPoints", () => {
const range = defaultDataTypeRange[DataType.UINT8];
const output = new Uint8Array(NUM_COLOR_CHANNELS * TRANSFER_FUNCTION_LENGTH);
it("returns transparent black when given no control points for raw classes", () => {
it("returns transparent black when given no control points for base classes", () => {
const controlPoints: ControlPoint[] = [];
const range = defaultDataTypeRange[DataType.UINT8];
const sortedControlPoints = new SortedControlPoints(controlPoints, range);
const lookupTable = new LookupTable(TRANSFER_FUNCTION_LENGTH);
lookupTable.updateFromControlPoints(sortedControlPoints);
Expand Down Expand Up @@ -105,34 +104,44 @@ describe("lerpBetweenControlPoints", () => {
const firstPointTransferIndex = transferFunction.toLookupTableIndex(0)!;
const secondPointTransferIndex = transferFunction.toLookupTableIndex(1)!;
const thirdPointTransferIndex = transferFunction.toLookupTableIndex(2)!;
expect(firstPointTransferIndex).toBe(Math.floor((120 / 255) * 511));
expect(secondPointTransferIndex).toBe(Math.floor((140 / 255) * 511));
expect(thirdPointTransferIndex).toBe(Math.floor((200 / 255) * 511));

// Transparent black up to the first control point
expect(
output
.slice(0, NUM_COLOR_CHANNELS * firstPointTransferIndex)
.every((value) => value === 0),
).toBeTruthy();
// The last control point value after the last control point
expect(
output
.slice(NUM_COLOR_CHANNELS * thirdPointTransferIndex)
.every((value) => value === 255),
).toBeTruthy();

const firstColor = controlPoints[0].outputColor;
const secondColor = controlPoints[1].outputColor;
// Performs linear interpolation between the first and second control points
const firstColor =
transferFunction.sortedControlPoints.controlPoints[0].outputColor;
const secondColor =
transferFunction.sortedControlPoints.controlPoints[1].outputColor;
const firstToSecondDifference =
secondPointTransferIndex - firstPointTransferIndex;
for (
let i = firstPointTransferIndex * NUM_COLOR_CHANNELS;
i < secondPointTransferIndex * NUM_COLOR_CHANNELS;
i++
) {
const difference = Math.floor((i - 120 * NUM_COLOR_CHANNELS) / 4);
const expectedValue =
firstColor[i % NUM_COLOR_CHANNELS] +
((secondColor[i % NUM_COLOR_CHANNELS] -
firstColor[i % NUM_COLOR_CHANNELS]) *
difference) /
20;
const decimalPart = expectedValue - Math.floor(expectedValue);
const t =
Math.floor(i / NUM_COLOR_CHANNELS - firstPointTransferIndex) /
firstToSecondDifference;
const difference =
secondColor[i % NUM_COLOR_CHANNELS] -
firstColor[i % NUM_COLOR_CHANNELS];
const expectedValue = firstColor[i % NUM_COLOR_CHANNELS] + t * difference;
// If the decimal part is 0.5, it could be rounded up or down depending on precision.
const decimalPart = expectedValue - Math.floor(expectedValue);
if (Math.abs(decimalPart - 0.5) < 0.001) {
expect([Math.floor(expectedValue), Math.ceil(expectedValue)]).toContain(
output[i],
Expand All @@ -142,21 +151,26 @@ describe("lerpBetweenControlPoints", () => {
}
}

const thirdColor = controlPoints[2].outputColor;
// Performs linear interpolation between the second and third control points
const thirdColor =
transferFunction.sortedControlPoints.controlPoints[2].outputColor;
const secondToThirdDifference =
thirdPointTransferIndex - secondPointTransferIndex;
for (
let i = secondPointTransferIndex * NUM_COLOR_CHANNELS;
i < thirdPointTransferIndex * NUM_COLOR_CHANNELS;
i++
) {
const difference = Math.floor((i - 140 * NUM_COLOR_CHANNELS) / 4);
const t =
Math.floor(i / NUM_COLOR_CHANNELS - secondPointTransferIndex) /
secondToThirdDifference;
const difference =
thirdColor[i % NUM_COLOR_CHANNELS] -
secondColor[i % NUM_COLOR_CHANNELS];
const expectedValue =
secondColor[i % NUM_COLOR_CHANNELS] +
((thirdColor[i % NUM_COLOR_CHANNELS] -
secondColor[i % NUM_COLOR_CHANNELS]) *
difference) /
60;
const decimalPart = expectedValue - Math.floor(expectedValue);
secondColor[i % NUM_COLOR_CHANNELS] + t * difference;
// If the decimal part is 0.5, it could be rounded up or down depending on precision.
const decimalPart = expectedValue - Math.floor(expectedValue);
if (Math.abs(decimalPart - 0.5) < 0.001) {
expect([Math.floor(expectedValue), Math.ceil(expectedValue)]).toContain(
output[i],
Expand Down
163 changes: 0 additions & 163 deletions src/widget/transfer_function.spec.ts

This file was deleted.

22 changes: 11 additions & 11 deletions src/widget/transfer_function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export class ControlPoint {
);
}
interpolateColor(other: ControlPoint, t: number): vec4 {
const outputColor = new vec4();
const outputColor = vec4.create();
for (let i = 0; i < 4; ++i) {
outputColor[i] = computeLerp(
[this.outputColor[i], other.outputColor[i]],
Expand Down Expand Up @@ -218,7 +218,7 @@ export class SortedControlPoints {
return this.findNearestControlPointIndex(value);
}
updatePointColor(index: number, color: vec4 | vec3) {
let outputColor = new vec4();
let outputColor = vec4.create();
if (outputColor.length === 3) {
outputColor = vec4.fromValues(
color[0],
Expand Down Expand Up @@ -281,6 +281,9 @@ export class LookupTable {
out[index + 2] = color[2];
out[index + 3] = color[3];
}
/**
* Convert the control point input value to an index in the transfer function lookup table
*/
function toTransferFunctionSpace(controlPoint: ControlPoint) {
return controlPoint.transferFunctionIndex(range, size);
}
Expand All @@ -306,21 +309,18 @@ export class LookupTable {
let controlPointIndex = 0;
for (let i = firstInputValue; i < size; ++i) {
const currentPoint = controlPoints[controlPointIndex];
const nextPoint =
controlPoints[
Math.min(controlPointIndex + 1, controlPoints.length - 1)
];
const lookupIndex = i * NUM_COLOR_CHANNELS;
if (currentPoint === nextPoint) {
if (controlPointIndex === controlPoints.length - 1) {
addLookupValue(lookupIndex, currentPoint.outputColor);
} else {
const currentInputValue = toTransferFunctionSpace(currentPoint);
const nextInputValue = toTransferFunctionSpace(nextPoint);
const nextPoint = controlPoints[controlPointIndex + 1];
const currentPointIndex = toTransferFunctionSpace(currentPoint);
const nextPointIndex = toTransferFunctionSpace(nextPoint);
const t =
(i - currentInputValue) / (nextInputValue - currentInputValue);
(i - currentPointIndex) / (nextPointIndex - currentPointIndex);
const lerpedColor = currentPoint.interpolateColor(nextPoint, t);
addLookupValue(lookupIndex, lerpedColor);
if (i === nextPoint.inputValue) {
if (i >= nextPointIndex) {
controlPointIndex++;
}
}
Expand Down

0 comments on commit 5de6aa3

Please sign in to comment.