tb_specular_free_transform — 2D typed op

Data kinds: rgbimagergbimage

Call: fullseye.apply(img, "tb_specular_free_transform", a=0.5, b=0.5) (the 2-D model is one image plus two scalar knobs a,b∈[0,1])

tb_specular_free_transform: input → output

*The figure is the actual output on a synthetic 128×128 input. Left: input, right: output. Point clouds are drawn as a top-down scatter (brightness = z), 1-D series as a line plot, volumes as the maximum-intensity projection along z, videos as the middle frame, complex images as magnitude; return values that are not pictures are shown as the values themselves.*

*Knob a does not change the output (measured: identical at 0.1 / 0.5 / 0.9).*

*Knob b does not change the output (measured: identical at 0.1 / 0.5 / 0.9).*

Stages (the ops that come before → this op, left to right):

tb_specular_free_transform: stages (docs site)

On other images (synthetic scene / photo / coins. Top row: inputs, bottom row: their outputs. Knobs at default):

tb_specular_free_transform: other inputs (docs site)

Usage

Project out the illuminant direction: the part of the image a highlight cannot touch. → (H, W, 3).

`I - (I.G) G for the unit illuminant colour G`. Under the dichromatic

model the interface term is `m_s * G`, so it lies entirely in the removed

direction and the result is invariant to any specular term whatsoever

exactly, for any lobe shape, any strength, any spatial pattern. That is the

specular-invariant subspace of Mallick et al. (2005); this operator is the

projection itself, with no rotation into named channels, so it stays in RGB

and composes with the rest of the family.

Use it when the *shape* of the specular lobe is unknown or the surface is

textured — feature matching, edge detection and correlation all work in this

subspace without any of the assumptions

:func:specular_diffuse_split needs.

This is a projection, not a picture. The result loses one of three

degrees of freedom (its component along `G` is exactly zero everywhere)

and, for an image with negative values after black-level subtraction, keeps

them. It is not a displayable "highlight-removed photo" and does not claim

to be; for that, use :func:specular_diffuse_split.

Raises `ValueError: *image_rgb* is not a valid (H, W, 3)` linear

RGB image (see :func:specular_diffuse_split); *illuminant_rgb* is not a

non-zero 3-vector.

Typed bridge of the specular op `specular_free_transform into the 2-D evolution registry: the same implementation, called under the op(v, a, b) convention. This op has no tunable parameter; a and b` are unused.

References (sample data, literature)

• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).

• Operator provenance and references — the sources of the research/methods this op family came from.

Try it in Studio

The program below has been verified to run (same input as the figure). In Studio's help this block becomes buttons that load and run it on the spot.

img_to_rgb 0.50 0.50
tb_specular_free_transform 0.50 0.50

▸ Load this pipeline  ·  Load & run

Runnable examples (verified samples that actually call this op)

The examples below call the underlying ledger op specular_free_transform. This bridge op is the same implementation adapted to the fn(v, a, b) convention, so the behaviour carries over unchanged (only the call form differs).

poc_leaf_disease_areapy -3.11 examples/poc_leaf_disease_area.py

specular_photometricpy -3.11 examples/specular_photometric.py

Ops the type connects to (they accept rgbimage as input)

identity · tb_wetness · tb_sensor_capture · tb_specular_diffuse_split · tb_specular_coefficient_map · tb_rgb_to_quaternion

Same category (typed)

tb_points_to_voxel · tb_estimate_point_normals · tb_iss_keypoints · tb_project_points · tb_render_point_depth · tb_statistical_outlier_removal · tb_radius_outlier_removal · tb_voxel_grid_downsample


*Provenance: ops.py — 2D operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*

© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.