Status: Needs Review
This page has not been reviewed for accuracy and completeness. Content may be outdated or contain errors.
Selector Nodes¶
Selector nodes cover current fixed, supervised, and trainable band-selection plus false-RGB generation workflows.
Channel Selectors¶
channel_selector
¶
Channel selector nodes for HSI to RGB conversion.
This module provides port-based nodes for selecting spectral channels from hyperspectral cubes and composing RGB images for downstream processing (e.g., with AdaCLIP).
Selectors gate/reweight individual channels independently:
output[c] = weight[c] * input[c] (diagonal operation, preserves channel count).
For cross-channel linear projection (full matrix, reduces channel count),
see :mod:cuvis_ai.node.channel_mixer.
Normalization design
All channel selectors share a common RGB normalization strategy in
ChannelSelectorBase, controlled by NormMode:
-
Percentile bounds (not absolute min/max): SpectralRadiance data contains outlier pixels whose absolute max can be 10x the median, compressing 99% of the image into the bottom of the brightness range. Using the 0.5th / 99.5th percentile clips these outliers and preserves visual dynamic range.
-
Per-channel [3] bounds: Separate min/max per R/G/B channel preserves colour balance. A single scalar bound would distort hue if one channel has a wider range than the others.
-
Three modes (
NormMode):running(default) — warmup + percentile accumulation with optional freeze. The first warmup frames use per-frame normalization (visually good immediately) while accumulating global bounds. After warmup, accumulated bounds are used. By default, accumulation is frozen after 20 frames to prevent late outliers from changing brightness; setfreeze_running_bounds_after_frames=Noneto keep legacy unbounded accumulation.statistical— pre-computed global percentiles viaStatisticalTrainer. Use when exact global stats matter and a full first pass is acceptable.per_frame— each frame normalized independently; no inter-frame state. Use for unrelated images or single-frame pipelines. -
Why warmup + accumulation (not EMA): Exponential moving averages have recency bias — for long videos the early-frame statistics are forgotten. Min/max accumulation bounds only ever expand (min-of-lows, max-of-highs) during the accumulation window, giving stable normalization without recency drift. The warmup period ensures the first few frames look natural before enough data has been accumulated.
NormMode
¶
Bases: StrEnum
RGB normalization mode for channel selectors.
ChannelSelectorBase
¶
ChannelSelectorBase(
norm_mode=RUNNING,
apply_gamma=True,
freeze_running_bounds_after_frames=20,
running_warmup_frames=_WARMUP_FRAMES,
**kwargs,
)
Bases: Node
Base class for hyperspectral band selection strategies.
This base class defines the common input/output ports for band selection nodes and provides shared percentile-based RGB normalization (see module docstring for design rationale).
Subclasses should implement forward() and _compute_raw_rgb() (the
latter is used by statistical_initialization and _running_normalize).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
norm_mode
|
str | NormMode
|
RGB normalization mode. Default |
RUNNING
|
apply_gamma
|
bool
|
Apply sRGB gamma curve after normalization. Default |
True
|
freeze_running_bounds_after_frames
|
int | None
|
When |
20
|
running_warmup_frames
|
int
|
Number of initial |
_WARMUP_FRAMES
|
Ports
INPUT_SPECS
cube : float32, shape (-1, -1, -1, -1)
Hyperspectral cube in BHWC format.
wavelengths : float32, shape (-1,)
Wavelength array in nanometers.
OUTPUT_SPECS
rgb_image : float32, shape (-1, -1, -1, 3)
Composed RGB image in BHWC format (0-1 range).
band_info : dict
Metadata about selected bands.
Source code in cuvis_ai/node/channel_selector.py
statistical_initialization
¶
Compute global percentile bounds across the entire dataset.
Uses _compute_raw_rgb() to convert each batch, then accumulates
per-channel percentile bounds (min-of-lows, max-of-highs).
Source code in cuvis_ai/node/channel_selector.py
NDVISelector
¶
NDVISelector(
nir_nm=827.0,
red_nm=668.0,
colormap_min=-0.7,
colormap_max=0.5,
eps=1e-06,
**kwargs,
)
Bases: _NormalizedDifferenceIndexBase
Normalized Difference Vegetation Index renderer.
Computes:
(CUBE(nir_nm) - CUBE(red_nm)) / (CUBE(nir_nm) + CUBE(red_nm))
Bands are resolved by nearest available sensor wavelength. The raw NDVI map
is returned via index_image and rgb_image contains a colour-mapped
render. The scalar NDVI image is mapped with the HSV-style colormap used
by the Blood_OXY plugin XML.
Source code in cuvis_ai/node/channel_selector.py
forward
¶
Compute NDVI plus colour-mapped RGB output.
Source code in cuvis_ai/node/channel_selector.py
FixedWavelengthSelector
¶
Bases: ChannelSelectorBase
Fixed wavelength band selection (e.g., 650, 550, 450 nm).
Selects bands nearest to the specified target wavelengths for R, G, B channels. This is the simplest band selection strategy that produces "true color-ish" images.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_wavelengths
|
tuple[float, float, float]
|
Target wavelengths for R, G, B channels in nanometers. Default: (650.0, 550.0, 450.0) |
(650.0, 550.0, 450.0)
|
Source code in cuvis_ai/node/channel_selector.py
forward
¶
Select bands and compose RGB image.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cube
|
Tensor
|
Hyperspectral cube [B, H, W, C]. |
required |
wavelengths
|
Tensor
|
Wavelength array [C]. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dictionary with "rgb_image" and "band_info" keys. |
Source code in cuvis_ai/node/channel_selector.py
RangeAverageFalseRGBSelector
¶
RangeAverageFalseRGBSelector(
red_range=(580.0, 650.0),
green_range=(500.0, 580.0),
blue_range=(420.0, 500.0),
**kwargs,
)
Bases: ChannelSelectorBase
Range-based false RGB selection by averaging bands per channel.
For each output channel (R/G/B), all spectral bands within the configured wavelength range are averaged per pixel. Channels with no matching bands are filled with zeros.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
red_range
|
tuple[float, float]
|
Inclusive wavelength range for red channel in nanometers. |
(580.0, 650.0)
|
green_range
|
tuple[float, float]
|
Inclusive wavelength range for green channel in nanometers. |
(500.0, 580.0)
|
blue_range
|
tuple[float, float]
|
Inclusive wavelength range for blue channel in nanometers. |
(420.0, 500.0)
|
Source code in cuvis_ai/node/channel_selector.py
forward
¶
Average spectral bands inside RGB ranges and compose normalized RGB.
Source code in cuvis_ai/node/channel_selector.py
FastRGBSelector
¶
FastRGBSelector(
red_range=(580.0, 650.0),
green_range=(500.0, 580.0),
blue_range=(420.0, 500.0),
normalization_strength=0.75,
**kwargs,
)
Bases: ChannelSelectorBase
cuvis-next parity FastRGB renderer.
This selector mirrors the cuvis fast_rgb user-plugin behavior:
- Per-channel contiguous spectral range averaging.
- Dynamic per-frame normalization by global RGB mean when enabled.
- Static reflectance-style scaling when normalization is disabled.
- 8-bit quantization before returning float RGB in [0, 1].
Source code in cuvis_ai/node/channel_selector.py
forward
¶
Render fast_rgb output with cuvis-next parity scaling.
Source code in cuvis_ai/node/channel_selector.py
HighContrastSelector
¶
Bases: ChannelSelectorBase
Data-driven band selection using spatial variance + Laplacian energy.
For each wavelength window, selects the band with the highest score based on: score = variance + alpha * Laplacian_energy
This produces "high contrast" images that may work better for visual anomaly detection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
windows
|
Sequence[tuple[float, float]]
|
Wavelength windows for Blue, Green, Red channels. Default: ((440, 500), (500, 580), (610, 700)) for visible spectrum. |
((440, 500), (500, 580), (610, 700))
|
alpha
|
float
|
Weight for Laplacian energy term. Default: 0.1 |
0.1
|
Source code in cuvis_ai/node/channel_selector.py
forward
¶
Select high-contrast bands and compose RGB image.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cube
|
Tensor
|
Hyperspectral cube [B, H, W, C]. |
required |
wavelengths
|
Tensor
|
Wavelength array [C]. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dictionary with "rgb_image" and "band_info" keys. |
Source code in cuvis_ai/node/channel_selector.py
CIRSelector
¶
Bases: ChannelSelectorBase
Color Infrared (CIR) false color composition.
Maps NIR to Red, Red to Green, Green to Blue for false-color composites. This is useful for highlighting vegetation and certain anomalies.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nir_nm
|
float
|
Near-infrared wavelength in nm. Default: 860.0 |
860.0
|
red_nm
|
float
|
Red wavelength in nm. Default: 670.0 |
670.0
|
green_nm
|
float
|
Green wavelength in nm. Default: 560.0 |
560.0
|
Source code in cuvis_ai/node/channel_selector.py
forward
¶
Select CIR bands and compose false-color image.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cube
|
Tensor
|
Hyperspectral cube [B, H, W, C]. |
required |
wavelengths
|
Tensor
|
Wavelength array [C]. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dictionary with "rgb_image" and "band_info" keys. |
Source code in cuvis_ai/node/channel_selector.py
CIETristimulusFalseRGBSelector
¶
Bases: ChannelSelectorBase
CIE 1931 tristimulus-based false RGB rendering.
Converts a hyperspectral cube to sRGB by integrating each pixel's spectrum with the CIE 1931 2-degree standard observer color matching functions (x_bar, y_bar, z_bar), applying a D65 white point normalization, and converting from CIE XYZ to linear sRGB.
Normalization and sRGB gamma are handled by ChannelSelectorBase (see
apply_gamma parameter inherited from the base class).
This produces the most physically grounded false RGB and lands closest to the distribution SAM3's Perception Encoder expects.
For wavelengths outside the visible range (approx. >780 nm), the CMFs are zero, so NIR bands do not contribute to the output.
Source code in cuvis_ai/node/channel_selector.py
forward
¶
Convert HSI cube to sRGB via CIE 1931 tristimulus integration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cube
|
Tensor
|
Hyperspectral cube [B, H, W, C]. |
required |
wavelengths
|
Tensor | ndarray
|
Wavelength array [C] in nanometers. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dictionary with "rgb_image" [B, H, W, 3] and "band_info". |
Source code in cuvis_ai/node/channel_selector.py
CameraEmulationFalseRGBSelector
¶
CameraEmulationFalseRGBSelector(
r_peak=610.0,
g_peak=540.0,
b_peak=460.0,
r_sigma=40.0,
g_sigma=35.0,
b_sigma=30.0,
**kwargs,
)
Bases: ChannelSelectorBase
Camera-emulation false RGB using smooth Gaussian sensitivity curves.
Defines three broad, smooth Gaussian weighting curves over the spectral
bands that mimic R/G/B camera sensitivity (peaks at configurable
wavelengths). The weight matrix W is [3, num_bands], applied as
rgb = W @ spectrum. Non-negativity is enforced by construction.
This is simple, stable, and requires no training. Good middle ground between single-band selection and learned mapping.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
r_peak
|
float
|
Red channel peak wavelength in nm. Default: 610.0 |
610.0
|
g_peak
|
float
|
Green channel peak wavelength in nm. Default: 540.0 |
540.0
|
b_peak
|
float
|
Blue channel peak wavelength in nm. Default: 460.0 |
460.0
|
r_sigma
|
float
|
Red channel Gaussian sigma in nm. Default: 40.0 |
40.0
|
g_sigma
|
float
|
Green channel Gaussian sigma in nm. Default: 35.0 |
35.0
|
b_sigma
|
float
|
Blue channel Gaussian sigma in nm. Default: 30.0 |
30.0
|
Source code in cuvis_ai/node/channel_selector.py
forward
¶
Convert HSI cube to false RGB using Gaussian camera sensitivity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cube
|
Tensor
|
Hyperspectral cube [B, H, W, C]. |
required |
wavelengths
|
Tensor | ndarray
|
Wavelength array [C] in nanometers. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dictionary with "rgb_image" [B, H, W, 3] and "band_info". |
Source code in cuvis_ai/node/channel_selector.py
SupervisedSelectorBase
¶
SupervisedSelectorBase(
num_spectral_bands,
score_weights=(1.0, 1.0, 1.0),
lambda_penalty=0.5,
**kwargs,
)
Bases: ChannelSelectorBase
Base class for supervised band selection strategies.
This class adds an optional mask input port and implements common
logic for statistical initialization via :meth:fit.
The mask is assumed to be binary (0/1), where 1 denotes the positive class (e.g. stone) and 0 denotes the negative class (e.g. lentil/background).
Source code in cuvis_ai/node/channel_selector.py
requires_initial_fit
property
¶
Whether this node requires statistical initialization from training data.
Returns:
| Type | Description |
|---|---|
bool
|
Always True for supervised band selectors. |
statistical_initialization
¶
Initialize band selection using supervised scoring.
Computes Fisher, AUC, and MI scores for each band, delegates to
:meth:_select_bands for strategy-specific selection, and stores
the 3 selected bands.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_stream
|
InputStream
|
Training data stream with cube, mask, and wavelengths. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If band selection doesn't return exactly 3 bands. |
Source code in cuvis_ai/node/channel_selector.py
forward
¶
Generate false-color RGB from selected bands.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cube
|
Tensor
|
Hyperspectral cube [B, H, W, C]. |
required |
wavelengths
|
ndarray
|
Wavelengths for each channel [C]. |
required |
mask
|
Tensor
|
Ground truth mask (unused in forward, required for initialization). |
None
|
context
|
Context
|
Pipeline execution context (unused). |
None
|
**_
|
Any
|
Additional unused keyword arguments. |
{}
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dictionary with "rgb_image" [B, H, W, 3] and "band_info" metadata. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the node has not been statistically initialized. |
Source code in cuvis_ai/node/channel_selector.py
SupervisedCIRSelector
¶
SupervisedCIRSelector(
windows=(
(840.0, 910.0),
(650.0, 720.0),
(500.0, 570.0),
),
score_weights=(1.0, 1.0, 1.0),
lambda_penalty=0.5,
**kwargs,
)
Bases: SupervisedSelectorBase
Supervised CIR/NIR band selection with window constraints.
Windows are typically set to:
- NIR: 840-910 nm
- Red: 650-720 nm
- Green: 500-570 nm
The selector chooses one band per window using a supervised score (Fisher + AUC + MI) with an mRMR-style redundancy penalty.
Source code in cuvis_ai/node/channel_selector.py
SupervisedWindowedSelector
¶
SupervisedWindowedSelector(
windows=(
(440.0, 500.0),
(500.0, 580.0),
(610.0, 700.0),
),
score_weights=(1.0, 1.0, 1.0),
lambda_penalty=0.5,
**kwargs,
)
Bases: SupervisedSelectorBase
Supervised band selection constrained to visible RGB windows.
Similar to :class:HighContrastSelector, but uses label-driven scores.
Default windows:
- Blue: 440-500 nm
- Green: 500-580 nm
- Red: 610-700 nm
Source code in cuvis_ai/node/channel_selector.py
SupervisedFullSpectrumSelector
¶
Bases: SupervisedSelectorBase
Supervised selection without window constraints.
Picks the top-3 discriminative bands globally with an mRMR-style redundancy penalty applied over the full spectrum.
Source code in cuvis_ai/node/channel_selector.py
SoftChannelSelector
¶
SoftChannelSelector(
n_select,
input_channels,
init_method="uniform",
temperature_init=5.0,
temperature_min=0.1,
temperature_decay=0.9,
hard=False,
eps=1e-06,
**kwargs,
)
Bases: Node
Soft channel selector with temperature-based Gumbel-Softmax selection.
This is a selector node — it gates/reweights individual channels independently:
output[c] = weight[c] * input[c] (diagonal operation, preserves channel count).
For cross-channel linear projection that reduces channel count, see
:class:cuvis_ai.node.channel_mixer.ConcreteChannelMixer or
:class:cuvis_ai.node.channel_mixer.LearnableChannelMixer.
This node learns to select a subset of input channels using differentiable channel selection with temperature annealing. Supports:
- Statistical initialization (uniform or importance-based)
- Gradient-based optimization with temperature scheduling
- Entropy and diversity regularization
- Hard selection at inference time
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_select
|
int
|
Number of channels to select |
required |
input_channels
|
int
|
Number of input channels |
required |
init_method
|
('uniform', 'variance')
|
Initialization method for channel weights (default: "uniform") |
"uniform"
|
temperature_init
|
float
|
Initial temperature for Gumbel-Softmax (default: 5.0) |
5.0
|
temperature_min
|
float
|
Minimum temperature (default: 0.1) |
0.1
|
temperature_decay
|
float
|
Temperature decay factor per epoch (default: 0.9) |
0.9
|
hard
|
bool
|
If True, use hard selection at inference (default: False) |
False
|
eps
|
float
|
Small constant for numerical stability (default: 1e-6) |
1e-06
|
Attributes:
| Name | Type | Description |
|---|---|---|
channel_logits |
Parameter or Tensor
|
Unnormalized channel importance scores [n_channels] |
temperature |
float
|
Current temperature for Gumbel-Softmax |
Source code in cuvis_ai/node/channel_selector.py
statistical_initialization
¶
Initialize channel selection weights from data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_stream
|
InputStream
|
Iterator yielding dicts matching INPUT_SPECS (port-based format) Expected format: {"data": tensor} where tensor is BHWC |
required |
Source code in cuvis_ai/node/channel_selector.py
update_temperature
¶
Update temperature with decay schedule.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
epoch
|
int
|
Current epoch number (used for per-epoch decay) |
None
|
step
|
int
|
Current training step (for more granular control) |
None
|
Source code in cuvis_ai/node/channel_selector.py
get_selection_weights
¶
Get current channel selection weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hard
|
bool
|
If True, use hard selection (top-k). If None, uses self.hard. |
None
|
Returns:
| Type | Description |
|---|---|
Tensor
|
Selection weights [n_channels] summing to n_select |
Source code in cuvis_ai/node/channel_selector.py
forward
¶
Apply soft channel selection to input.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Tensor
|
Input tensor [B, H, W, C] |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Tensor]
|
Dictionary with "selected" key containing reweighted channels and optional "weights" key containing selection weights |
Source code in cuvis_ai/node/channel_selector.py
TopKIndices
¶
Bases: Node
Utility node that surfaces the top-k channel indices from selector weights.
This node extracts the indices of the top-k weighted channels from a selector's weight vector. Useful for introspection and reporting which channels were selected.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
k
|
int
|
Number of top indices to return |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
k |
int
|
Number of top indices to return |
Source code in cuvis_ai/node/channel_selector.py
forward
¶
Return the indices of the top-k weighted channels.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
Tensor
|
Channel selection weights [n_channels] |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Tensor]
|
Dictionary with "indices" key containing top-k indices |