onepix-fis-common-function 0.1.0__py3-none-any.whl
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.
- fis_common_function/CIE1931-2deg-XYZ.csv +95 -0
- fis_common_function/FIS_common_acquisition.py +34 -0
- fis_common_function/FIS_common_analysis.py +656 -0
- fis_common_function/FIS_common_param.json +3 -0
- fis_common_function/FIS_common_reconstruction.py +213 -0
- fis_common_function/__init__.py +7 -0
- onepix_fis_common_function-0.1.0.dist-info/METADATA +106 -0
- onepix_fis_common_function-0.1.0.dist-info/RECORD +11 -0
- onepix_fis_common_function-0.1.0.dist-info/WHEEL +5 -0
- onepix_fis_common_function-0.1.0.dist-info/entry_points.txt +2 -0
- onepix_fis_common_function-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
360,0.000129900000,0.000003917000,0.000606100000
|
|
2
|
+
365,0.000232100000,0.000006965000,0.001086000000
|
|
3
|
+
370,0.000414900000,0.000012390000,0.001946000000
|
|
4
|
+
375,0.000741600000,0.000022020000,0.003486000000
|
|
5
|
+
380,0.001368000000,0.000039000000,0.006450001000
|
|
6
|
+
385,0.002236000000,0.000064000000,0.010549990000
|
|
7
|
+
390,0.004243000000,0.000120000000,0.020050010000
|
|
8
|
+
395,0.007650000000,0.000217000000,0.036210000000
|
|
9
|
+
400,0.014310000000,0.000396000000,0.067850010000
|
|
10
|
+
405,0.023190000000,0.000640000000,0.110200000000
|
|
11
|
+
410,0.043510000000,0.001210000000,0.207400000000
|
|
12
|
+
415,0.077630000000,0.002180000000,0.371300000000
|
|
13
|
+
420,0.134380000000,0.004000000000,0.645600000000
|
|
14
|
+
425,0.214770000000,0.007300000000,1.039050100000
|
|
15
|
+
430,0.283900000000,0.011600000000,1.385600000000
|
|
16
|
+
435,0.328500000000,0.016840000000,1.622960000000
|
|
17
|
+
440,0.348280000000,0.023000000000,1.747060000000
|
|
18
|
+
445,0.348060000000,0.029800000000,1.782600000000
|
|
19
|
+
450,0.336200000000,0.038000000000,1.772110000000
|
|
20
|
+
455,0.318700000000,0.048000000000,1.744100000000
|
|
21
|
+
460,0.290800000000,0.060000000000,1.669200000000
|
|
22
|
+
465,0.251100000000,0.073900000000,1.528100000000
|
|
23
|
+
470,0.195360000000,0.090980000000,1.287640000000
|
|
24
|
+
475,0.142100000000,0.112600000000,1.041900000000
|
|
25
|
+
480,0.095640000000,0.139020000000,0.812950100000
|
|
26
|
+
485,0.057950010000,0.169300000000,0.616200000000
|
|
27
|
+
490,0.032010000000,0.208020000000,0.465180000000
|
|
28
|
+
495,0.014700000000,0.258600000000,0.353300000000
|
|
29
|
+
500,0.004900000000,0.323000000000,0.272000000000
|
|
30
|
+
505,0.002400000000,0.407300000000,0.212300000000
|
|
31
|
+
510,0.009300000000,0.503000000000,0.158200000000
|
|
32
|
+
515,0.029100000000,0.608200000000,0.111700000000
|
|
33
|
+
520,0.063270000000,0.710000000000,0.078249990000
|
|
34
|
+
525,0.109600000000,0.793200000000,0.057250010000
|
|
35
|
+
530,0.165500000000,0.862000000000,0.042160000000
|
|
36
|
+
535,0.225749900000,0.914850100000,0.029840000000
|
|
37
|
+
540,0.290400000000,0.954000000000,0.020300000000
|
|
38
|
+
545,0.359700000000,0.980300000000,0.013400000000
|
|
39
|
+
550,0.433449900000,0.994950100000,0.008749999000
|
|
40
|
+
555,0.512050100000,1.000000000000,0.005749999000
|
|
41
|
+
560,0.594500000000,0.995000000000,0.003900000000
|
|
42
|
+
565,0.678400000000,0.978600000000,0.002749999000
|
|
43
|
+
570,0.762100000000,0.952000000000,0.002100000000
|
|
44
|
+
575,0.842500000000,0.915400000000,0.001800000000
|
|
45
|
+
580,0.916300000000,0.870000000000,0.001650001000
|
|
46
|
+
585,0.978600000000,0.816300000000,0.001400000000
|
|
47
|
+
590,1.026300000000,0.757000000000,0.001100000000
|
|
48
|
+
595,1.056700000000,0.694900000000,0.001000000000
|
|
49
|
+
600,1.062200000000,0.631000000000,0.000800000000
|
|
50
|
+
605,1.045600000000,0.566800000000,0.000600000000
|
|
51
|
+
610,1.002600000000,0.503000000000,0.000340000000
|
|
52
|
+
615,0.938400000000,0.441200000000,0.000240000000
|
|
53
|
+
620,0.854449900000,0.381000000000,0.000190000000
|
|
54
|
+
625,0.751400000000,0.321000000000,0.000100000000
|
|
55
|
+
630,0.642400000000,0.265000000000,0.000049999990
|
|
56
|
+
635,0.541900000000,0.217000000000,0.000030000000
|
|
57
|
+
640,0.447900000000,0.175000000000,0.000020000000
|
|
58
|
+
645,0.360800000000,0.138200000000,0.000010000000
|
|
59
|
+
650,0.283500000000,0.107000000000,0.000000000000
|
|
60
|
+
655,0.218700000000,0.081600000000,0.000000000000
|
|
61
|
+
660,0.164900000000,0.061000000000,0.000000000000
|
|
62
|
+
665,0.121200000000,0.044580000000,0.000000000000
|
|
63
|
+
670,0.087400000000,0.032000000000,0.000000000000
|
|
64
|
+
675,0.063600000000,0.023200000000,0.000000000000
|
|
65
|
+
680,0.046770000000,0.017000000000,0.000000000000
|
|
66
|
+
685,0.032900000000,0.011920000000,0.000000000000
|
|
67
|
+
690,0.022700000000,0.008210000000,0.000000000000
|
|
68
|
+
695,0.015840000000,0.005723000000,0.000000000000
|
|
69
|
+
700,0.011359160000,0.004102000000,0.000000000000
|
|
70
|
+
705,0.008110916000,0.002929000000,0.000000000000
|
|
71
|
+
710,0.005790346000,0.002091000000,0.000000000000
|
|
72
|
+
715,0.004109457000,0.001484000000,0.000000000000
|
|
73
|
+
720,0.002899327000,0.001047000000,0.000000000000
|
|
74
|
+
725,0.002049190000,0.000740000000,0.000000000000
|
|
75
|
+
730,0.001439971000,0.000520000000,0.000000000000
|
|
76
|
+
735,0.000999949300,0.000361100000,0.000000000000
|
|
77
|
+
740,0.000690078600,0.000249200000,0.000000000000
|
|
78
|
+
745,0.000476021300,0.000171900000,0.000000000000
|
|
79
|
+
750,0.000332301100,0.000120000000,0.000000000000
|
|
80
|
+
755,0.000234826100,0.000084800000,0.000000000000
|
|
81
|
+
760,0.000166150500,0.000060000000,0.000000000000
|
|
82
|
+
765,0.000117413000,0.000042400000,0.000000000000
|
|
83
|
+
770,0.000083075270,0.000030000000,0.000000000000
|
|
84
|
+
775,0.000058706520,0.000021200000,0.000000000000
|
|
85
|
+
780,0.000041509940,0.000014990000,0.000000000000
|
|
86
|
+
785,0.000029353260,0.000010600000,0.000000000000
|
|
87
|
+
790,0.000020673830,0.000007465700,0.000000000000
|
|
88
|
+
795,0.000014559770,0.000005257800,0.000000000000
|
|
89
|
+
800,0.000010253980,0.000003702900,0.000000000000
|
|
90
|
+
805,0.000007221456,0.000002607800,0.000000000000
|
|
91
|
+
810,0.000005085868,0.000001836600,0.000000000000
|
|
92
|
+
815,0.000003581652,0.000001293400,0.000000000000
|
|
93
|
+
820,0.000002522525,0.000000910930,0.000000000000
|
|
94
|
+
825,0.000001776509,0.000000641530,0.000000000000
|
|
95
|
+
830,0.000001251141,0.000000451810,0.000000000000
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
from datetime import date
|
|
4
|
+
import time
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
from onepix.logging_config import root
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
class FisCommonAcquisition:
|
|
11
|
+
def __init__(self,acquisition_class=None):
|
|
12
|
+
if acquisition_class:
|
|
13
|
+
self.acquisition_class = acquisition_class
|
|
14
|
+
base_path = os.path.join(os.path.dirname(os.path.abspath(__file__)))
|
|
15
|
+
self.config_path = os.path.join(base_path, "FIS_common_param.json")
|
|
16
|
+
self.hardware_dict = self._load_json(self.config_path)
|
|
17
|
+
self.spatial_res=self.hardware_dict["spatial_res"]
|
|
18
|
+
print("in common acquisition spatial res is ",self.spatial_res)
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def _load_json(path):
|
|
22
|
+
"""Charge un fichier JSON en gérant les erreurs."""
|
|
23
|
+
try:
|
|
24
|
+
with open(path, "r") as f:
|
|
25
|
+
return json.load(f)
|
|
26
|
+
except FileNotFoundError:
|
|
27
|
+
logger.warning(f"⚠️ Warning: {path} not found. Using empty config.")
|
|
28
|
+
return {}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
import matplotlib.patches as patches
|
|
4
|
+
import os
|
|
5
|
+
import glob
|
|
6
|
+
from scipy.signal import savgol_filter
|
|
7
|
+
from tkinter import filedialog
|
|
8
|
+
from tkinter import *
|
|
9
|
+
import cv2
|
|
10
|
+
from sklearn.decomposition import PCA
|
|
11
|
+
from sklearn.cluster import KMeans
|
|
12
|
+
import spectral.io.envi as envi
|
|
13
|
+
from scipy.interpolate import interp1d
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from datetime import date
|
|
17
|
+
import time
|
|
18
|
+
|
|
19
|
+
class FisAnalysis:
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
def get_header_data(self, path):
|
|
25
|
+
"""
|
|
26
|
+
This function allows to generate a dictionnary containing acquisition data
|
|
27
|
+
useful for the data cube reconstruction.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
path : str
|
|
32
|
+
Header file path
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
acq_data : dict
|
|
37
|
+
Dictionnary containing acquisition data.
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
header = []
|
|
42
|
+
with open(path, "r") as file:
|
|
43
|
+
for line in file.readlines():
|
|
44
|
+
header.append(line.split(":"))
|
|
45
|
+
acq_data = dict()
|
|
46
|
+
# acq_data['Acquisition_name']=header[0][0][8:]
|
|
47
|
+
for x in header:
|
|
48
|
+
if x[0].strip() == "Imaging method":
|
|
49
|
+
acq_data["imaging_method"] = x[1].strip()
|
|
50
|
+
|
|
51
|
+
if x[0].strip() == "Integration time":
|
|
52
|
+
acq_data["integration_time_ms"] = float(x[1].strip()[:-2])
|
|
53
|
+
|
|
54
|
+
return acq_data
|
|
55
|
+
|
|
56
|
+
def load_hypercube(self, opt=None):
|
|
57
|
+
"""
|
|
58
|
+
This function allows to load saved spectra with timers of the displays and spectrometers.
|
|
59
|
+
at runtime, a window appears to select the folder path in which the data are located.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
acq_data : dict
|
|
64
|
+
Dictionary containing data extracted from files saved after acquisition to reconstruct data cubes.
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
res = {"reconstructed_image": [], "wavelengths": []}
|
|
69
|
+
|
|
70
|
+
if opt == None:
|
|
71
|
+
if os.path.isdir("./Hypercubes"):
|
|
72
|
+
data_folder = "./Hypercubes"
|
|
73
|
+
elif "../Hypercubes":
|
|
74
|
+
data_folder = "../Hypercubes"
|
|
75
|
+
else:
|
|
76
|
+
data_folder = os.getcwd()
|
|
77
|
+
|
|
78
|
+
meas_path = filedialog.askdirectory(
|
|
79
|
+
title="Select the folder containing the acquisitions",
|
|
80
|
+
initialdir=data_folder,
|
|
81
|
+
)
|
|
82
|
+
elif opt == "last":
|
|
83
|
+
root_path = os.getcwd()
|
|
84
|
+
path = os.path.join(root_path, "Hypercubes")
|
|
85
|
+
meas_path = max(glob.glob(os.path.join(path, "*/")), key=os.path.getmtime)
|
|
86
|
+
|
|
87
|
+
else:
|
|
88
|
+
meas_path = opt
|
|
89
|
+
|
|
90
|
+
hyp_filename = glob.glob(f"{meas_path}/*.hdr")[0]
|
|
91
|
+
try:
|
|
92
|
+
info_filename = glob.glob(f"{meas_path}/*.txt")[0]
|
|
93
|
+
res["pattern_method"] = self.get_header_data(info_filename)[
|
|
94
|
+
"imaging_method"
|
|
95
|
+
]
|
|
96
|
+
except:
|
|
97
|
+
pass
|
|
98
|
+
res["infos"] = "ONE_PIX_analysis" + meas_path.split("/")[-1][19:]
|
|
99
|
+
data = envi.open(hyp_filename)
|
|
100
|
+
res["reconstructed_image"] = data.load()
|
|
101
|
+
res["wavelengths"] = np.array(data.bands.centers)
|
|
102
|
+
|
|
103
|
+
return res
|
|
104
|
+
|
|
105
|
+
def snr_filt(self, spectre_desplit, noise_level=500):
|
|
106
|
+
"""
|
|
107
|
+
snr_filt filters spectra in which the maximum intensity is lower than a defined level.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
spectre_desplit : array of floats
|
|
112
|
+
2D array of spectra.
|
|
113
|
+
noise_level : float, optional
|
|
114
|
+
minimum accepted intensity level. The default is 500.
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
spectre_desplit : array of floats
|
|
119
|
+
filtered 2D array of spectra.
|
|
120
|
+
|
|
121
|
+
"""
|
|
122
|
+
M = np.max(abs(spectre_desplit), axis=1)
|
|
123
|
+
idx_noise = np.squeeze(np.array(np.where(M < noise_level)))
|
|
124
|
+
spectre_desplit[idx_noise, :] = 0
|
|
125
|
+
return spectre_desplit
|
|
126
|
+
|
|
127
|
+
def Flux2Ref(self, rawHypercube, Wavelengths, reference=None):
|
|
128
|
+
"""
|
|
129
|
+
Flux2Ref allows to normalize a raw hypercube in reflectance using a standard present in
|
|
130
|
+
the reconstructed image and its reflectance certificate.
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
rawHypercube: numpy.array
|
|
135
|
+
3D array of spectral intensities data cube.
|
|
136
|
+
Wavelengths: numpy.array of floats
|
|
137
|
+
1D array of spectrometer's sampled wavelengths.
|
|
138
|
+
reference: numpy.array, optional
|
|
139
|
+
2D array of reflectance certificate composed of wavelengths and reflectance data.
|
|
140
|
+
The default is None for RELATIVE reflectances.
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
----------
|
|
144
|
+
Normalised_Hypercube (numpy.array)
|
|
145
|
+
Reflectance normalised hypercube.
|
|
146
|
+
"""
|
|
147
|
+
if reference == None:
|
|
148
|
+
reference = np.array([Wavelengths, np.ones_like(Wavelengths)])
|
|
149
|
+
sz = np.shape(rawHypercube) # Hypercube dimensions
|
|
150
|
+
# Display spectral mean of the hypercube and select the reference area
|
|
151
|
+
fig, ax = plt.subplots(1)
|
|
152
|
+
ax.imshow(np.mean(rawHypercube, axis=2))
|
|
153
|
+
plt.title("Select a reference area to normalise the data cube")
|
|
154
|
+
x1, x2 = plt.ginput(2) # Select two points to define reference area
|
|
155
|
+
x1 = np.round(x1).astype(np.int32)
|
|
156
|
+
x2 = np.round(x2).astype(np.int32)
|
|
157
|
+
# Create a Rectangle patch
|
|
158
|
+
rect = patches.Rectangle(
|
|
159
|
+
(x1[0], x1[1]),
|
|
160
|
+
x2[0] - x1[0],
|
|
161
|
+
x2[1] - x1[1],
|
|
162
|
+
linewidth=1,
|
|
163
|
+
edgecolor="r",
|
|
164
|
+
facecolor="none",
|
|
165
|
+
)
|
|
166
|
+
ax.add_patch(rect) # Add the patch to the Axes
|
|
167
|
+
plt.show()
|
|
168
|
+
# Determine the spectral normalization coefficient
|
|
169
|
+
Refmes = np.mean(
|
|
170
|
+
rawHypercube[x1[1] : x2[1], x1[0] : x2[0], :], axis=(0, 1)
|
|
171
|
+
) # 2D spatial mean of the selected area
|
|
172
|
+
Lref = np.squeeze(
|
|
173
|
+
np.interp(Wavelengths, reference[:, 0], reference[:, 1])
|
|
174
|
+
) # Interpolation of the reflectance certificate to fit with experimental Wavelengths sampling
|
|
175
|
+
Normcoeff = Lref / Refmes # Normalisation coefficient
|
|
176
|
+
|
|
177
|
+
# Hypercube normalisation without for loops
|
|
178
|
+
RefHypercube = np.tile(
|
|
179
|
+
np.reshape(Normcoeff, [1, 1, sz[2]]), [sz[0], sz[1], 1]
|
|
180
|
+
) # reshape NormCoeff into a hypercube
|
|
181
|
+
Normalised_Hypercube = rawHypercube * RefHypercube # Hypercube Normalisation
|
|
182
|
+
|
|
183
|
+
return Normalised_Hypercube
|
|
184
|
+
|
|
185
|
+
def spikes_correction(self, res):
|
|
186
|
+
"""
|
|
187
|
+
Allows to correct by clicking on an over represented frequency in the Fourier domain.
|
|
188
|
+
|
|
189
|
+
Parameters
|
|
190
|
+
----------
|
|
191
|
+
res: dict
|
|
192
|
+
dictionnary of acquisition results. Especially contains the whole spatial spectrum data cube
|
|
193
|
+
and the association image datacube.
|
|
194
|
+
|
|
195
|
+
Returns
|
|
196
|
+
-------
|
|
197
|
+
res: dict
|
|
198
|
+
actualised dictionnary of acquisition results.
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
fig, ax = plt.subplots()
|
|
204
|
+
whole_spectrum = np.fft.fftshift(
|
|
205
|
+
np.fft.fft2(res["reconstructed_image"], axes=(0, 1))
|
|
206
|
+
)
|
|
207
|
+
plt.imshow(np.log10(abs(np.mean(whole_spectrum, 2))))
|
|
208
|
+
plt.title(
|
|
209
|
+
"Spectral frequencies spikes correction. Pacq_datas escape key to end"
|
|
210
|
+
)
|
|
211
|
+
p = plt.ginput(-1)
|
|
212
|
+
p = np.round(p).astype(np.int32)
|
|
213
|
+
plt.clf()
|
|
214
|
+
|
|
215
|
+
for pixel in p:
|
|
216
|
+
if pixel[0] == np.size(whole_spectrum, 1) - 1:
|
|
217
|
+
whole_spectrum[pixel[1], pixel[0], :] = whole_spectrum[
|
|
218
|
+
pixel[1], pixel[0] - 2, :
|
|
219
|
+
]
|
|
220
|
+
else:
|
|
221
|
+
whole_spectrum[pixel[1], pixel[0], :] = whole_spectrum[
|
|
222
|
+
pixel[1], pixel[0] + 1, :
|
|
223
|
+
]
|
|
224
|
+
plt.imshow(np.log10(abs(np.mean(whole_spectrum, 2))))
|
|
225
|
+
plt.show()
|
|
226
|
+
res["reconstructed_image"] = abs(np.fft.ifft2(whole_spectrum, axes=(0, 1)))
|
|
227
|
+
return res
|
|
228
|
+
|
|
229
|
+
def datacube_normalisation_snv(self, datacube):
|
|
230
|
+
"""
|
|
231
|
+
datacube_normalisation_snv allows to normamlize an hypercube using Standrad Variate normalisation
|
|
232
|
+
|
|
233
|
+
Parameters
|
|
234
|
+
----------
|
|
235
|
+
datacube : array
|
|
236
|
+
3D image data cube.
|
|
237
|
+
|
|
238
|
+
Returns
|
|
239
|
+
-------
|
|
240
|
+
datacube : array
|
|
241
|
+
3D normalize datacube.
|
|
242
|
+
|
|
243
|
+
"""
|
|
244
|
+
norm_datacube = np.zeros_like(datacube)
|
|
245
|
+
for i in range(0, np.shape(datacube)[0]):
|
|
246
|
+
for j in range(0, np.shape(datacube)[1]):
|
|
247
|
+
norm_datacube[i, j, :] = (
|
|
248
|
+
datacube[i, j, :] - np.mean(datacube[i, j, :])
|
|
249
|
+
) / np.std(datacube[i, j, :])
|
|
250
|
+
return norm_datacube
|
|
251
|
+
|
|
252
|
+
def datacube_reflectance_normalisation(self, datacube, ref_datacube):
|
|
253
|
+
if np.shape(ref_datacube) != np.shape(datacube):
|
|
254
|
+
ref = np.zeros_like(datacube)
|
|
255
|
+
for wl in range(np.size(ref, 2)):
|
|
256
|
+
ref[:, :, wl] = cv2.resize(ref_datacube[:, :, wl], (np.shape(ref)[:2]))
|
|
257
|
+
else:
|
|
258
|
+
ref = ref_datacube
|
|
259
|
+
|
|
260
|
+
normalised_datacube = datacube / ref
|
|
261
|
+
|
|
262
|
+
return normalised_datacube
|
|
263
|
+
|
|
264
|
+
def select_disp_spectra(self, datacube, wavelengths, n, mode):
|
|
265
|
+
"""
|
|
266
|
+
select_disp_spectra allows to select pixel(s) of one hypercube and plot their spectra.
|
|
267
|
+
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
datacube : array of floats
|
|
271
|
+
3D image data cube.
|
|
272
|
+
wavelengths : array of floats
|
|
273
|
+
1D array of sampled wavelengths.
|
|
274
|
+
n : int
|
|
275
|
+
Number of spectra to plot.
|
|
276
|
+
mode : str
|
|
277
|
+
for the mean area of spectra allows to plot mean ('mean') or each
|
|
278
|
+
selected spectrum in one figure ('single').
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
spec : array
|
|
283
|
+
Spectra selected by the user from the data cube.
|
|
284
|
+
|
|
285
|
+
"""
|
|
286
|
+
# Display Hypercube spectral mean to visualise an image
|
|
287
|
+
rgb_image = self.RGB_reconstruction(datacube, wavelengths)
|
|
288
|
+
fig, ax = plt.subplots()
|
|
289
|
+
# plt.subplot(1,2,1)
|
|
290
|
+
ax.imshow(rgb_image)
|
|
291
|
+
|
|
292
|
+
# Selection of pixel(s) or region of interest
|
|
293
|
+
if mode == "single":
|
|
294
|
+
self.tick = np.arange(n) + 1
|
|
295
|
+
# Select pixel(s)
|
|
296
|
+
self.pixels = fig.ginput(n)
|
|
297
|
+
self.pixels = np.round(self.pixels).astype(np.int32)
|
|
298
|
+
# Display results
|
|
299
|
+
plt.plot(self.pixels[:, 0], self.pixels[:, 1], "x", color="red")
|
|
300
|
+
for i, txt in enumerate(self.tick):
|
|
301
|
+
plt.annotate(
|
|
302
|
+
txt,
|
|
303
|
+
(self.pixels[i, 0] + 1, self.pixels[i, 1] + 1),
|
|
304
|
+
color="r",
|
|
305
|
+
bbox=dict(boxstyle="circle"),
|
|
306
|
+
)
|
|
307
|
+
spec = datacube[self.pixels[:, 1], self.pixels[:, 0], :]
|
|
308
|
+
plt.show(block=False)
|
|
309
|
+
|
|
310
|
+
elif mode == "mean":
|
|
311
|
+
# Select 2 corner pixels of rectangle area
|
|
312
|
+
p = plt.ginput(2)
|
|
313
|
+
p = np.round(p).astype(np.int32)
|
|
314
|
+
spec = datacube[
|
|
315
|
+
p[0, 1] : p[1, 1], p[0, 0] : p[1, 0], :
|
|
316
|
+
] # 2D spatial mean of the selected area
|
|
317
|
+
|
|
318
|
+
# Display results
|
|
319
|
+
rect = patches.Rectangle(
|
|
320
|
+
(p[0, 0], p[0, 1]),
|
|
321
|
+
p[1, 0] - p[0, 0],
|
|
322
|
+
p[1, 1] - p[0, 1],
|
|
323
|
+
linewidth=1,
|
|
324
|
+
edgecolor="r",
|
|
325
|
+
facecolor="none",
|
|
326
|
+
)
|
|
327
|
+
ax.add_patch(rect) # Add the patch to the Axes
|
|
328
|
+
plt.subplot(1, 2, 2)
|
|
329
|
+
plt.plot(wavelengths, np.mean(spec, axis=(0, 1)))
|
|
330
|
+
plt.xlabel("Wavelengths (nm)")
|
|
331
|
+
plt.ylabel("Mean intensity (counts)")
|
|
332
|
+
plt.show(block=False)
|
|
333
|
+
|
|
334
|
+
else:
|
|
335
|
+
print(
|
|
336
|
+
"mode='single' to display each spectrum or mode='mean' to display the mean of the spectra from the selected area"
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
plt.close()
|
|
340
|
+
|
|
341
|
+
return spec
|
|
342
|
+
|
|
343
|
+
def smooth_datacube(self, datacube, window_length, polyorder):
|
|
344
|
+
"""
|
|
345
|
+
This function allows to smooth spectra of a whole datacube using Savitzky-Golay filter.
|
|
346
|
+
See more at :
|
|
347
|
+
https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.savgol_filter.html
|
|
348
|
+
|
|
349
|
+
Parameters
|
|
350
|
+
----------
|
|
351
|
+
datacube: array of floats
|
|
352
|
+
3D image data cube.
|
|
353
|
+
window_length : int
|
|
354
|
+
The length of the filter window.
|
|
355
|
+
polyorder : int
|
|
356
|
+
The order of the polynomial used to fit the samples.
|
|
357
|
+
polyorder must be less than window_length.
|
|
358
|
+
|
|
359
|
+
Returns
|
|
360
|
+
-------
|
|
361
|
+
smoothed_datacube : array
|
|
362
|
+
3D array of smoothed datacube.
|
|
363
|
+
|
|
364
|
+
"""
|
|
365
|
+
sz = np.shape(datacube)
|
|
366
|
+
smoothed_datacube = np.zeros(sz)
|
|
367
|
+
for line in range(sz[0]):
|
|
368
|
+
for col in range(sz[1]):
|
|
369
|
+
smoothed_datacube[line, col, :] = savgol_filter(
|
|
370
|
+
datacube[line, col, :], window_length, polyorder
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
return smoothed_datacube
|
|
374
|
+
|
|
375
|
+
def RGB_reconstruction(self, datacube, wavelengths, gamma=2.2):
|
|
376
|
+
"""
|
|
377
|
+
Convertit une image hyperspectrale en une image en espace CIE XYZ et la transforme en sRGB.
|
|
378
|
+
|
|
379
|
+
:param hsi_image: Image hyperspectrale sous forme (H, W, C)
|
|
380
|
+
:param wavelengths: Tableau des longueurs d'onde correspondant aux C canaux
|
|
381
|
+
:param cie_cmfs: Matrice des fonctions colorimétriques CIE 1931 (λ, X, Y, Z)
|
|
382
|
+
:param gamma: Correction gamma pour l'affichage
|
|
383
|
+
:return: Image RGB en uint8 (H, W, 3)
|
|
384
|
+
"""
|
|
385
|
+
# Récupérer le chemin absolu du script en cours d'exécution
|
|
386
|
+
script_dir = Path(__file__).parent # Dossier où se trouve le script
|
|
387
|
+
cie_file = script_dir / "CIE1931-2deg-XYZ.csv" # Fichier CMF attendu dans le même dossier
|
|
388
|
+
|
|
389
|
+
# Chargement des fonctions colorimétriques CIE 1931
|
|
390
|
+
cie_cmfs = np.loadtxt(cie_file, delimiter=",")
|
|
391
|
+
|
|
392
|
+
# Extraction des longueurs d'onde et des fonctions CMF
|
|
393
|
+
cmf_wavelengths, X_cmf, Y_cmf, Z_cmf = cie_cmfs[:, 0], cie_cmfs[:, 1], cie_cmfs[:, 2], cie_cmfs[:, 3]
|
|
394
|
+
|
|
395
|
+
# Interpolation des CMFs pour correspondre aux longueurs d'onde de l'image hyperspectrale
|
|
396
|
+
interp_X = interp1d(cmf_wavelengths, X_cmf, kind='linear', bounds_error=False, fill_value=0)
|
|
397
|
+
interp_Y = interp1d(cmf_wavelengths, Y_cmf, kind='linear', bounds_error=False, fill_value=0)
|
|
398
|
+
interp_Z = interp1d(cmf_wavelengths, Z_cmf, kind='linear', bounds_error=False, fill_value=0)
|
|
399
|
+
|
|
400
|
+
# Pondération des bandes spectrales par les CMFs
|
|
401
|
+
X = np.sum(datacube * interp_X(wavelengths), axis=-1)
|
|
402
|
+
Y = np.sum(datacube * interp_Y(wavelengths), axis=-1)
|
|
403
|
+
Z = np.sum(datacube * interp_Z(wavelengths), axis=-1)
|
|
404
|
+
|
|
405
|
+
# Empiler les canaux pour obtenir une image XYZ
|
|
406
|
+
xyz_image = np.stack([X, Y, Z], axis=-1)
|
|
407
|
+
|
|
408
|
+
# Transformation de XYZ vers sRGB (D65 standard)
|
|
409
|
+
M_XYZ_to_sRGB = np.array([[ 3.2406, -1.5372, -0.4986],
|
|
410
|
+
[-0.9689, 1.8758, 0.0415],
|
|
411
|
+
[ 0.0557, -0.2040, 1.0570]])
|
|
412
|
+
|
|
413
|
+
rgb_image = np.dot(xyz_image, M_XYZ_to_sRGB.T)
|
|
414
|
+
|
|
415
|
+
# Normalisation et correction gamma
|
|
416
|
+
rgb_image -= rgb_image.min()
|
|
417
|
+
rgb_image /= rgb_image.max()
|
|
418
|
+
rgb_image = np.power(rgb_image, 1 / gamma)
|
|
419
|
+
|
|
420
|
+
# Conversion en uint8 pour affichage
|
|
421
|
+
return (rgb_image * 255).astype(np.uint8)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def clustering(self, datacube, components, n_cluster):
|
|
426
|
+
"""
|
|
427
|
+
Clustering function allow to realize classification of the datacube
|
|
428
|
+
using kmeans algorithm on princpal components of the PCA.
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
Parameters
|
|
432
|
+
----------
|
|
433
|
+
datacube : array
|
|
434
|
+
3D image data cube.
|
|
435
|
+
components : int
|
|
436
|
+
number of principal component used for the kmeans clustering.
|
|
437
|
+
n_cluster : int
|
|
438
|
+
number of cluster for the kmeans clustering.
|
|
439
|
+
|
|
440
|
+
Returns
|
|
441
|
+
-------
|
|
442
|
+
image_seg : array of int
|
|
443
|
+
2D array of clustered image.
|
|
444
|
+
|
|
445
|
+
"""
|
|
446
|
+
|
|
447
|
+
image_reshape = np.reshape(
|
|
448
|
+
datacube,
|
|
449
|
+
(np.size(datacube, 0) * np.size(datacube, 1), np.size(datacube, 2)),
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
pca = PCA(components)
|
|
453
|
+
principalComponents = pca.fit_transform(image_reshape)
|
|
454
|
+
|
|
455
|
+
kmeans = KMeans(n_cluster, n_init=10, random_state=0).fit(principalComponents)
|
|
456
|
+
image_seg = np.reshape(
|
|
457
|
+
kmeans.labels_, (np.size(datacube, 0), np.size(datacube, 1))
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
return image_seg
|
|
461
|
+
|
|
462
|
+
def display_clust_spectra(self, image_seg, datacube, wl):
|
|
463
|
+
"""
|
|
464
|
+
display_clust_spectra allows to display mean spectra of each cluster
|
|
465
|
+
of a segmented image.
|
|
466
|
+
|
|
467
|
+
Parameters
|
|
468
|
+
----------
|
|
469
|
+
image_seg : array of int
|
|
470
|
+
2D array of clustered image.
|
|
471
|
+
datacube : array
|
|
472
|
+
3D image data cube.
|
|
473
|
+
wl : array of floats
|
|
474
|
+
1D sampled spectrometers wavelengths.
|
|
475
|
+
|
|
476
|
+
Returns
|
|
477
|
+
-------
|
|
478
|
+
spec : array of floats
|
|
479
|
+
spectra of each cluster.
|
|
480
|
+
|
|
481
|
+
"""
|
|
482
|
+
n_cluster = np.max(image_seg) + 1
|
|
483
|
+
fig, ax = plt.subplots()
|
|
484
|
+
|
|
485
|
+
plt.subplot(1, 2, 1)
|
|
486
|
+
plt.imshow(image_seg)
|
|
487
|
+
plt.title("Clustered image")
|
|
488
|
+
legend = []
|
|
489
|
+
spec = []
|
|
490
|
+
mean_spec = np.zeros((n_cluster, len(wl)))
|
|
491
|
+
plt.subplot(1, 2, 2)
|
|
492
|
+
for clust in range(n_cluster):
|
|
493
|
+
idx_clust = np.where(image_seg == clust)
|
|
494
|
+
spec.append(datacube[idx_clust[0], idx_clust[1], :])
|
|
495
|
+
mean_spec[clust, :] = np.mean(spec[clust], 0)
|
|
496
|
+
legend.append(f"Cluster {clust}")
|
|
497
|
+
plt.plot(wl, mean_spec.T)
|
|
498
|
+
|
|
499
|
+
plt.title("Mean spectra of the clusters")
|
|
500
|
+
plt.xlabel("Wavelengths (nm)")
|
|
501
|
+
plt.ylabel("Reflectance")
|
|
502
|
+
plt.legend(legend)
|
|
503
|
+
|
|
504
|
+
return spec
|
|
505
|
+
|
|
506
|
+
def clip_datacube(self, datacube, wavelengths, lower_bound, upper_bound):
|
|
507
|
+
"""
|
|
508
|
+
rognage_hyp allows to clip spectral (3rd) dimension of a datacube to
|
|
509
|
+
limit the analyse to the part of the measured spectra of interest.
|
|
510
|
+
|
|
511
|
+
Parameters
|
|
512
|
+
----------
|
|
513
|
+
datacube : array
|
|
514
|
+
3D image data cube.
|
|
515
|
+
wavelengths : array of floats
|
|
516
|
+
1D sampled spectrometers wavelengths.
|
|
517
|
+
lower_bound : int
|
|
518
|
+
lower bound pixel of the associated wavelengths to start.
|
|
519
|
+
upper_bound : int
|
|
520
|
+
upper bound pixel of the associated wavelengths to end.
|
|
521
|
+
|
|
522
|
+
Returns
|
|
523
|
+
-------
|
|
524
|
+
datacube_rogn : array
|
|
525
|
+
3D clipped datacube.
|
|
526
|
+
wavelengths_rogn : array
|
|
527
|
+
1D array of clipped wavelengths.
|
|
528
|
+
|
|
529
|
+
"""
|
|
530
|
+
|
|
531
|
+
indice_min = np.abs(wavelengths - lower_bound).argmin()
|
|
532
|
+
indice_max = np.abs(wavelengths - upper_bound).argmin()
|
|
533
|
+
|
|
534
|
+
wavelengths_rogn = wavelengths[indice_min:indice_max]
|
|
535
|
+
datacube_rogn = datacube[:, :, indice_min:indice_max]
|
|
536
|
+
|
|
537
|
+
return datacube_rogn, wavelengths_rogn
|
|
538
|
+
|
|
539
|
+
def save_acquisition_envi(
|
|
540
|
+
self, datacube, wavelengths, header, save_envi_name=None, save_path=None
|
|
541
|
+
):
|
|
542
|
+
"""
|
|
543
|
+
This function allow to save the resulting acquisitions from one
|
|
544
|
+
OPConfig object into the Hypercube folder.
|
|
545
|
+
|
|
546
|
+
Parameters
|
|
547
|
+
----------
|
|
548
|
+
config : class
|
|
549
|
+
OPConfig class object.
|
|
550
|
+
|
|
551
|
+
Returns
|
|
552
|
+
-------
|
|
553
|
+
None.
|
|
554
|
+
|
|
555
|
+
"""
|
|
556
|
+
root_path = os.getcwd()
|
|
557
|
+
if save_path is None:
|
|
558
|
+
save_path = f"..{os.sep}Hypercubes"
|
|
559
|
+
if os.path.isdir(save_path):
|
|
560
|
+
pass
|
|
561
|
+
else:
|
|
562
|
+
os.mkdir(save_path)
|
|
563
|
+
os.chdir(save_path)
|
|
564
|
+
|
|
565
|
+
self.fdate = date.today().strftime(
|
|
566
|
+
"%d_%m_%Y"
|
|
567
|
+
) # convert the current date in string
|
|
568
|
+
self.actual_time = time.strftime("%H-%M-%S") # get the current time
|
|
569
|
+
folder_name = (
|
|
570
|
+
f"ONE-PIX_reconstructed_data_{self.fdate}_{self.actual_time}"
|
|
571
|
+
if save_envi_name is None
|
|
572
|
+
else save_envi_name
|
|
573
|
+
)
|
|
574
|
+
os.mkdir(folder_name)
|
|
575
|
+
os.chdir(folder_name)
|
|
576
|
+
self.save_path = folder_name
|
|
577
|
+
if save_envi_name is None:
|
|
578
|
+
save_envi_name = folder_name
|
|
579
|
+
# saving the acquired spatial spectra hypercube
|
|
580
|
+
self.py2envi(datacube, wavelengths, save_envi_name, save_path)
|
|
581
|
+
with open(folder_name + ".txt", "w+") as header_file:
|
|
582
|
+
header_file.write(header)
|
|
583
|
+
os.chdir(root_path)
|
|
584
|
+
|
|
585
|
+
def py2envi(self, datacube, wavelengths, save_envi_name, save_path=None):
|
|
586
|
+
"""
|
|
587
|
+
py2ms allows to save ONE-PIX data into ENVI format https://www.l3harrisgeospatial.com/docs/enviheaderfiles.html
|
|
588
|
+
metadata can be improved !
|
|
589
|
+
|
|
590
|
+
Parameters
|
|
591
|
+
----------
|
|
592
|
+
save_envi_name : TYPE
|
|
593
|
+
DESCRIPTION.
|
|
594
|
+
datacube : TYPE
|
|
595
|
+
DESCRIPTION.
|
|
596
|
+
wavelengths : TYPE
|
|
597
|
+
DESCRIPTION.
|
|
598
|
+
|
|
599
|
+
Returns
|
|
600
|
+
-------
|
|
601
|
+
None.
|
|
602
|
+
|
|
603
|
+
"""
|
|
604
|
+
filename = save_envi_name + ".hdr"
|
|
605
|
+
path = os.getcwd()
|
|
606
|
+
if save_path == None:
|
|
607
|
+
save_path = filedialog.askdirectory(title="Open the save directory")
|
|
608
|
+
|
|
609
|
+
envi.save_image(
|
|
610
|
+
filename, datacube, dtype=np.float32, metadata={"wavelength": wavelengths}
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
def py2ms(self, datacube, wavelengths, save_gerbil_name):
|
|
614
|
+
"""
|
|
615
|
+
|
|
616
|
+
py2ms allows to save ONE-PIX data into Gerbil format http://gerbilvis.org/
|
|
617
|
+
a window appears to select the directory where the hyperspectral data will be saved in gebril format
|
|
618
|
+
Input:
|
|
619
|
+
save_gerbil_name : the name of the saved data into gerbil format (without .txt extension)
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
Parameters
|
|
623
|
+
----------
|
|
624
|
+
save_gerbil_name : str
|
|
625
|
+
the name of the saved data into gerbil format (without .txt extension).
|
|
626
|
+
datacube : array
|
|
627
|
+
datacube to export into Gerbil format.
|
|
628
|
+
wavelengths : array
|
|
629
|
+
Sampled wavelengths associated to the measured datacube.
|
|
630
|
+
|
|
631
|
+
Returns
|
|
632
|
+
-------
|
|
633
|
+
None.
|
|
634
|
+
|
|
635
|
+
"""
|
|
636
|
+
|
|
637
|
+
save_path = filedialog.askdirectory(title="Open the save directory")
|
|
638
|
+
maxval = datacube.max()
|
|
639
|
+
minval = datacube.min()
|
|
640
|
+
Range = maxval - minval
|
|
641
|
+
datacube = (datacube + minval) * (255 / Range)
|
|
642
|
+
|
|
643
|
+
fid = open(save_path + "\\" + save_gerbil_name + ".txt", "w")
|
|
644
|
+
os.mkdir(save_path + "\\" + save_gerbil_name)
|
|
645
|
+
nz = np.shape(datacube)
|
|
646
|
+
|
|
647
|
+
fid.write("{0} {1} \n".format(nz[2], save_gerbil_name + "\\"))
|
|
648
|
+
|
|
649
|
+
for i in range(0, nz[2]):
|
|
650
|
+
filename = "{0}_{1}.png".format(save_gerbil_name, i)
|
|
651
|
+
cv2.imwrite(
|
|
652
|
+
save_path + "\\" + save_gerbil_name + "\\" + filename, datacube[:, :, i]
|
|
653
|
+
)
|
|
654
|
+
fid.write("{0} {1}\n".format(filename, wavelengths[i]))
|
|
655
|
+
|
|
656
|
+
fid.close()
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
from tkinter import filedialog
|
|
4
|
+
from datetime import date
|
|
5
|
+
import time
|
|
6
|
+
import spectral.io.envi as envi
|
|
7
|
+
import cv2
|
|
8
|
+
from scipy.interpolate import interp1d
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FisCommonReconstruction:
|
|
13
|
+
def __init__(self, acquisition_dict=None):
|
|
14
|
+
self.acquisition_dict = acquisition_dict
|
|
15
|
+
return
|
|
16
|
+
|
|
17
|
+
def get_result_to_plot(self, datacube, wavelengths, gamma=2.2):
|
|
18
|
+
"""
|
|
19
|
+
Convertit une image hyperspectrale en une image en espace CIE XYZ et la transforme en sRGB.
|
|
20
|
+
|
|
21
|
+
:param hsi_image: Image hyperspectrale sous forme (H, W, C)
|
|
22
|
+
:param wavelengths: Tableau des longueurs d'onde correspondant aux C canaux
|
|
23
|
+
:param cie_cmfs: Matrice des fonctions colorimétriques CIE 1931 (λ, X, Y, Z)
|
|
24
|
+
:param gamma: Correction gamma pour l'affichage
|
|
25
|
+
:return: Image RGB en uint8 (H, W, 3)
|
|
26
|
+
"""
|
|
27
|
+
# Récupérer le chemin absolu du script en cours d'exécution
|
|
28
|
+
script_dir = Path(__file__).parent # Dossier où se trouve le script
|
|
29
|
+
cie_file = script_dir / "CIE1931-2deg-XYZ.csv" # Fichier CMF attendu dans le même dossier
|
|
30
|
+
|
|
31
|
+
# Chargement des fonctions colorimétriques CIE 1931
|
|
32
|
+
cie_cmfs = np.loadtxt(cie_file, delimiter=",")
|
|
33
|
+
|
|
34
|
+
# Extraction des longueurs d'onde et des fonctions CMF
|
|
35
|
+
cmf_wavelengths, X_cmf, Y_cmf, Z_cmf = cie_cmfs[:, 0], cie_cmfs[:, 1], cie_cmfs[:, 2], cie_cmfs[:, 3]
|
|
36
|
+
|
|
37
|
+
# Interpolation des CMFs pour correspondre aux longueurs d'onde de l'image hyperspectrale
|
|
38
|
+
interp_X = interp1d(cmf_wavelengths, X_cmf, kind='linear', bounds_error=False, fill_value=0)
|
|
39
|
+
interp_Y = interp1d(cmf_wavelengths, Y_cmf, kind='linear', bounds_error=False, fill_value=0)
|
|
40
|
+
interp_Z = interp1d(cmf_wavelengths, Z_cmf, kind='linear', bounds_error=False, fill_value=0)
|
|
41
|
+
|
|
42
|
+
# Pondération des bandes spectrales par les CMFs
|
|
43
|
+
X = np.sum(datacube * interp_X(wavelengths), axis=-1)
|
|
44
|
+
Y = np.sum(datacube * interp_Y(wavelengths), axis=-1)
|
|
45
|
+
Z = np.sum(datacube * interp_Z(wavelengths), axis=-1)
|
|
46
|
+
|
|
47
|
+
# Empiler les canaux pour obtenir une image XYZ
|
|
48
|
+
xyz_image = np.stack([X, Y, Z], axis=-1)
|
|
49
|
+
|
|
50
|
+
# Transformation de XYZ vers sRGB (D65 standard)
|
|
51
|
+
M_XYZ_to_sRGB = np.array([[ 3.2406, -1.5372, -0.4986],
|
|
52
|
+
[-0.9689, 1.8758, 0.0415],
|
|
53
|
+
[ 0.0557, -0.2040, 1.0570]])
|
|
54
|
+
|
|
55
|
+
rgb_image = np.dot(xyz_image, M_XYZ_to_sRGB.T)
|
|
56
|
+
|
|
57
|
+
# Normalisation et correction gamma
|
|
58
|
+
rgb_image -= rgb_image.min()
|
|
59
|
+
rgb_image /= rgb_image.max()
|
|
60
|
+
rgb_image = np.power(rgb_image, 1 / gamma)
|
|
61
|
+
|
|
62
|
+
# Conversion en uint8 pour affichage
|
|
63
|
+
return (rgb_image * 255).astype(np.uint8)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def save_acquisition_envi(
|
|
67
|
+
self, reconstruction_results, header, save_envi_name=None, save_path=None
|
|
68
|
+
):
|
|
69
|
+
"""
|
|
70
|
+
This function allow to save the resulting acquisitions from one
|
|
71
|
+
OPConfig object into the Hypercube folder.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
config : class
|
|
76
|
+
OPConfig class object.
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
None.
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
datacube=reconstruction_results["reconstructed_data"]
|
|
84
|
+
wavelengths=reconstruction_results["wavelengths"]
|
|
85
|
+
if save_path==None:
|
|
86
|
+
save_path = Path(__file__).resolve().parent.parent.parent.parent / "measure"
|
|
87
|
+
print("save path: ",save_path)
|
|
88
|
+
if os.path.isdir(save_path):
|
|
89
|
+
pass
|
|
90
|
+
else :
|
|
91
|
+
os.mkdir(save_path)
|
|
92
|
+
|
|
93
|
+
self.fdate = date.today().strftime(
|
|
94
|
+
"%d_%m_%Y"
|
|
95
|
+
) # convert the current date in string
|
|
96
|
+
self.actual_time = time.strftime("%H-%M-%S") # get the current time
|
|
97
|
+
folder_name = (
|
|
98
|
+
f"ONE-PIX_reconstructed_data_{self.fdate}_{self.actual_time}"
|
|
99
|
+
if save_envi_name is None
|
|
100
|
+
else save_envi_name
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
self.final_path = os.path.join(save_path,folder_name)
|
|
104
|
+
os.mkdir(os.path.join(self.final_path))
|
|
105
|
+
if save_envi_name is None:
|
|
106
|
+
save_envi_name = folder_name
|
|
107
|
+
# saving the acquired spatial spectra hypercube
|
|
108
|
+
self.py2envi(datacube, wavelengths, save_envi_name, self.final_path)
|
|
109
|
+
with open(os.path.join(self.final_path,folder_name) + ".txt", "w+") as header_file:
|
|
110
|
+
header_file.write(header)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def py2envi(self, datacube, wavelengths, save_envi_name="cube", save_path=None):
|
|
115
|
+
"""
|
|
116
|
+
Sauvegarde un cube hyperspectral au format ENVI dans un dossier 'measure'
|
|
117
|
+
situé à la racine du dossier 'onepix_dev' (3 niveaux au-dessus de plugins/).
|
|
118
|
+
|
|
119
|
+
Exemple :
|
|
120
|
+
onepix_dev/
|
|
121
|
+
├── plugins/
|
|
122
|
+
│ └── imaging_methods/
|
|
123
|
+
│ └── FIS_common_functions/
|
|
124
|
+
│ └── FIS_common_reconstruction.py ← ce script
|
|
125
|
+
└── measure/
|
|
126
|
+
└── cube_xxx/
|
|
127
|
+
├── cube_xxx.hdr
|
|
128
|
+
├── cube_xxx.dat
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
# 🧠 Si wavelengths est un seul nombre, le transformer en liste
|
|
132
|
+
if isinstance(wavelengths, (int, float)):
|
|
133
|
+
wavelengths = [wavelengths]
|
|
134
|
+
|
|
135
|
+
# 📁 Dossier du script actuel
|
|
136
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
137
|
+
|
|
138
|
+
# 📁 On remonte 4 niveaux pour atteindre onepix_dev/
|
|
139
|
+
repo_root = os.path.abspath(os.path.join(current_dir, "../../../../"))
|
|
140
|
+
|
|
141
|
+
# 📁 Crée (si besoin) le dossier measure/ dans onepix_dev/
|
|
142
|
+
measure_dir = os.path.join(repo_root, "measure")
|
|
143
|
+
os.makedirs(measure_dir, exist_ok=True)
|
|
144
|
+
|
|
145
|
+
# 📁 Si aucun chemin spécifique n’est donné, on crée un sous-dossier
|
|
146
|
+
if save_path is None:
|
|
147
|
+
if not save_envi_name:
|
|
148
|
+
save_envi_name = f"cube_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
|
149
|
+
save_path = os.path.join(measure_dir, save_envi_name)
|
|
150
|
+
|
|
151
|
+
os.makedirs(save_path, exist_ok=True)
|
|
152
|
+
|
|
153
|
+
# 📝 Nom complet du fichier HDR
|
|
154
|
+
hdr_path = os.path.join(save_path, save_envi_name + ".hdr")
|
|
155
|
+
|
|
156
|
+
# 💾 Sauvegarde du cube ENVI
|
|
157
|
+
envi.save_image(
|
|
158
|
+
hdr_path,
|
|
159
|
+
datacube,
|
|
160
|
+
dtype=np.float32,
|
|
161
|
+
metadata={"wavelength": list(map(str, wavelengths))}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
print(f"[OK] Cube ENVI sauvegardé dans : {save_path}")
|
|
165
|
+
print(f" - Fichier HDR : {hdr_path}")
|
|
166
|
+
|
|
167
|
+
# 🔁 Retourne le chemin pour d'autres usages
|
|
168
|
+
return save_path
|
|
169
|
+
|
|
170
|
+
def py2ms(self, datacube, wavelengths, save_gerbil_name):
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
py2ms allows to save ONE-PIX data into Gerbil format http://gerbilvis.org/
|
|
174
|
+
a window appears to select the directory where the hyperspectral data will be saved in gebril format
|
|
175
|
+
Input:
|
|
176
|
+
save_gerbil_name : the name of the saved data into gerbil format (without .txt extension)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
save_gerbil_name : str
|
|
182
|
+
the name of the saved data into gerbil format (without .txt extension).
|
|
183
|
+
datacube : array
|
|
184
|
+
datacube to export into Gerbil format.
|
|
185
|
+
wavelengths : array
|
|
186
|
+
Sampled wavelengths associated to the measured datacube.
|
|
187
|
+
|
|
188
|
+
Returns
|
|
189
|
+
-------
|
|
190
|
+
None.
|
|
191
|
+
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
save_path = filedialog.askdirectory(title="Open the save directory")
|
|
195
|
+
maxval = datacube.max()
|
|
196
|
+
minval = datacube.min()
|
|
197
|
+
Range = maxval - minval
|
|
198
|
+
datacube = (datacube + minval) * (255 / Range)
|
|
199
|
+
|
|
200
|
+
fid = open(save_path + "\\" + save_gerbil_name + ".txt", "w")
|
|
201
|
+
os.mkdir(save_path + "\\" + save_gerbil_name)
|
|
202
|
+
nz = np.shape(datacube)
|
|
203
|
+
|
|
204
|
+
fid.write("{0} {1} \n".format(nz[2], save_gerbil_name + "\\"))
|
|
205
|
+
|
|
206
|
+
for i in range(0, nz[2]):
|
|
207
|
+
filename = "{0}_{1}.png".format(save_gerbil_name, i)
|
|
208
|
+
cv2.imwrite(
|
|
209
|
+
save_path + "\\" + save_gerbil_name + "\\" + filename, datacube[:, :, i]
|
|
210
|
+
)
|
|
211
|
+
fid.write("{0} {1}\n".format(filename, wavelengths[i]))
|
|
212
|
+
|
|
213
|
+
fid.close()
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: onepix-fis-common-function
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Common FIS functions for ONE-PIX imaging methods
|
|
5
|
+
Author: M. Ribes
|
|
6
|
+
Author-email: "G. Russias" <contact@photonics-open-projects.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://gitlab.com/ton_repo/onepix_fis_common_function
|
|
9
|
+
Project-URL: Source, https://gitlab.com/ton_repo/onepix_fis_common_function
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: importlib-metadata; python_version < "3.10"
|
|
13
|
+
|
|
14
|
+
# fis_common_functions
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Getting started
|
|
19
|
+
|
|
20
|
+
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
|
|
21
|
+
|
|
22
|
+
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
|
|
23
|
+
|
|
24
|
+
## Add your files
|
|
25
|
+
|
|
26
|
+
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
|
|
27
|
+
- [ ] [Add files using the command line](https://docs.gitlab.com/topics/git/add_files/#add-files-to-a-git-repository) or push an existing Git repository with the following command:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
cd existing_repo
|
|
31
|
+
git remote add origin https://gitlab.com/private1120225/onepix_plugins/fis_common_functions.git
|
|
32
|
+
git branch -M main
|
|
33
|
+
git push -uf origin main
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Integrate with your tools
|
|
37
|
+
|
|
38
|
+
- [ ] [Set up project integrations](https://gitlab.com/private1120225/onepix_plugins/fis_common_functions/-/settings/integrations)
|
|
39
|
+
|
|
40
|
+
## Collaborate with your team
|
|
41
|
+
|
|
42
|
+
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
|
|
43
|
+
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
|
44
|
+
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
|
45
|
+
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
|
46
|
+
- [ ] [Set auto-merge](https://docs.gitlab.com/user/project/merge_requests/auto_merge/)
|
|
47
|
+
|
|
48
|
+
## Test and Deploy
|
|
49
|
+
|
|
50
|
+
Use the built-in continuous integration in GitLab.
|
|
51
|
+
|
|
52
|
+
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/)
|
|
53
|
+
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
|
54
|
+
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
|
55
|
+
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
|
56
|
+
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
|
57
|
+
|
|
58
|
+
***
|
|
59
|
+
|
|
60
|
+
# Editing this README
|
|
61
|
+
|
|
62
|
+
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
|
|
63
|
+
|
|
64
|
+
## Suggestions for a good README
|
|
65
|
+
|
|
66
|
+
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
|
|
67
|
+
|
|
68
|
+
## Name
|
|
69
|
+
Choose a self-explaining name for your project.
|
|
70
|
+
|
|
71
|
+
## Description
|
|
72
|
+
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
|
|
73
|
+
|
|
74
|
+
## Badges
|
|
75
|
+
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
|
76
|
+
|
|
77
|
+
## Visuals
|
|
78
|
+
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
|
79
|
+
|
|
80
|
+
## Installation
|
|
81
|
+
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
|
|
85
|
+
|
|
86
|
+
## Support
|
|
87
|
+
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
|
88
|
+
|
|
89
|
+
## Roadmap
|
|
90
|
+
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
|
91
|
+
|
|
92
|
+
## Contributing
|
|
93
|
+
State if you are open to contributions and what your requirements are for accepting them.
|
|
94
|
+
|
|
95
|
+
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
|
|
96
|
+
|
|
97
|
+
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
|
|
98
|
+
|
|
99
|
+
## Authors and acknowledgment
|
|
100
|
+
Show your appreciation to those who have contributed to the project.
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
For open source projects, say how it is licensed.
|
|
104
|
+
|
|
105
|
+
## Project status
|
|
106
|
+
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
fis_common_function/CIE1931-2deg-XYZ.csv,sha256=Qla56gWPu-IBbsw_TRaHRI_K3cV_osWJ5I9QG7HymUY,4750
|
|
2
|
+
fis_common_function/FIS_common_acquisition.py,sha256=T3wD5vxQyrx_BqC9xoasZdAC6iVVmEfHE-vxUV8fHOE,1079
|
|
3
|
+
fis_common_function/FIS_common_analysis.py,sha256=KQreAqCVOR7VkVQaRWpQBeVxAumZTR63N93HuUxZYCY,23037
|
|
4
|
+
fis_common_function/FIS_common_param.json,sha256=pi7ntvDFuYMCr91exFNycEOUGlaMQViOvz5vkqHlPVw,21
|
|
5
|
+
fis_common_function/FIS_common_reconstruction.py,sha256=S_2tarSAyQDbQWjWtmeLSjTW2ixBTkrwhxuuz3Fkyu8,8344
|
|
6
|
+
fis_common_function/__init__.py,sha256=gLXQR6TNS7cb9ph_nG_zKd0myL9SkqahQFOEZrZ80m0,93
|
|
7
|
+
onepix_fis_common_function-0.1.0.dist-info/METADATA,sha256=SsjbjyaE7rOdJvomMcOVjOa6ECLynx1amqbZHfei3Hk,6802
|
|
8
|
+
onepix_fis_common_function-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
onepix_fis_common_function-0.1.0.dist-info/entry_points.txt,sha256=61G3jgkMrDRno3IwL_yCzYwTI7K057cgdiJcPmLZywE,58
|
|
10
|
+
onepix_fis_common_function-0.1.0.dist-info/top_level.txt,sha256=vWbU_MlnhGxpnI7HmcbCnfVn0J87_RgZXYAYEnrD33A,20
|
|
11
|
+
onepix_fis_common_function-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fis_common_function
|