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.
- shinier-0.1.2/INPUT/test_face1.png +0 -0
- shinier-0.1.2/INPUT/test_face2.png +0 -0
- shinier-0.1.2/INPUT/test_face3.png +0 -0
- shinier-0.1.2/INPUT/test_face4.png +0 -0
- shinier-0.1.2/INPUT/test_face5.png +0 -0
- shinier-0.1.2/INPUT/test_scene1.png +0 -0
- shinier-0.1.2/INPUT/test_scene2.png +0 -0
- shinier-0.1.2/INPUT/test_scene3.png +0 -0
- shinier-0.1.2/INPUT/test_scene4.png +0 -0
- shinier-0.1.2/INPUT/test_scene5.png +0 -0
- shinier-0.1.2/LICENSE +21 -0
- shinier-0.1.2/MANIFEST.in +11 -0
- shinier-0.1.2/MASK/mask.png +0 -0
- shinier-0.1.2/OUTPUT/.gitkeep +0 -0
- shinier-0.1.2/PKG-INFO +608 -0
- shinier-0.1.2/README.md +565 -0
- shinier-0.1.2/assets/SHINIER_logo.jpg +0 -0
- shinier-0.1.2/documentation/Controlling low-level image properties: The SHINE toolbox.pdf +0 -0
- shinier-0.1.2/documentation/Example_usage.ipynb +944 -0
- shinier-0.1.2/documentation/The SHINIER the Better_ An Adaptation of the SHINE Toolbox on Python.docx +0 -0
- shinier-0.1.2/pyproject.toml +32 -0
- shinier-0.1.2/pytest.ini +10 -0
- shinier-0.1.2/requirements.txt +5 -0
- shinier-0.1.2/setup.py +83 -0
- shinier-0.1.2/src/shinier/ImageDataset.py +137 -0
- shinier-0.1.2/src/shinier/ImageListIO.py +469 -0
- shinier-0.1.2/src/shinier/ImageProcessor.py +784 -0
- shinier-0.1.2/src/shinier/Options.py +340 -0
- shinier-0.1.2/src/shinier/SHINIER.py +350 -0
- shinier-0.1.2/src/shinier/SHINIER_r1.py +359 -0
- shinier-0.1.2/src/shinier/__init__.py +46 -0
- shinier-0.1.2/src/shinier/_cconvolve.cpp +32286 -0
- shinier-0.1.2/src/shinier/_cconvolve.pyx +161 -0
- shinier-0.1.2/src/shinier/utils.py +2394 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_face1.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_face2.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_face3.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_face4.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_face5.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_scene1.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_scene2.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_scene3.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_scene4.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_512X512/test_scene5.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_face1.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_face2.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_face3.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_face4.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_face5.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_scene1.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_scene2.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_scene3.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_scene4.png +0 -0
- shinier-0.1.2/tests/IMAGES/SAMPLE_64X64/test_scene5.png +0 -0
- shinier-0.1.2/tests/README.md +169 -0
- shinier-0.1.2/tests/__init__.py +2 -0
- shinier-0.1.2/tests/compiled_convolution_tests.py +32 -0
- shinier-0.1.2/tests/conftest.py +113 -0
- shinier-0.1.2/tests/manual_test.py +91 -0
- shinier-0.1.2/tests/tools/replay_failure.py +150 -0
- shinier-0.1.2/tests/tools/utils.py +319 -0
- shinier-0.1.2/tests/unit_tests/ImageDataset_test.py +157 -0
- shinier-0.1.2/tests/unit_tests/ImageListIO_test.py +140 -0
- shinier-0.1.2/tests/unit_tests/ImageProcessor_test.py +142 -0
- shinier-0.1.2/tests/unit_tests/Options_test.py +198 -0
- 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>
|