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.
@@ -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,3 @@
1
+ {
2
+ "spatial_res":2
3
+ }
@@ -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,7 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on Mon Apr 11 11:08:45 2022
4
+
5
+ @author: mribes
6
+ """
7
+
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [onepix.common]
2
+ fis_common_function = fis_common_function
@@ -0,0 +1 @@
1
+ fis_common_function