pixelwise_transform¶
- pixelwise_transform(arr, scale=1.0, offset=0.0, clamp_min=None, clamp_max=None)[source]¶
Apply linear transform scale*arr + offset with optional clamping per element.
NaNs propagate unchanged.
- Parameters:
- Return type:
Overview¶
pixelwise_transform applies a per-element linear transform to 1D–4D numeric (time‑first) arrays:
Clamping is optional—if both clamp_min and clamp_max are None the result is simply scale * x + offset. NaNs propagate unchanged (no special handling or implicit replacement).
Supported ranks (any numeric dtype accepted; internally coerced to float64):
- 1D: (N,)
- 2D: (T, F) or generic (rows, cols)
- 3D: (T, Y, X)
- 4D: (T, B, Y, X)
Purpose¶
A lightweight, fused transform for: - Contrast / dynamic range adjustments - Reflectance normalization - Simple standardization (e.g., scale & shift to [0,1]) - Post-smoothing enhancement (chaining with temporal moving averages)
Parameters¶
arrnumpy.ndarray(1D–4D)Input numeric array.
scalefloat, default1.0Multiplicative factor applied to each element.
offsetfloat, default0.0Additive offset after scaling.
clamp_minfloatorNone, defaultNoneLower bound; values below are set to this boundary.
clamp_maxfloatorNone, defaultNoneUpper bound; values above are set to this boundary.
Returns¶
numpy.ndarray of same shape and float64 dtype.
NaN Behavior¶
Input NaNs remain NaN regardless of scaling or clamping.
No implicit replacement; chain with
replace_nansor masking functions if needed.
Formula Details¶
Unclamped: .. math:: y = s cdot x + o
Clamped: .. math:
y = \min\left(\max\left(s \cdot x + o,\ \text{clamp\_min}\right),\ \text{clamp\_max}\right)
If only one clamp is defined, only that bound is enforced.
Complexity¶
Time: O(N) where N = total elements.
Memory: One output allocation (no intermediate temporaries).
Parallelism: Current implementation is a single pass; potential vectorization handled by Rust compiler. (No Rayon parallelization—operation typically memory bound.)
Examples¶
Basic scaling: .. code-block:: python
import numpy as np from eo_processor import pixelwise_transform
arr = np.array([0.0, 0.5, 1.0]) out = pixelwise_transform(arr, scale=2.0, offset=-0.5) print(out) # [-0.5, 0.5, 1.5]
With clamping: .. code-block:: python
- stretched = pixelwise_transform(arr, scale=2.0, offset=-0.5,
clamp_min=0.0, clamp_max=1.0)
print(stretched) # [0.0, 0.5, 1.0]
2D transform: .. code-block:: python
img = np.random.rand(512, 512) norm = pixelwise_transform(img, scale=1.2, offset=-0.1, clamp_min=0.0, clamp_max=1.0)
4D stack (time, band, y, x): .. code-block:: python
cube4 = np.random.rand(12, 4, 256, 256) adjusted = pixelwise_transform(cube4, scale=0.8, offset=0.02)
Chaining with moving average: .. code-block:: python
from eo_processor import moving_average_temporal, pixelwise_transform cube = np.random.rand(48, 512, 512) smooth = moving_average_temporal(cube, window=5) enhanced = pixelwise_transform(smooth, scale=1.1, offset=0.05,
clamp_min=0.0, clamp_max=1.0)
Use Cases¶
Producing normalized inputs for ML models after temporal smoothing.
Reflectance stretching for visualization.
Intensity compression before encoding (clamping to [0, 1]).
Quick brightness/contrast adjustments in preprocessing pipelines.
Performance Notes¶
A single memory-bound pass typically dominated by system bandwidth. Rust avoids Python loop overhead, benefiting large arrays (e.g., multi-band tiles). Gains vs pure NumPy may be modest for very simple transforms because NumPy also performs vectorized arithmetic efficiently; primary advantage is when chaining inside a Rust-heavy pipeline (reduced Python call overhead).
Representative Micro Benchmark (Indicative)¶
Shape (Y, X) = (4096, 4096), scale=1.2, offset=-0.1, clamp [0,1]:
Speedup ≈ 1.15x (environment dependent; rerun locally).
Fairness & Baseline¶
Speedups >1× emerge primarily from reduced Python dispatch when chaining many operations; raw arithmetic often similar. Report which baseline is used (single combined vectorized expression in NumPy vs separate steps).
Performance Claim Template¶
Benchmark:
Shape: (4096, 4096)
Operation: scale=1.2, offset=-0.1, clamp_min=0.0, clamp_max=1.0
NumPy: 0.060s
Rust pixelwise_transform: 0.052s
Speedup: 1.15x
Methodology: single run, time.perf_counter(), float64 data, warm cache
Validation: np.allclose(rust_out, np.clip(img*1.2 - 0.1, 0, 1), atol=1e-12)
Guidance¶
Use pixelwise_transform when:
- Operating inside a Rust-accelerated workflow.
- You need uniform NaN propagation without mask arrays.
- Chaining scale/offset/clamp logic across large multidimensional arrays.
Limitations¶
No gamma or nonlinear curves (implement externally).
No per-band distinct scale/offset (pre-apply broadcasting or extend function).
No vectorized SIMD annotations yet (opportunity for future optimization).
No automatic handling of integer overflow (inputs coerced to float64 first).
Potential Future Extensions¶
Per-band scaling arrays (e.g.
scale[b]).Nonlinear transforms (gamma, log, sigmoid).
Fused multi-step pipeline (e.g. smoothing + transform + masking in one pass).
Optional parallelization for extremely large arrays (evaluate overhead benefits).
Validation Snippet¶
import numpy as np
from eo_processor import pixelwise_transform
img = np.random.rand(1024, 1024)
rust_out = pixelwise_transform(img, scale=1.2, offset=-0.1, clamp_min=0, clamp_max=1)
np_out = np.clip(img * 1.2 - 0.1, 0, 1)
assert np.allclose(rust_out, np_out, atol=1e-12)
End of pixelwise_transform reference.