shinier 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. shinier-0.1.2/INPUT/test_face1.png +0 -0
  2. shinier-0.1.2/INPUT/test_face2.png +0 -0
  3. shinier-0.1.2/INPUT/test_face3.png +0 -0
  4. shinier-0.1.2/INPUT/test_face4.png +0 -0
  5. shinier-0.1.2/INPUT/test_face5.png +0 -0
  6. shinier-0.1.2/INPUT/test_scene1.png +0 -0
  7. shinier-0.1.2/INPUT/test_scene2.png +0 -0
  8. shinier-0.1.2/INPUT/test_scene3.png +0 -0
  9. shinier-0.1.2/INPUT/test_scene4.png +0 -0
  10. shinier-0.1.2/INPUT/test_scene5.png +0 -0
  11. shinier-0.1.2/LICENSE +21 -0
  12. shinier-0.1.2/MANIFEST.in +11 -0
  13. shinier-0.1.2/MASK/mask.png +0 -0
  14. shinier-0.1.2/OUTPUT/.gitkeep +0 -0
  15. shinier-0.1.2/PKG-INFO +608 -0
  16. shinier-0.1.2/README.md +565 -0
  17. shinier-0.1.2/assets/SHINIER_logo.jpg +0 -0
  18. shinier-0.1.2/documentation/Controlling low-level image properties: The SHINE toolbox.pdf +0 -0
  19. shinier-0.1.2/documentation/Example_usage.ipynb +944 -0
  20. shinier-0.1.2/documentation/The SHINIER the Better_ An Adaptation of the SHINE Toolbox on Python.docx +0 -0
  21. shinier-0.1.2/pyproject.toml +32 -0
  22. shinier-0.1.2/pytest.ini +10 -0
  23. shinier-0.1.2/requirements.txt +5 -0
  24. shinier-0.1.2/setup.py +83 -0
  25. shinier-0.1.2/src/shinier/ImageDataset.py +137 -0
  26. shinier-0.1.2/src/shinier/ImageListIO.py +469 -0
  27. shinier-0.1.2/src/shinier/ImageProcessor.py +784 -0
  28. shinier-0.1.2/src/shinier/Options.py +340 -0
  29. shinier-0.1.2/src/shinier/SHINIER.py +350 -0
  30. shinier-0.1.2/src/shinier/SHINIER_r1.py +359 -0
  31. shinier-0.1.2/src/shinier/__init__.py +46 -0
  32. shinier-0.1.2/src/shinier/_cconvolve.cpp +32286 -0
  33. shinier-0.1.2/src/shinier/_cconvolve.pyx +161 -0
  34. shinier-0.1.2/src/shinier/utils.py +2394 -0
  35. shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_face1.png +0 -0
  36. shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_face2.png +0 -0
  37. shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_face3.png +0 -0
  38. shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_face4.png +0 -0
  39. shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_face5.png +0 -0
  40. shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_scene1.png +0 -0
  41. shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_scene2.png +0 -0
  42. shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_scene3.png +0 -0
  43. shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_scene4.png +0 -0
  44. shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_scene5.png +0 -0
  45. shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_face1.png +0 -0
  46. shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_face2.png +0 -0
  47. shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_face3.png +0 -0
  48. shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_face4.png +0 -0
  49. shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_face5.png +0 -0
  50. shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_scene1.png +0 -0
  51. shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_scene2.png +0 -0
  52. shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_scene3.png +0 -0
  53. shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_scene4.png +0 -0
  54. shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_scene5.png +0 -0
  55. shinier-0.1.2/tests/README.md +169 -0
  56. shinier-0.1.2/tests/__init__.py +2 -0
  57. shinier-0.1.2/tests/compiled_convolution_tests.py +32 -0
  58. shinier-0.1.2/tests/conftest.py +113 -0
  59. shinier-0.1.2/tests/manual_test.py +91 -0
  60. shinier-0.1.2/tests/tools/replay_failure.py +150 -0
  61. shinier-0.1.2/tests/tools/utils.py +319 -0
  62. shinier-0.1.2/tests/unit_tests/ImageDataset_test.py +157 -0
  63. shinier-0.1.2/tests/unit_tests/ImageListIO_test.py +140 -0
  64. shinier-0.1.2/tests/unit_tests/ImageProcessor_test.py +142 -0
  65. shinier-0.1.2/tests/unit_tests/Options_test.py +198 -0
  66. shinier-0.1.2/tests/validation_tests/ImageProcessor_validation_test.py +384 -0
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
shinier-0.1.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [year] [your name]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,11 @@
1
+ # --- Core extension handling ---
2
+ include src/shinier/_cconvolve.cpp
3
+ exclude src/shinier/_cconvolve.pyx
4
+
5
+ # --- Documentation and assets ---
6
+ recursive-include assets *
7
+ recursive-include documentation *
8
+ recursive-include INPUT *
9
+ recursive-include MASK *
10
+
11
+ include OUTPUT/.gitkeep
Binary file
File without changes
shinier-0.1.2/PKG-INFO ADDED
@@ -0,0 +1,608 @@
1
+ Metadata-Version: 2.4
2
+ Name: shinier
3
+ Version: 0.1.2
4
+ Summary: Python port of the SHINE toolbox with added options (color mgmt, dithering, EHS), optimized for large image sets.
5
+ Project-URL: Homepage, https://github.com/Charestlab/shinier
6
+ Project-URL: Issues, https://github.com/Charestlab/shinier/issues
7
+ Project-URL: Source, https://github.com/Charestlab/shinier
8
+ Author: CharestLab
9
+ License: MIT License
10
+
11
+ Copyright (c) [year] [your name]
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: SHINE,equalization,images,psychophysics,vision
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Operating System :: OS Independent
34
+ Classifier: Programming Language :: Python :: 3
35
+ Requires-Python: >=3.9
36
+ Requires-Dist: cython>=3.0
37
+ Requires-Dist: imageio>=2.22
38
+ Requires-Dist: matplotlib>=3.9
39
+ Requires-Dist: numpy>=1.23
40
+ Requires-Dist: scipy>=1.10
41
+ Requires-Dist: tqdm>=4.66
42
+ Description-Content-Type: text/markdown
43
+
44
+ # 🌟 SHINIER - Complete Technical Documentation
45
+
46
+ <p align="center">
47
+ <img src="https://github.com/Charestlab/shinier/blob/main/assets/SHINIER_logo.jpg" width="400" alt="SHINIER Logo">
48
+ </p>
49
+
50
+ ## 📋 Table of Contents
51
+
52
+ 1. [Overview](#overview)
53
+ 2. [Package Architecture](#package-architecture)
54
+ 3. [MATLAB vs Python Differences](#matlab-vs-python-differences)
55
+ 4. [Detailed Processing Modes](#detailed-processing-modes)
56
+ 5. [Main Classes](#main-classes)
57
+ 6. [Visualization Functions](#visualization-functions)
58
+ 7. [Implemented Algorithms](#implemented-algorithms)
59
+ 8. [Memory Management and Performance](#memory-management-and-performance)
60
+ 9. [Testing and Validation](#testing-and-validation)
61
+
62
+ ---
63
+
64
+ ## 🎯 Overview
65
+
66
+ **SHINIER** is a modern Python implementation of the **SHINE** (Spectrum, Histogram, and Intensity Normalization and Equalization) toolbox, originally developed in MATLAB by [Willenbockel et al. (2010)](https://doi.org/10.3758/BRM.42.3.671). This new version implemented new options (e.g., color management, dithering and [Coltuc, Bolon & Chassery (2006)](https://www.cin.ufpe.br/~if751/projetos/artigos/Exact%20Histogram%20Specification.pdf) exact histogram specification algorithm) and refined the previous ones.
67
+
68
+ **Reference** : [Willenbockel, V., Sadr, J., Fiset, D., Horne, G. O., Gosselin, F., & Tanaka, J. W. (2010). Controlling low-level image properties: The SHINE toolbox. *Behavior Research Methods*, 42(3), 671-684.](https://doi.org/10.3758/BRM.42.3.671)
69
+
70
+ ### Main Objectives
71
+ - **Compatibility**: Maintain compatibility with the original MATLAB implementation
72
+ - **Performance**: Optimize performance for large datasets
73
+ - **Extensibility**: Object-oriented architecture for easy extensions
74
+ - **Precision**: Reduce rounding errors in multi-step calculations
75
+
76
+ ---
77
+
78
+ ## 🏗️ Package Architecture
79
+
80
+ ### Module Structure
81
+
82
+ ```
83
+ shinier/
84
+ ├── __init__.py # Package entry point
85
+ ├── Options.py # Parameter configuration
86
+ ├── ImageDataset.py # Image collection management
87
+ ├── ImageListIO.py # Image file input/output
88
+ ├── ImageProcessor.py # Main image processing
89
+ ├── SHINIER.py # Command-line interface
90
+ └── utils.py # Utility functions and MATLAB operators
91
+ ```
92
+
93
+ ### Processing Flow
94
+
95
+ ```mermaid
96
+ graph TD
97
+ A[Options] --> B[ImageDataset]
98
+ B --> C[ImageProcessor]
99
+ C --> D[Mode-based Processing]
100
+ D --> E[Final Conversion]
101
+ E --> F[Output Images]
102
+ ```
103
+
104
+ ---
105
+
106
+ ## 🔬 MATLAB vs Python Differences
107
+
108
+ ### 1. **Rounding Operators**
109
+
110
+ #### MATLAB `round()`
111
+ ```matlab
112
+ >> round([2.5, 3.5, -2.5, -3.5])
113
+ ans = [3, 4, -3, -4]
114
+ ```
115
+ - Rounds **away from zero** (round-away-from-zero)
116
+ - Deterministic behavior for values exactly halfway
117
+
118
+ #### Python `numpy.round()` - IEEE 754 Standard
119
+ ```python
120
+ >>> np.round([2.5, 3.5, -2.5, -3.5])
121
+ array([2., 4., -2., -4.])
122
+ ```
123
+ - Rounds **to nearest even** (round-half-to-even, "Bankers' Rounding")
124
+ - **IEEE 754-2019 Standard** compliant (recommended default for binary formats)
125
+ - **Statistically unbiased** for large datasets
126
+ - **Reduces cumulative rounding errors** in iterative computations
127
+
128
+ #### SHINIER Solution - Compatibility vs Standards
129
+ ```python
130
+ class MatlabOperators:
131
+ @staticmethod
132
+ def round(x):
133
+ """Simulates MATLAB's rounding behavior for compatibility"""
134
+ return np.sign(x) * np.ceil(np.floor(np.abs(x) * 2) / 2)
135
+ ```
136
+
137
+ **Scientific Rationale:**
138
+
139
+ While SHINIER provides MATLAB-compatible rounding for **legacy compatibility**, the **IEEE 754-2019 standard** recommends round-half-to-even because:
140
+
141
+ 1. **Statistical Unbiasedness**: Round-half-to-even produces unbiased results when rounding large datasets
142
+ 2. **Error Minimization**: Reduces cumulative rounding errors in iterative algorithms
143
+ 3. **Industry Standard**: Used by most modern computing systems (Python, R, Julia, etc.)
144
+ 4. **Numerical Stability**: Better performance in floating-point arithmetic
145
+
146
+ **References:**
147
+ - [**IEEE 754-2019**](https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=8766229&tag=1): "IEEE Standard for Floating-Point Arithmetic"
148
+ - [**Goldberg, D. (1991)**](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html): "What Every Computer Scientist Should Know About Floating-Point Arithmetic"
149
+ - [**Kahan, W. (1996)**](https://people.eecs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF): "IEEE Standard 754 for Binary Floating-Point Arithmetic"
150
+
151
+ **Recommendation**: Use `legacy_mode=False` (default) for scientifically robust results, `legacy_mode=True` only for MATLAB compatibility validation.
152
+
153
+ ### 2. **Integer Type Conversion**
154
+
155
+ #### MATLAB `uint8()`
156
+ ```matlab
157
+ >> uint8([2.5, 3.5, -2.5, 255.5])
158
+ ans = [3, 4, 0, 255]
159
+ ```
160
+ - Rounds to nearest integer
161
+ - **Clips** values between [0, 255]
162
+
163
+ #### Python `numpy.astype('uint8')`
164
+ ```python
165
+ >>> np.array([2.5, 3.5, -2.5, 256, 257]).astype('uint8')
166
+ array([2, 3, 254, 0, 1], dtype=uint8)
167
+ ```
168
+ - **Truncates** decimal values
169
+ - **Wrap-around** behavior for out-of-range values
170
+
171
+ #### SHINIER Solution
172
+ ```python
173
+ @staticmethod
174
+ def uint8(x):
175
+ """Replicates MATLAB's uint8 behavior"""
176
+ return np.uint8(np.clip(MatlabOperators.round(x), 0, 255))
177
+ ```
178
+
179
+ ### 3. **Standard Deviation Calculation**
180
+
181
+ #### MATLAB `std2()`
182
+ ```matlab
183
+ >> A = rand(100, 100);
184
+ >> std2(A)
185
+ ans = 0.287630126526993
186
+ ```
187
+ - Uses **N-1** divisor (unbiased estimator)
188
+
189
+ #### Python `numpy.std()` - Statistical Best Practice
190
+ ```python
191
+ >>> A = np.random.rand(100, 100)
192
+ >>> np.std(A)
193
+ 0.28761574466111084
194
+ ```
195
+ - Uses **N** divisor (biased estimator) by default
196
+ - **Population standard deviation** (mathematically correct for complete populations)
197
+ - **Consistent with most statistical software** (R, Julia, etc.)
198
+
199
+ #### SHINIER Solution - Scientific Flexibility
200
+ ```python
201
+ @staticmethod
202
+ def std2(x):
203
+ """Replicates MATLAB's std2 function for compatibility"""
204
+ return np.std(x, ddof=1) # ddof=1 for N-1 (sample std)
205
+ ```
206
+
207
+ **Statistical Rationale:**
208
+
209
+ The choice between N and N-1 divisors depends on the **statistical context**:
210
+
211
+ - **N divisor**: Population standard deviation (when you have the complete population)
212
+ - **N-1 divisor**: Sample standard deviation (when estimating population from sample)
213
+
214
+ **SHINIER Approach**: Provides both options through `ddof` parameter, allowing users to choose based on their statistical requirements.
215
+
216
+ ### 4. **RGB to Grayscale Conversion**
217
+
218
+ #### MATLAB `rgb2gray()` - Legacy Standard
219
+ ```matlab
220
+ % Uses Rec.ITU-R BT.601-7 (SD monitors)
221
+ Y' = 0.299 * R + 0.587 * G + 0.114 * B
222
+ ```
223
+ - **Rec.ITU-R BT.601-7**: Standard-Definition television (1982)
224
+ - **Legacy standard** optimized for CRT monitors
225
+
226
+ #### SHINIER `rgb2gray()` - Modern Standards Support
227
+ ```python
228
+ def rgb2gray(image, recommendation='rec709'):
229
+ """RGB to grayscale conversion with multiple luma coefficient standards"""
230
+ rgb_luma_coefficients = {
231
+ 'equal': [0.333, 0.333, 0.333], # Equal weighting (not perceptually accurate)
232
+ 'rec601': [0.298936021293775, 0.587043074451121, 0.114020904255103], # SD legacy
233
+ 'rec709': [0.2126, 0.7152, 0.0722], # HD standard (recommended)
234
+ 'rec2020': [0.2627, 0.6780, 0.0593] # UHD standard
235
+ }
236
+ weights = rgb_luma_coefficients[recommendation]
237
+ return np.dot(image.astype(np.float64), weights)
238
+ ```
239
+
240
+ **Scientific Rationale:**
241
+
242
+ Different luma coefficients are optimized for different **display technologies** and **viewing conditions**:
243
+
244
+ - **Rec.ITU-R BT.601**: **Legacy compatibility** for SD displays
245
+ - **Rec.ITU-R BT.709**: **Recommended default** for modern displays (HD, 4K)
246
+ - **Rec.ITU-R BT.2020**: **Future-proof** for UHD/HDR displays
247
+
248
+ **References:**
249
+ - [**Poynton, Charles (1997)**](https://poynton.ca/PDFs/ColorFAQ.pdf): "Frequently Asked Questions about Color"
250
+ - [**ITU-R BT.601-7 (2011)**](https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.601-7-201103-I!!PDF-E.pdf): "Studio encoding parameters of digital television for standard 4:3 and wide-screen 16:9 aspect ratios"
251
+ - [**ITU-R BT.709-6 (2015)**](https://www.itu.int/dms_pubrec/itu-r/rec/bt/r-rec-bt.709-6-201506-i!!pdf-e.pdf): "Parameter values for the HDTV standards for production and international programme exchange"
252
+ - [**ITU-R BT.2020-2 (2015)**](https://www.itu.int/dms_pubrec/itu-r/rec/bt/r-rec-bt.2020-2-201510-i!!pdf-e.pdf): "Parameter values for ultra-high definition television systems"
253
+
254
+ **SHINIER Advantage**: Provides **multiple standards** allowing users to choose the most appropriate for their display technology and research context.
255
+
256
+ **WARNING AND REMINDER**: Ajusting for the luminance transfer functions implemented in image-capturing devices
257
+ and the precise calibration of display monitors are essential for accurate visual stimuli presentation.
258
+
259
+ ---
260
+
261
+ ## ⚙️ Detailed Processing Modes
262
+
263
+ ### Mode 1: Luminance Matching Only
264
+ ```python
265
+ mode = 1 # lum_match only
266
+ ```
267
+ **Algorithm:**
268
+ - If `target_lum` is None or equals `(0, 0)`: compute target parameters from the dataset
269
+ - `mean` = average of image means
270
+ - `std` = average of image standard deviations
271
+ - Otherwise (when `target_lum=(mean, std)` is provided): use it directly as the target
272
+ - Apply linear rescaling per image: `new_pixel = (pixel - mean) * (target_std/std) + target_mean`
273
+
274
+ **Specific Parameters:**
275
+ - `target_lum`: Optional tuple `(mean, std)` where `mean ∈ [0, 255]` and `std ∈ [0, +∞)`. If omitted or `(0, 0)`, dataset averages are used
276
+ - `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)
277
+
278
+ ### Mode 2: Histogram Matching Only
279
+ ```python
280
+ mode = 2 # hist_match only
281
+ ```
282
+ **Available Algorithms:**
283
+ - **Exact specification** (`hist_specification=0`): [Coltuc, Bolon & Chassery (2006)]((https://www.cin.ufpe.br/~if751/projetos/artigos/Exact%20Histogram%20Specification.pdf)) algorithm
284
+ - **Specification with noise** (`hist_specification=1`): Legacy version with noise addition
285
+
286
+ **SSIM Optimization:**
287
+ - `hist_optim=1`: SSIM-based optimization ([Avanaki, 2009](https://link.springer.com/article/10.1007/s10043-009-0119-z)))
288
+ - `hist_iterations`: Number of iterations (default: 10)
289
+ - `step_size`: Step size (default: 34)
290
+
291
+ ### Mode 3: Spatial Frequency Matching Only
292
+ ```python
293
+ mode = 3 # sf_match only
294
+ ```
295
+ **Match spatial frequencies of input images to a target rotational spectrum.**
296
+
297
+ **Algorithm (summary):**
298
+ 1) Convert images to float [0,255] (H×W×C) and prepare optional masks/bit-depth.
299
+ 2) Build the target rotational magnitude spectrum (provided or averaged from inputs).
300
+ 3) For each image, compute FFT, replace the radial magnitude profile with the target while preserving phase, then inverse FFT.
301
+ 4) Apply rescaling per `options.rescaling` (0 none, 1 per-image, 2 global, 3 average) and clip to valid range.
302
+ 5) Store float255 outputs and optionally log/plot spectral diagnostics.
303
+
304
+ ---
305
+
306
+ ### Mode 4: Spectrum Matching Only
307
+ ```python
308
+ mode = 4 # spec_match only
309
+ ```
310
+ **Match the full magnitude spectrum of images to a target spectrum (2D magnitude, not only rotational average).**
311
+
312
+ **Algorithm (summary):**
313
+ 1) Convert images to float [0,255] (H×W×C) and consider masks if provided.
314
+ 2) Build the target 2D magnitude spectrum (provided or element-wise average of input magnitudes).
315
+ 3) For each image, compute FFT, replace the full magnitude by the target while preserving phase, then inverse FFT.
316
+ 4) Apply rescaling per `options.rescaling` and clip to the valid intensity range.
317
+ 5) Store float255 outputs and optionally log or visualize spectrum-related diagnostics.
318
+
319
+ ### Composite Modes (5-8)
320
+
321
+ #### Mode 5: Histogram + Spatial Frequency
322
+ ```python
323
+ mode = 5 # hist_match → sf_match
324
+ ```
325
+
326
+ #### Mode 6: Histogram + Spectrum
327
+ ```python
328
+ mode = 6 # hist_match → spec_match
329
+ ```
330
+
331
+ #### Mode 7: Spatial Frequency + Histogram
332
+ ```python
333
+ mode = 7 # sf_match → hist_match
334
+ ```
335
+
336
+ #### Mode 8: Spectrum + Histogram (Recommended)
337
+ ```python
338
+ mode = 8 # spec_match → hist_match
339
+ ```
340
+
341
+ **High Numerical Precision:**
342
+ - Composite modes use temporary floating-point precision
343
+ - Reduces rounding errors in multi-step calculations
344
+
345
+ ### Mode 9: Dithering Only
346
+ ```python
347
+ mode = 9 # only dithering
348
+ ```
349
+
350
+ ---
351
+
352
+ ## 🏛️ Main Classes
353
+
354
+ ### `Options`
355
+ Centralized configuration class for all processing parameters.
356
+
357
+ ```python
358
+ class Options:
359
+ def __init__(
360
+ # Folders and formats
361
+ images_format: str = 'png',
362
+ input_folder: Union[str, Path] = Path('./../INPUT'),
363
+ output_folder: Union[str, Path] = Path('./../OUTPUT'),
364
+
365
+ # Masks and figure-ground separation
366
+ masks_format: str = 'png',
367
+ masks_folder: Optional[Union[str, Path]] = None,
368
+ whole_image: Literal[1, 2, 3] = 1,
369
+ background: Union[int, float] = 300,
370
+
371
+ # General options
372
+ mode: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9] = 8,
373
+ as_gray: Literal[0, 1, 2, 3, 4] = 0,
374
+ dithering: Literal[0, 1, 2] = 1,
375
+ conserve_memory: bool = True,
376
+ seed: Optional[int] = None,
377
+ legacy_mode: bool = False,
378
+
379
+ iterations: int = 2,
380
+
381
+ # Processing mode
382
+ mode: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9] = 8,
383
+
384
+ # Luminance matching
385
+ safe_lum_match: bool = False,
386
+ target_lum: Optional[Iterable[Union[int, float]]] = (0, 0),
387
+
388
+ # Histogram matching
389
+ hist_specification: Literal[0, 1] = 0,
390
+ hist_optim: Literal[0, 1] = 0,
391
+ hist_iterations: int = 10,
392
+ step_size: int = 34,
393
+ target_hist: Optional[np.ndarray] = None,
394
+
395
+ # Fourier matching
396
+ rescaling: Optional[Literal[0, 1, 2, 3]] = 2,
397
+ target_spectrum: Optional[np.ndarray] = None
398
+ ):
399
+ ```
400
+
401
+ ### `ImageDataset`
402
+ Management of image and mask collections with state tracking.
403
+
404
+ ```python
405
+ class ImageDataset:
406
+ def __init__(
407
+ self,
408
+ images: ImageListType = None,
409
+ masks: ImageListType = None,
410
+ options: Optional[Options] = None
411
+ ):
412
+ self.images: ImageListIO # Image collection
413
+ self.masks: ImageListIO # Mask collection
414
+ self.n_images: int # Number of images
415
+ self.n_masks: int # Number of masks
416
+ self.processing_logs: List # Processing log
417
+ self.options: Options # Configuration options
418
+ ```
419
+
420
+ ### `ImageProcessor`
421
+ Main image processing class.
422
+
423
+ ```python
424
+ class ImageProcessor:
425
+ def __init__(
426
+ self,
427
+ dataset: ImageDataset,
428
+ options: Optional[Options] = None,
429
+ verbose: Literal[0, 1, 2] = 0
430
+ ):
431
+ self.dataset: ImageDataset
432
+ self.options: Options
433
+ self.verbose: Literal[0, 1, 2]
434
+ self.log: List # Processing log
435
+ self.validation: List # Validation results
436
+ ```
437
+
438
+ ---
439
+
440
+ ## Visualization Functions
441
+
442
+ ```python
443
+ def imhist_plot(img, bins=256, figsize=(8, 6)):
444
+ """Image histogram plotting"""
445
+
446
+ def spectrum_plot(spectrum, figsize=(10, 8)):
447
+ """Magnitude spectrum plotting"""
448
+
449
+ def sf_plot(sf_profile, figsize=(8, 6)):
450
+ """Spatial frequency profile plotting"""
451
+ ```
452
+
453
+ ---
454
+
455
+ ## 🧮 Implemented Algorithms
456
+
457
+ ### 1. Exact Histogram Specification
458
+ **Reference:** [Coltuc, D., Bolon, P., & Chassery, J. M. (2006). Exact histogram specification. *IEEE Transactions on Image Processing*, 15(5), 1143-1152.](https://www.cin.ufpe.br/~if751/projetos/artigos/Exact%20Histogram%20Specification.pdf)
459
+
460
+ **Algorithm:**
461
+ 1. Calculate cumulative distribution function (CDF) of source image
462
+ 2. Calculate CDF of target histogram
463
+ 3. Create mapping table based on CDFs
464
+ 4. Apply mapping pixel by pixel
465
+
466
+ ### 2. SSIM Optimization for Histogram
467
+ **Reference:** [Avanaki, A. N. (2009). Exact histogram specification for digital images using a variational approach. *Journal of Visual Communication and Image Representation*, 20(7), 505-515.](https://link.springer.com/article/10.1007/s10043-009-0119-z)
468
+
469
+ **Algorithm:**
470
+ 1. Initial calculation of target histogram
471
+ 2. Successive iterations with SSIM-based adjustment
472
+ 3. Step size optimization for fast convergence
473
+
474
+ ### 3. Floyd-Steinberg Dithering
475
+ **Reference:** Floyd, R. W., & Steinberg, L. (1976). An adaptive algorithm for spatial grey scale.
476
+
477
+ **Algorithm:**
478
+ 1. Sequential image traversal (left to right, top to bottom)
479
+ 2. Calculate quantization error for each pixel
480
+ 3. Distribute error to neighboring pixels with different weights.
481
+
482
+ ### 4. Noisy Bit Dithering
483
+ **Reference:** [Allard, R., & Faubert, J. (2008). The noisy-bit method for digital halftoning. *Journal of the Optical Society of America A*, 25(8), 1980-1989.](https://link.springer.com/article/10.3758/BRM.40.3.735)
484
+
485
+ **Algorithm:**
486
+ 1. Add controlled noise to each pixel
487
+ 2. Quantize with rounding
488
+ 3. Preserve overall image structure
489
+
490
+ ---
491
+
492
+ ## 💾 Memory Management and Performance
493
+
494
+ ### Memory Conservation Mode (`conserve_memory=True`)
495
+
496
+ **Operation:**
497
+ 1. Create temporary directory `/tmp/shinier-<pid>/`
498
+ 2. Save images as `.npy` format in temporary directory
499
+ 3. Load only one image in memory at a time
500
+ 4. Automatic cleanup at end of processing
501
+
502
+ **Advantages:**
503
+ - Significant RAM usage reduction
504
+ - Ability to process very large datasets
505
+ - Automatic temporary file management
506
+
507
+ **Implementation Code:**
508
+ ```python
509
+ class ImageListIO:
510
+ def __init__(self, input_data, conserve_memory=True, ...):
511
+ if conserve_memory:
512
+ self._setup_temp_storage()
513
+ self._save_to_temp(input_data)
514
+ else:
515
+ self._load_all_images(input_data)
516
+ ```
517
+
518
+ ---
519
+
520
+ ## 🧪 Testing and Validation
521
+
522
+ ### Unit Tests
523
+
524
+ **Tested Components:**
525
+ - `Options`: Parameter validation
526
+ - `ImageListIO`: Image loading/saving
527
+ - `ImageDataset`: Collection management
528
+ - `ImageProcessor`: Image processing
529
+
530
+ **Test Images:**
531
+ - Noise-generated images for testing
532
+ - Binary masks for figure-ground separation
533
+ - Reference images for validation
534
+
535
+ ### MATLAB Validation
536
+
537
+ **TODO**
538
+
539
+ ---
540
+
541
+ ## 📚 Usage Examples
542
+
543
+ - [See](documentation/Example_usage.ipynb) `Example_usage.ipynb` [in the documentation folder for](documentation/Example_usage.ipynb):
544
+ - Coding usage
545
+ - Interactive CLI usage
546
+
547
+ Examples in this README have been intentionally minimized; please open the notebook for complete, executable code.
548
+
549
+ ---
550
+
551
+ ## 🔧 Troubleshooting and Optimization
552
+
553
+ ### Common Issues
554
+
555
+ **1. Out-of-range values in luminance matching**
556
+ ```python
557
+ # Solution: Enable safety mode
558
+ options = Options(
559
+ safe_lum_match=True, # Automatically adjusts parameters
560
+ mode=1
561
+ )
562
+ ```
563
+
564
+ **2. Excessive memory usage**
565
+ ```python
566
+ # Solution: Enable memory conservation
567
+ options = Options(
568
+ conserve_memory=True, # Load one image at a time
569
+ mode=8
570
+ )
571
+ ```
572
+
573
+ **3. Results different from MATLAB**
574
+ ```python
575
+ # Solution: Enable legacy mode
576
+ options = Options(
577
+ legacy_mode=True, # Uses exact MATLAB algorithms
578
+ mode=8
579
+ )
580
+ ```
581
+ **Recommendations:**
582
+ - **The SHINIER, the better. Legacy doesn't mean it's ideal.**
583
+
584
+ **4. Composite modes (5-8) not achieving perfect matching for both histogram and Fourier simultaneously**
585
+ ```python
586
+ # Solution: Increase iterations for composite modes
587
+ options = Options(
588
+ mode=8, # Spectrum + Histogram
589
+ iterations=5, # Increase from default 2 for better dual matching
590
+ )
591
+ ```
592
+
593
+ **Scientific Rationale:**
594
+ 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](documentation/Controlling%20low-level%20image%20properties:%20The%20SHINE%20toolbox.pdf), **iterative application** of both steps allows the algorithm to progressively minimize residual discrepancies between the desired luminance distribution and spectral amplitude structure.
595
+
596
+ 1. **Sequential Processing**: Each cycle compensates for the distortions introduced by the preceding transformation (e.g., histogram adjustment altering spectral power).
597
+ 2. **Convergence**: Repeated alternation drives both properties toward their joint target values.
598
+ 3. **Iterative Refinement**: After several iterations (typically 5), the process reaches a stable equilibrium where further refinement yields negligible improvement.
599
+
600
+ **Recommendations:**
601
+ - **Target histogram and spectrum**: Avoid providing explicit target histograms or spectra. As explained in the [SHINE article](documentation/Controlling%20low-level%20image%20properties:%20The%20SHINE%20toolbox.pdf), the algorithm is designed to automatically compute the mean histogram and spectrum across all input images at each iteration, ensuring that the matching process remains adaptive and consistent.
602
+
603
+ ---
604
+
605
+ <p align="center">
606
+ <strong>Code developed by Nicolas Dupuis-Roy and Mathias Salvas-Hébert </strong><br>
607
+ <em>Version 0.1.0 - Complete technical documentation</em>
608
+ </p>