Documentation๏
๐ Table of Contents๏
๐ฏ Overview๏
SHINIER is a modern Python implementation of the SHINE (Spectrum, Histogram, and Intensity Normalization, Equalization, and Refinements) toolbox, originally developed in MATLAB by Willenbockel et al. (2010). This new version implemented new options (e.g., color management, dithering and Coltuc, Bolon & Chassery (2006) exact histogram specification algorithm) and refined the previous ones.
Main Objectives๏
Compatibility: Maintain compatibility with the original MATLAB implementation
Performance: Optimize performance for large datasets
Extensibility: Object-oriented architecture for easy extensions
Precision: Reduce rounding errors in multi-step calculations
๐๏ธ Package Architecture๏
Module Structure๏
shinier/src
โโโ color # Color-space conversion
โ โโโ Converter.py # Color conversion and transfer functions
โ โโโ GamutControl.py # Gamut management and repairs
โ โโโ ...
โโโ __init__.py # Package entry point
โโโ base.py # Customized Pydantic base-model
โโโ ImageDataset.py # Image collection management
โโโ ImageListIO.py # Image file input/output
โโโ ImageProcessor.py # Main image processing
โโโ Options.py # Parameter configuration
โโโ SHINIER.py # Command-line interface
โโโ utils.py # Utility functions and MATLAB operators
โโโ ...
Processing Flow๏
graph TD
A[Options] --> B[ImageDataset]
B --> C[ImageProcessor]
C --> D[Mode-based Processing]
D --> E[Final Conversion]
E --> F[Output Images]
๐ฌ MATLAB vs Python Differences๏
1. Rounding Operators๏
MATLAB round()๏
>> round([2.5, 3.5, -2.5, -3.5])
ans = [3, 4, -3, -4]
Rounds away from zero (round-away-from-zero)
Deterministic behavior for values exactly halfway
Python numpy.round() - IEEE 754 Standard๏
>>> np.round([2.5, 3.5, -2.5, -3.5])
array([2., 4., -2., -4.])
Rounds to nearest even (round-half-to-even, โBankersโ Roundingโ)
IEEE 754-2019 Standard compliant (recommended default for binary formats)
Statistically unbiased for large datasets
Reduces cumulative rounding errors in iterative computations
SHINIER Solution - Compatibility vs Standards๏
class MatlabOperators:
@staticmethod
def round(x):
"""Simulates MATLAB's rounding behavior for compatibility"""
return np.sign(x) * np.ceil(np.floor(np.abs(x) * 2) / 2)
Scientific Rationale:
While SHINIER provides MATLAB-compatible rounding for legacy compatibility, the IEEE 754-2019 standard recommends round-half-to-even because:
Statistical Unbiasedness: Round-half-to-even produces unbiased results when rounding large datasets
Error Minimization: Reduces cumulative rounding errors in iterative algorithms
Industry Standard: Used by most modern computing systems (Python, R, Julia, etc.)
Numerical Stability: Better performance in floating-point arithmetic
References:
IEEE 754-2019: โIEEE Standard for Floating-Point Arithmeticโ
Goldberg, D. (1991): โWhat Every Computer Scientist Should Know About Floating-Point Arithmeticโ
Kahan, W. (1996): โIEEE Standard 754 for Binary Floating-Point Arithmeticโ
Recommendation: Use legacy_mode=False (default) for scientifically robust results, legacy_mode=True only for MATLAB compatibility validation.
2. Integer Type Conversion๏
MATLAB uint8()๏
>> uint8([2.5, 3.5, -2.5, 255.5])
ans = [3, 4, 0, 255]
Rounds to nearest integer
Clips values between [0, 255]
Python numpy.astype('uint8')๏
>>> np.array([2.5, 3.5, -2.5, 256, 257]).astype('uint8')
array([2, 3, 254, 0, 1], dtype=uint8)
Truncates decimal values
Wrap-around behavior for out-of-range values
SHINIER Solution๏
@staticmethod
def uint8(x):
"""Replicates MATLAB's uint8 behavior"""
return np.uint8(np.clip(MatlabOperators.round(x), 0, 255))
3. Standard Deviation Calculation๏
MATLAB std2()๏
>> A = rand(100, 100);
>> std2(A)
ans = 0.287630126526993
Uses N-1 divisor (unbiased estimator)
Python numpy.std() - Statistical Best Practice๏
>>> A = np.random.rand(100, 100)
>>> np.std(A)
0.28761574466111084
Uses N divisor (biased estimator) by default
Population standard deviation (mathematically correct for complete populations)
Consistent with most statistical software (R, Julia, etc.)
SHINIER Solution - Scientific Flexibility๏
@staticmethod
def std2(x):
"""Replicates MATLAB's std2 function for compatibility"""
return np.std(x, ddof=1) # ddof=1 for N-1 (sample std)
Statistical Rationale:
The choice between N and N-1 divisors depends on the statistical context:
N divisor: Population standard deviation (when you have the complete population)
N-1 divisor: Sample standard deviation (when estimating population from sample)
SHINIER Approach: Provides both options through ddof parameter, allowing users to choose based on their statistical requirements.
4. RGB to Grayscale Conversion๏
MATLAB rgb2gray() - Legacy Standard๏
% Uses Rec.ITU-R BT.601-7 (SD monitors)
Y' = 0.299 * R + 0.587 * G + 0.114 * B
Rec.ITU-R BT.601-7: Standard-Definition television (1982)
Legacy standard optimized for CRT monitors
SHINIER rgb2gray() - Modern Standards Support๏
def rgb2gray(image, recommendation='rec709'):
"""RGB to grayscale conversion with multiple luma coefficient standards"""
rgb_luma_coefficients = {
'equal': [0.333, 0.333, 0.333], # Equal weighting (not perceptually accurate)
'rec601': [0.298936021293775, 0.587043074451121, 0.114020904255103], # SD legacy
'rec709': [0.2126, 0.7152, 0.0722], # HD standard (recommended)
'rec2020': [0.2627, 0.6780, 0.0593] # UHD standard
}
weights = rgb_luma_coefficients[recommendation]
return np.dot(image.astype(np.float64), weights)
Scientific Rationale:
Different luma coefficients are optimized for different display technologies and viewing conditions:
Rec.ITU-R BT.601: Legacy compatibility for SD displays
Rec.ITU-R BT.709: Recommended default for modern displays (HD, 4K)
Rec.ITU-R BT.2020: Future-proof for UHD/HDR displays
References:
Poynton, Charles (1997): โFrequently Asked Questions about Colorโ
ITU-R BT.601-7 (2011): โStudio encoding parameters of digital television for standard 4:3 and wide-screen 16:9 aspect ratiosโ
ITU-R BT.709-6 (2015): โParameter values for the HDTV standards for production and international programme exchangeโ
ITU-R BT.2020-2 (2015): โParameter values for ultra-high definition television systemsโ
SHINIER Advantage: Provides multiple standards allowing users to choose the most appropriate for their display technology and research context.
WARNING AND REMINDER: Ajusting for the luminance transfer functions implemented in image-capturing devices and the precise calibration of display monitors are essential for accurate visual stimuli presentation.
โ๏ธ Detailed Processing Modes๏
Mode 1: Luminance Matching Only๏
mode = 1 # lum_match only
Algorithm:
A
target_lumvalue of0uses the dataset average for that statisticmean
0= average of image meansstd
0= average of image standard deviations
If
target_lum=(mean, std)is provided: use it directly as the targetIf one value is
None, leave that statistic unchanged for each image:target_lum=(None, 20): keep each image mean and set std to 20target_lum=(100, None): set mean to 100 and keep each image std
Apply linear rescaling per image:
new_pixel = (pixel - mean) * (target_std/std) + target_mean
Specific Parameters:
target_lum: Tuple(mean, std)wheremean โ [0, 255]orNone, andstd โ [0, +โ)orNone.0uses the dataset average for that statistic, so(0, 20)uses the average mean and a std of 20, while(100, 0)uses a mean of 100 and the average std.safe_lum_match: If True, automatically adjusts(target_mean, target_std)to keep all pixel values within [0, 255] (values may differ slightly from the requested target)
Mode 2: Histogram Matching Only๏
mode = 2 # hist_match only
Available Algorithms:
Exact specification (
hist_specification=0): Coltuc, Bolon & Chassery (2006) algorithmSpecification with noise (
hist_specification=1): Legacy version with noise addition
SSIM Optimization:
hist_optim=1: SSIM-based optimization (Avanaki, 2009))hist_iterations: Number of iterations (default: 10)step_size: Step size (default: 34)
Mode 3: Spatial Frequency Matching Only๏
mode = 3 # sf_match only
Equalizes the mean amplitude per spatial frequency across images โ i.e., the rotational average of the Fourier magnitude spectrum. Image phase (and thus structure) is preserved; only the amplitude envelope is adjusted.
Algorithm:
1 Convert images to float [0, 255] (HรWรC).
2. Build the target rotational magnitude profile (provided, or averaged from all inputs).
3. For each image: optionally pad โ FFT โ replace the radial magnitude profile with the target while keeping the phase โ inverse FFT โ crop back if padded.
4. Rescale per options.rescaling (0 = none, 1 = per-image, 2 = global, 3 = average) and clip to valid range.
5. Store float255 outputs and optionally log/plot spectral diagnostics.
Mode 4: Spectrum Matching Only๏
mode = 4 # spec_match only
Equalizes the amplitude at every spatial frequency and orientation across images โ matching the full 2D Fourier magnitude spectrum. Image phase (and thus structure) is preserved; only the amplitude at each frequency/orientation pair is adjusted.
Algorithm:
Convert images to float [0, 255] (HรWรC).
Build the target 2D magnitude (provided, or element-wise average across all inputs).
For each image: optionally pad โ FFT โ replace the full magnitude by the target while keeping the phase โ inverse FFT โ crop back if padded.
Apply rescaling per
options.rescalingand clip to the valid intensity range.Store float255 outputs and optionally log or visualize spectrum-related diagnostics.
Composite Modes (5-8)๏
Mode 5: Histogram + Spatial Frequency๏
mode = 5 # hist_match โ sf_match
Mode 6: Histogram + Spectrum๏
mode = 6 # hist_match โ spec_match
Mode 7: Spatial Frequency + Histogram๏
mode = 7 # sf_match โ hist_match
Mode 8: Spectrum + Histogram (Recommended)๏
mode = 8 # spec_match โ hist_match
High Numerical Precision:
Composite modes use temporary floating-point precision
Reduces rounding errors in multi-step calculations
Mode 9: Dithering Only๏
mode = 9 # only dithering
Border Artifacts and FFT Padding๏
Why border artifacts occur The Fourier transform implicitly treats an image as if it repeats infinitely in all directions: the left edge connects to the right, and the top to the bottom. When opposite edges differ, this creates artificial discontinuities that introduce unwanted high-frequency energy (spectral leakage), sometimes visible as ringing or edge artifacts after reconstruction.
How FFT padding helps Images can optionally be padded before computing the FFT, then cropped back to the original size after the inverse FFT. This pushes border discontinuities farther from the region of interest and reduces edge-related spectral artifacts.
Available padding modes (fft_padding_mode)
Mode |
Name |
Behavior |
|---|---|---|
|
Disabled |
No padding |
|
Reflect |
Mirror the image without repeating the edge pixel |
|
Symmetric |
Mirror the image including the edge pixel |
|
Constant |
Pad using a constant intensity value. If |
Notes
Padding reduces border discontinuities but does not remove the FFTโs periodic assumption.
1(Reflect) and2(Symmetric) generally preserve local image continuity better than3(Constant).3(Constant) padding avoids introducing mirrored structures.If a
target_spectrumarray is passed directly, it must already match the padded FFT dimensions when padding is enabled.FFT padding and feathered masks are complementary: feathered masks reduce visible edge discontinuities in the stimulus; FFT padding reduces spectral artifacts during Fourier operations. For psychophysics, feathered masks remain the recommended default.
๐๏ธ Main Classes๏
Converter๏
Encapsulates color-space conversions and transfer functions for Rec.601/709/2020.
class Converter:
model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid", validate_assignment=True)
rec_standard: Literal["rec601", "rec709", "rec2020"] = "rec709"
gamma: float = 2.4
safe_mode: bool = True
white_point: np.ndarray = Field(default_factory=lambda: WHITE_D65.copy())
M_RGB2XYZ: np.ndarray = Field(default_factory=lambda: M_RGB2XYZ_709.copy())
M_XYZ2RGB: np.ndarray = Field(default_factory=lambda: np.linalg.inv(M_RGB2XYZ_709))
GamutControl (Color gamut management)๏
Manages gamut repairs and dataset- or image-level constraints after luminance/chroma manipulations.
class GamutControl:
model_config = ConfigDict(arbitrary_types_allowed=True)
color_space: Literal['xyY'] = 'xyY'
strategy: Literal['constrain_dataset_luminance', 'constrain_dataset_chrominance', 'constrain_image_chrominance', 'constrain_image_luminance', 'clip'] = 'constrain_dataset_chrominance'
rec_standard: str = 'rec709'
warning_threshold: float = 1.0
prc_clipping: float = 0.5
low_Y_desaturate: bool = False
low_Y_threshold: float = 0.01
low_Y_fade_width: float = 0.0
log_low_Y_chroma_loss: bool = False
_converter: ColorConverter = PrivateAttr(default_factory=ColorConverter)
_converter_raw: ColorConverter = PrivateAttr(default_factory=ColorConverter)
# methods: apply_image, apply_dataset, apply_low_Y_desaturation, helpers for chroma masking and reliability
Options๏
Centralized configuration class for all processing parameters.
class Options:
# --- I/O ---
input_folder: Optional[Path] = Field(default=REPO_ROOT / "INPUT")
output_folder: Path = Field(default=REPO_ROOT / "OUTPUT")
# --- Masks ---
masks_folder: Optional[Path] = Field(default=None)
whole_image: Literal[1, 2, 3] = 1
background: Union[conint(ge=0, le=255), Literal[300]] = 300
# --- Mode ---
mode: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9] = 2
seed: Optional[int] = None
legacy_mode: bool = False
iterations: conint(ge=1) = 5
# --- Color ---
as_gray: bool = False
linear_luminance: bool = False
rec_standard: Literal[1, 2, 3] = 2
gamut_strategy: Literal['constrain_dataset_luminance', 'constrain_dataset_chrominance', 'constrain_image_chrominance', 'constrain_image_luminance', 'clip'] = Field(default='constrain_image_chrominance')
# --- Dithering / Memory ---
dithering: Literal[0, 1, 2] = 0
conserve_memory: bool = True
# --- Luminance ---
safe_lum_match: bool = True
target_lum: Tuple[Optional[confloat(ge=0, le=255)], Optional[confloat(ge=0)]] = (0, 0)
# --- Histogram ---
hist_optim: bool = False
hist_specification: Optional[Literal[1, 2, 3, 4]] = 4
hist_iterations: conint(ge=1) = 10
target_hist: Optional[Union[np.ndarray, Path, Literal["equal"]]] = Field(default=None)
# --- Fourier ---
rescaling: Optional[Literal[0, 1, 2, 3]] = 2
target_spectrum: Optional[Union[np.ndarray, Path]] = Field(default=None)
fft_padding_mode: Literal[0, 1, 2, 3] = 0
fft_padding_value: Union[int, Literal[300]] = 300
# --- Misc ---
verbose: Literal[-1, 0, 1, 2, 3] = 0
ImageDataset๏
Management of image and mask collections with state tracking.
class ImageDataset:
model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid", validate_assignment=True)
# --- User-provided / externally settable attributes ---
images: Optional[Union[ImageListIO, ImageListType]] = None
masks: Optional[Union[ImageListIO, ImageListType]] = None
options: Options = Field(default_factory=Options)
# --- Internally constructed / derived attributes ---
processing_logs: List[str] = Field(default_factory=list)
n_images: Optional[int] = None
n_masks: Optional[int] = None
images_name: Optional[List[str]] = None
masks_name: Optional[List[str]] = None
magnitudes: Optional[ImageListIO] = None
phases: Optional[ImageListIO] = None
buffer: Optional[ImageListIO] = None
buffer_other: Optional[ImageListIO] = None
ImageProcessor๏
Main image processing class.
class ImageProcessor:
model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=False)
# --- Public attributes ---
# User inputs are mainly: dataset, options.
# Test-related flags below are internal execution controls.
bool_masks: List = Field(default_factory=list)
dataset: ImageDataset
desaturate_chroma_on_low_luminance: bool = Field(default=True)
from_cli: bool = Field(default=False)
from_unit_test: bool = Field(default=False)
from_validation_test: bool = Field(default=False)
log: List[str] = Field(default_factory=list)
options: Optional[Options] = None
seed: Optional[int] = None
ssim_data: List[dict] = Field(default_factory=list)
ssim_results: List[dict] = Field(default_factory=list)
validation: List[dict] = Field(default_factory=list)
verbose: Literal[-1, 0, 1, 2, 3] = 0
# --- Private attributes ---
_backward_conversion_type: str = PrivateAttr(default=None)
_color_space: Literal['uvw01', 'xyY'] = PrivateAttr(default='xyY')
_complete: bool = PrivateAttr(default=False)
_dataset_map: dict = PrivateAttr(default_factory=dict)
_fct_name2process_name: dict = PrivateAttr(default_factory=dict)
_final_buffer: Optional[ImageListIO] = PrivateAttr(default=None)
_initial_buffer: Optional[ImageListIO] = PrivateAttr(default=None)
_initial_targets: Optional[Dict[str, np.ndarray]] = PrivateAttr(default={})
_is_first_operation: bool = PrivateAttr(default=True)
_is_last_operation: bool = PrivateAttr(default=False)
_iter_num: int = PrivateAttr(default=0)
_log_param: dict = PrivateAttr(default_factory=dict)
_lum_stats: List[np.ndarray] = PrivateAttr(default_factory=list)
_mode2processing_steps: dict = PrivateAttr(default_factory=dict)
_n_steps: int = PrivateAttr(default=0)
_processed_channel: Optional[int] = PrivateAttr(default=None)
_processed_image: Optional[str] = PrivateAttr(default=None)
_processing_function: Optional[str] = PrivateAttr(default=None)
_processing_steps: List[str] = PrivateAttr(default_factory=list)
_radius_grid: Optional[np.ndarray] = PrivateAttr(default=None)
_rec_standard: str = PrivateAttr(default="rec709")
_step: int = PrivateAttr(default=0)
_sum_bool_masks: List = PrivateAttr(default_factory=list)
_target_hist: Optional[np.ndarray] = PrivateAttr(default=None)
_target_lum: Optional[Tuple[Optional[float], Optional[float]]] = PrivateAttr(default=None)
_target_sf: Optional[np.ndarray] = PrivateAttr(default=None)
_target_spectrum: Optional[np.ndarray] = PrivateAttr(default=None)
Additional Resources๏
For a detailed description of the available options, see the
Optionsclass inOptions.py; each parameter lists its purpose, allowed values, and default.For algorithmic details and a walkthrough of processing steps, see the
ImageProcessorclass inImageProcessor.py.For color management and gamut-control strategies, see the
GamutControlclass incolor/GamutControl.py. Interactive visual examples are available at shinier-web examples.
Visualization Functions๏
These helpers are implemented in src/shinier/utils.py.
def hist_plot(
hist: np.ndarray,
bins: int = 256,
figsize: Optional[tuple] = None,
dpi=100,
title: Optional[str] = None,
target_hist: Optional[np.ndarray] = None,
descriptives: bool = False,
ax: Optional[plt.Axes] = None,
show_normalized_rmse: bool = False,
) -> Tuple[plt.Figure, Tuple[Any, Any]]:
"""Display a histogram with optional target overlay and descriptives (ฮผ, ยฑฯ)."""
def imhist_plot(
img: np.ndarray,
bins: int = 256,
figsize=(8, 6),
dpi=100,
title: Optional[str] = None,
target_hist: Optional[np.ndarray] = None,
binary_mask: Optional[np.ndarray] = None,
descriptives: bool = False,
ax: Optional[plt.Axes] = None,
show_normalized_rmse: bool = False,
) -> Tuple[plt.Figure, Tuple[Any, Any, Any]]:
"""Display an image with a compact histogram and optional descriptives (ฮผ, ยฑฯ).
Returns the figure and a tuple of axes: (image_ax, gradient_bar_ax, hist_ax).
"""
def sf_plot(
image: np.ndarray,
sf_p: Optional[np.ndarray] = None,
target_sf: Optional[np.ndarray] = None,
ax: Optional[plt.axis] = None,
show_normalized_rmse: bool = False,
) -> Union[plt.Figure, plt.Axes]:
"""Plot the rotational average (spatial-frequency profile) with optional target overlay."""
def spectrum_plot(
spectrum: np.ndarray,
cmap: str = "gray",
log: bool = True,
gamma: float = 1.0,
ax: Optional[plt.Axes] = None,
with_colorbar: bool = True,
colorbar_label: str = 'log(1 + |F|) (stretched)',
target_spectrum: Optional[np.ndarray] = None,
show_normalized_rmse: bool = False,
) -> Union[plt.Figure, plt.Axes]:
"""Display a Fourier magnitude spectrum with optional log/gamma scaling and target comparison."""
def im_power_spectrum_plot(im: np.ndarray, with_colorbar: bool = True) -> plt.Figure:
"""Display the centered 2D log-scaled Fourier power spectrum (grayscale/luminance).
- Converts RGB input to luminance (Rec.709) when needed and computes the power
spectrum |F|^2, then delegates rendering to `spectrum_plot`.
- Useful to visualize energy distribution across frequencies and orientations.
"""
def show_processing_overview(processor: ImageProcessor, img_idx: int = 0, show_figure: bool = True, show_initial_target: bool = False) -> plt.Figure:
"""Display before/after images and diagnostics for all processing steps in one figure.
The figure layout adapts to the active SHINIER mode:
โข Row 1: before/after images.
โข Subsequent rows: one row per processing step (e.g., luminance, histogram, spectrum).
Each diagnostic row shows "before" (left) and "after" (right) panels side by side.
Args:
processor (ImageProcessor): The SHINIER ImageProcessor instance.
img_idx (int, optional): Index of the image to visualize. Defaults to 0.
show_figure (bool): If False, return the fig object without showing it (i.e. plt.show())
show_initial_target (bool): If True, plots the initial target in composite modes.
Returns:
matplotlib.figure.Figure: Composite figure summarizing the image transformations.
"""
StimulusMasker๏
StimulusMasker is a utility class for creating elliptical masks and applying
them to images or image sets. It is useful when stimuli should be shown inside a
controlled region of interest while the outside area is replaced by a constant
user-defined background value.
Available mask types are:
"hard": binary ellipse with a sharp border."gaussian": hard ellipse with a Gaussian-smoothed border."feathered_disk": linear edge transition with an explicit width in pixels.
The interactive GUI is often the easiest way to choose the right cutoff and offset values because it shows the masked image live while sliders are adjusted.
from shinier import StimulusMasker
masker = StimulusMasker(
image_size=128,
cutoff_a=0.7,
mask_type="feathered_disk",
edge_width=3,
)
mask = masker.mask()
masked_image = masker.apply(image)
masked_images = masker.apply_all(stim_arr)
# Opens a Matplotlib GUI with sliders for cutoff, offset, and mask softness.
mask_from_gui = masker.interactive_mask(image)
๐งฎ Implemented Algorithms๏
1. Exact Histogram Specification๏
Algorithm:
Calculate cumulative distribution function (CDF) of source image
Calculate CDF of target histogram
Create mapping table based on CDFs
Apply mapping pixel by pixel
2. SSIM Optimization for Histogram๏
Algorithm:
Initial calculation of target histogram
Successive iterations with SSIM-based adjustment
Step size optimization for fast convergence
3. Floyd-Steinberg Dithering๏
Reference: Floyd, R. W., & Steinberg, L. (1976). An adaptive algorithm for spatial grey scale.
Algorithm:
Sequential image traversal (left to right, top to bottom)
Calculate quantization error for each pixel
Distribute error to neighboring pixels with different weights.
4. Noisy Bit Dithering๏
Algorithm:
Add controlled noise to each pixel
Quantize with rounding
Preserve overall image structure
๐พ Memory Management and Performance๏
Memory Conservation Mode (conserve_memory=True)๏
Operation:
Create temporary directory
/tmp/shinier-<pid>/Save images as
.npyformat in temporary directoryLoad only one image in memory at a time
Automatic cleanup at end of processing
Advantages:
Significant RAM usage reduction
Ability to process very large datasets
Automatic temporary file management
Implementation Code:
class ImageListIO:
def __init__(self, input_data, conserve_memory=True, ...):
if conserve_memory:
self._setup_temp_storage()
self._save_to_temp(input_data)
else:
self._load_all_images(input_data)
๐งช Testing and Validation๏
Unit Tests๏
Tested Components:
Options: Parameter validationImageListIO: Image loading/savingImageDataset: Collection managementImageProcessor: Image processingConverter: Luminance preservation and minimal chroma distortion.GamutControl: Chroma-loss minimization.
Test Images:
Noise-generated images for testing
Binary masks for figure-ground separation
Reference images for validation
MATLAB Validation๏
TODO
๐ Usage Examples๏
See
demos.ipynbin the documentation folder for:Coding usage
Interactive CLI usage
Examples in here have been intentionally minimized; please open the demos for more examples.
๐ง Troubleshooting and Optimization๏
Common Issues๏
1. Out-of-range values in luminance matching
# Solution: Enable safety mode
options = Options(
safe_lum_match=True, # Automatically adjusts parameters
mode=1
)
When using partial luminance targets, None keeps the original statistic for each image. For example,
target_lum=(None, 20) may reduce the requested contrast in safe mode if needed, but target_lum=(100, None)
will raise an error if the requested mean cannot be achieved safely without changing the original contrasts.
2. Excessive memory usage
# Solution: Enable memory conservation
options = Options(
conserve_memory=True, # Load one image at a time
mode=8
)
3. Results different from MATLAB
# Solution: Enable legacy mode
options = Options(
legacy_mode=True, # Uses exact MATLAB algorithms
mode=8
)
Recommendations:
The SHINIER, the better. Legacy doesnโt mean itโs ideal.
4. Composite modes (5-8) not achieving perfect matching for both histogram and Fourier simultaneously
# Solution: Increase iterations for composite modes
options = Options(
mode=8, # Spectrum + Histogram
iterations=5,
)
Scientific Rationale: Composite modes (5-8) apply two sequential transformations (e.g., spectrum matching followed by histogram matching). Because each transformation modifies the image in ways that can partially undo the effects of the other, a single pass rarely yields convergence. As detailed in the original SHINE documentation, iterative application of both steps allows the algorithm to progressively minimize residual discrepancies between the desired luminance distribution and spectral amplitude structure.
Sequential Processing: Each cycle compensates for the distortions introduced by the preceding transformation (e.g., histogram adjustment altering spectral power).
Convergence: Repeated alternation drives both properties toward their joint target values.
Iterative Refinement: After several iterations (typically 5), the process reaches a stable equilibrium where further refinement yields negligible improvement.
Code developed by Nicolas Dupuis-Roy and Mathias Salvas-Hรฉbert
Version 0.2.0 - Complete technical documentation