Compose a series of monotonic piecewise functions and a color transfer function into a single color transfer function#3531
Conversation
699fd8a to
7b3f85c
Compare
|
Did you check ColorTransferFunction/CssFilters.js ? Maybe it already does what you need... |
7b3f85c to
d75d6a0
Compare
df5aea4 to
4644df0
Compare
4644df0 to
15b4364
Compare
Interesting. These filters look like direct color transforms (i.e. transforming 5d color vectors [R, G,B, A, 1] whereas we are working mostly with scalar value functions/transforms that are not restricted to linear matrix transforms. If I understand correctly the CssFilters transforms are entirely represented using 5D matrices or a composition of them. |
4a2b612 to
13545e5
Compare
Indeed, what you want to do is non-linear. Those 5D matrices won't work for your use case. Nevermind |
|
@jadh4v please let us know when it is a good time for us to do a code review. |
05b3c3f to
af9d3da
Compare
@finetjul @sankhesh The PR is ready for re-review. I have made some significant changes to my previous approach. Primarily taking an iterative numerical approach for computing inverse as well as additional bisection of composed function. |
af9d3da to
fa3dd36
Compare
|
https://github.com/Kitware/vtk-js/blob/master/Documentation/.vitepress/sidebar.ts is an empty file but is still tracked. It gets updated if you build documentation, and can throw off devs into thinking that it needs to be committed. Should we remove it from being tracked? |
fa3dd36 to
6f34e9f
Compare
findX(y) returns an x such that getValue(x) === y. It locates the first segment whose endpoint values bracket y and bisects it through forward evaluation, so midpoint and sharpness are honored without duplicating their math; within a segment the curve is monotonic between its endpoint values, so bisection converges. An exact hit on y is not reachable for every y, so the bracket collapsing to adjacent floats is the loop's termination guarantee, not an equality test. Where several x attain y — a plateau, or the constant run after the jump of a discontinuous segment (sharpness ~1) — the search returns the leftmost one, so a step yields its jump location regardless of the node's midpoint. Exact node values are returned exactly: the left endpoint always, the right endpoint except on a step, where the saturated run before x1 is resolved to the jump instead. For y outside the output range with clamping on, the endpoint holding the nearest extremum is returned; null is returned when no x qualifies (extremum only attained at an interior node, clamping off, or empty function). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add vtkColorTransferFunction.getDataPointer() returning the function's nodes, mirroring the VTK/C++ API. The returned array is a live read-only reference to the internal nodes, documented as such: direct writes bypass sortAndUpdateRange() and modified(), so changes would not propagate through the pipeline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ains compose(fnList, colorFn, outputFn, errorThreshold?, maxSubdivisionDepth?) collapses an ordered chain of monotonic piecewise value-transform functions plus a final color transfer function into one output color transfer function. Each stage's breakpoints are pulled back through the upstream inverses into the source domain, then forward-propagated to sample the composed color. Transforms must be monotonic (a 'Varied' input aborts). Without an errorThreshold they must also be piecewise linear; with one, shaped segments are captured by subdividing each output segment until linear interpolation is within the threshold, bounded by maxSubdivisionDepth (default 24). The color transfer function is unrestricted. Returns the maximum midpoint deviation from the true chain (0 = exact), or null when an input is rejected, leaving outputFn untouched. Test the result against null, not truthiness (0 is falsy). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 'Unknown (Error condition)' entry was carried over from the VTK/C++ header, but in this implementation functionType can only be 0-3 and the default case returns 'Varied'; the .d.ts union already reflects that. Note the divergence from C++ instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ctions DICOM slice viewer demonstrating the DICOM value transform pipeline: modality, VOI, and interactive window/level transforms are stored as vtkPiecewiseFunction instances and chained with a color map preset via the PiecewiseFunction compose() helper into a single RGB transfer function applied to the image slice. Files are decoded with itk-wasm loaded on demand; the transform parameters are driven by lil-gui controls. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
getNodeValue was declared to return void but the implementation returns -1 (index out of range) or 1 (success); type both node accessors as -1 | 1, narrow val from any[] to number[], and document the return values. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6f34e9f to
13e9b48
Compare
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 the transforms to be applied on the GPU via the composed transfer function's lookup at render time, rather than storing the transformed pixel arrays 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
vtkPiecewiseFunction.findX(y)(new method): inverse ofgetValue(). Locates the first segment whose endpoint values bracketyand bisects it through forward evaluation, so midpoint and sharpness are honored. Returnsnullwhen noxmaps toy(e.g. out-of-range values with clamping off).compose(fnList, colorFn, outputFn, errorThreshold?, maxSubdivisionDepth?)(new helper inCommon/DataModel/PiecewiseFunction/helpers): composes an ordered chain of piecewise value-transform functions with a final-stage color transfer function into a single output color transfer function. Breakpoints from every stage are pulled back through the upstream inverses (findX) into the source domain, then forward-propagated to sample the composed color at each one.getType() !== 'Varied') so breakpoints can be pulled back unambiguously. Without anerrorThresholdthey must also be piecewise linear (midpoint 0.5, sharpness 0 on every segment), since the output interpolates linearly between the collected breakpoints; with anerrorThreshold, shaped (non-linear) segments in any stage are captured instead by recursively subdividing each output segment at its midpoint until linear interpolation matches the true composed color to within the threshold on every color channel. The subdivision is depth-first, so the threshold alone cannot bound the node count:maxSubdivisionDepth(default 24) caps the worst case at 2^depth − 1 extra nodes per segment and guarantees termination at discontinuities. The color transfer function itself is never restricted.errorThresholdmeans the depth cap stopped refinement somewhere), ornullwhen an input is rejected (with a warning, leavingoutputFnuntouched). Note that 0 is falsy: test the result againstnullfor success, never its truthiness.compose([modalityFn, voiFn, userFn], colorFn, resultFn, 0.01)— see the new example.vtkColorTransferFunction.getDataPointer()(new method): returns the function's nodes, for parity with the VTK/C++ API.vtkPiecewiseFunctiontypings fixed:getNodeValue/setNodeValuewere declared to returnvoidbut return-1(index out of range) or1(success);valnarrowed fromany[]tonumber[].New example
Rendering/ComposePiecewiseFunctions: DICOM slice viewer chaining modality, VOI, and interactive window/level transforms with a color map preset into a single transfer function.Documentation and TypeScript definitions were updated to match those changes
PR and Code Checklist
npm run reformatto have correctly formatted codeTesting