Compose a series of piecewise functions and a color transfer function into a single color transfer function#3531
Conversation
…ple piecewise functions This example tests a feature that allows composition of multiple piecewise functions into a single function which results in a single color transfer function. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move the piecewise function composition logic out of the ComposePiecewiseFunctions example into a reusable, exported compose() helper in Common/DataModel/PiecewiseFunction/helpers.js. Add findX() as a public method on vtkPiecewiseFunction, replacing the example local invertFn implementation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cover vtkPiecewiseFunction.findX (linear interpolation, multi-segment, flat-segment handling, decreasing functions, clamping, empty function) and Common/DataModel/PiecewiseFunction/helpers.compose (identity passthrough, breakpoint inversion, multi-stage chaining, repeated calls). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
compose() no longer needs an explicit dataRange since outputFn's mapping range is already derived from its node positions via addRGBPoint's sortAndUpdateRange. Update the example and tests to match the new compose(fnList, colorFn, outputFn) signature. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
699fd8a to
7b3f85c
Compare
| // UI helpers | ||
| // ---------------------------------------------------------------------------- | ||
|
|
||
| const body = document.querySelector('body'); |
There was a problem hiding this comment.
We do use lil-gui now, check other examples
| if (model.clamping && nodes.length > 0) { | ||
| const first = nodes[0]; | ||
| const last = nodes[nodes.length - 1]; | ||
| if (y <= first.y) { |
There was a problem hiding this comment.
If your piecewise function is a "decreasing function", you won't have the expected result here. Maybe you could:
- check what y is higher between the first and the last point
- or better: check the tangent at the first/last points.
(please create a unit test to enforce this scenario)
| for (let i = 0; i < nodes.length - 1; i++) { | ||
| const { x: x0, y: y0 } = nodes[i]; | ||
| const { x: x1, y: y1 } = nodes[i + 1]; | ||
| if (y0 === y1) { |
There was a problem hiding this comment.
you claim that you will return the "first" maching one, but if y is == y0, then you won't return the first matching x.
(Please create a unit test to check this scenario.)
| const ret = []; | ||
| const v = [0, 0, 0, 0, 0, 0]; // [x, r, g, b, midpoint, sharpness] | ||
| for (let i = 0; i < cfun.getSize(); i++) { | ||
| if (cfun.getNodeValue(i, v) === 1) { |
There was a problem hiding this comment.
can you please take the opportunity to fix index.d.ts to document the return value type of getNodeValue
| @@ -0,0 +1,67 @@ | |||
| function getColorFunctionXValues(cfun) { | |||
| const ret = []; | |||
There was a problem hiding this comment.
for performance reason, you could allocate it to the size of cfun.getSize()
| const ret = []; | ||
| const v = [0, 0, 0, 0, 0, 0]; // [x, r, g, b, midpoint, sharpness] | ||
| for (let i = 0; i < cfun.getSize(); i++) { | ||
| if (cfun.getNodeValue(i, v) === 1) { |
There was a problem hiding this comment.
for performance reason, I wouldn't bother check the return value...
| const minY = Math.min(y0, y1); | ||
| const maxY = Math.max(y0, y1); | ||
| if (y >= minY && y <= maxY) { | ||
| return x0 + ((y - y0) / (y1 - y0)) * (x1 - x0); |
There was a problem hiding this comment.
here you ignore midpoint and sharpness and assume it is linear but it may not
| // Also reverse compute from x-values of the final-stage color transfer function, | ||
| // and add those to our xSet so that we don't miss any break points defined | ||
| // within the color transfer function. | ||
| const colorXs = getColorFunctionXValues(colorFn); |
There was a problem hiding this comment.
why not create a getDataPointer convenient method for a color function ? that would bring some consistency...
| * outputs through to the color function, producing equivalent break points | ||
| * in the composed result. h(g(f(x))): g's x-values live in f's output domain | ||
| * and must be pulled back through f-inverse; h's x-values need g-inverse then | ||
| * f-inverse, and so on. |
There was a problem hiding this comment.
you assume piecewise functions to be linear between nodes, but they may not.
|
Did you check ColorTransferFunction/CssFilters.js ? Maybe it already does what you need... |
Context
Imaging applications may require multiple scalar transforms on pixel data before it is rendered. These transforms can be done directly on the image pixel array before passing the image to the GPU. However, the transform can necessitate a data type conversion to higher number of bytes to maintain precision. This can consume a lot of memory and make pixel arrays very large if the application is handling many such images simultaneously.
We implement a feature to compose multiple scalar transforms (piecewise functions) and one color transfer function into a single color transfer function that represents the entire transform from original input scalars to output display colors. This allows us to push the color transformations directly to the GPU shader where the pixel values will transform to color values just-in-time to render, rather than storing them inflated on the CPU side.
Results
We have added an example that shows how three different scalar transforms (e.g. modality transforms, values-of-interest, color-window adjustments, etc) can be composed along with a color function.
Changes
PR and Code Checklist
npm run reformatto have correctly formatted codeTesting