pyFRESCO 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.
pyfresco/RGBmap.py ADDED
@@ -0,0 +1,672 @@
1
+ """RGBmap module for FRESCO package.
2
+ """
3
+
4
+ import matplotlib
5
+ matplotlib.use('TkAgg')
6
+ import numpy as np
7
+ import spectral.io.envi as envi
8
+ import matplotlib.pyplot as plt
9
+ from matplotlib.widgets import Slider, Button
10
+ import rasterio
11
+ from rasterio.control import GroundControlPoint
12
+ from rasterio.transform import from_gcps
13
+ from rasterio.crs import CRS
14
+
15
+ def open_raw(path_img_IF , path_hdr_IF , path_img_SR , path_hdr_SR):
16
+ """
17
+ Function to open the spectral parameters and spectral reflectance datacubes.
18
+
19
+ Parameters
20
+ ----------
21
+ path_img_IF : string
22
+ Complete path of the reflectance datacube.
23
+ path_hdr_IF : string
24
+ Complete path of the reflectance datacube header.
25
+ path_img_SR : string
26
+ Complete path of the spectral parameters datacube.
27
+ path_hdr_SR : string
28
+ Complete path of the spectral parameter datacube header.
29
+
30
+ Returns
31
+ -------
32
+ img : python spectral.io.bsqfile.BsqFile object
33
+ Spectral reflectances datacube.
34
+ img_sr : python spectral.io.bsqfile.BsqFile object
35
+ Spectral parameters datacube.
36
+ wavelength : list
37
+ List of the CRISM observation wavelengths.
38
+ sr_names : list
39
+ List of the CRISM spectral parameters.
40
+ """
41
+
42
+ img = envi.open(path_hdr_IF , path_img_IF)
43
+
44
+ img_sr = envi.open(path_hdr_SR , path_img_SR)
45
+
46
+ wavelength = np.array(img.metadata['wavelength']).astype(float)
47
+
48
+ sr_names = img_sr.metadata['band names']
49
+
50
+ return img , img_sr , wavelength , sr_names
51
+
52
+ class RGBImageManipulator():
53
+ """
54
+ Class to generate the RGB map. With this class it is possible to manually control the contrast of each RGB channel and, after the RGB map is produced, it possible to save it both in .txt and .tiff and also to georeferentiate it.
55
+
56
+ Parameters
57
+ ----------
58
+ img : python spectral.io.bsqfile.BsqFile object
59
+ The spectral reflectance datacube.
60
+ img_sr : python spectral.io.bsqfile.BsqFile object
61
+ The spectral parameter datacube.
62
+ preset : string
63
+ The preset as given in Viviano-Beck et al., 2014 (https://doi.org/10.1002/2014JE004627). If None then no preset is selected.
64
+ ch1 : int
65
+ If preset is None, this is the index of the spectral parameter used in the Red channel.
66
+ ch2 : int
67
+ If preset is None, this is the index of the spectral parameter used in the Green channel.
68
+ ch3 : int
69
+ If preset is None, this is the index of the spectral parameter used in the Blue channel.
70
+ wavelength : list or array
71
+ The wavelengths detected by CRISM.
72
+ """
73
+ def __init__(self , img , img_sr , preset , ch1 , ch2 , ch3 , wavelength):
74
+
75
+ self.img = img
76
+ self.img_sr = img_sr
77
+ self.preset = preset
78
+ self.ch1 = ch1
79
+ self.ch2 = ch2
80
+ self.ch3 = ch3
81
+ self.w = wavelength
82
+
83
+ def RGB_Viviano_Beck_2014(self, preset):
84
+ """
85
+ This function uploads descriptions, spectral parameter names and combinations for RGB map as given in Viviano-Beck et al., 2014 (https://doi.org/10.1002/2014JE004627).
86
+ Used in the function RGB_map_slider.
87
+
88
+ Parameters
89
+ ----------
90
+ preset : string
91
+ Pre-selected spectral parameters for RGB map generation as given in Viviano-Beck et al., 2014.
92
+
93
+ Returns
94
+ -------
95
+ names : list
96
+ List of the spectral parameters corresponding to the RGB channels.
97
+ descriptions : string
98
+ Description of the RGB map as given in Viviano-Beck et al., 2014.
99
+ indexes : list
100
+ Indexes of the spectral parameters for the RGB map as they sorted in the spectral parameter datacube.
101
+ """
102
+ sr = ['R770', 'RBR', 'BD530_2', 'SH600_2', 'SH770', 'BD640_2', 'BD860_2', 'BD920_2', 'RPEAK1',
103
+ 'BDI1000VIS', 'R440', 'IRR1', 'BDI1000IR', 'OLINDEX3', 'R1330', 'BD1300', 'LCPINDEX2',
104
+ 'HCPINDEX2', 'VAR', 'ISLOPE1', 'BD1400', 'BD1435', 'BD1500_2', 'ICER1_2', 'BD1750_2',
105
+ 'BD1900_2', 'BD1900R2', 'BDI2000', 'BD2100_2', 'BD2165', 'BD2190', 'MIN2200', 'BD2210_2',
106
+ 'D2200', 'BD2230', 'BD2250', 'MIN2250', 'BD2265', 'BD2290', 'D2300', 'BD2355', 'SINDEX2',
107
+ 'ICER2_2', 'MIN2295_2480', 'MIN2345_2537', 'BD2500_2', 'BD3000', 'BD3100', 'BD3200',
108
+ 'BD3400_2', 'CINDEX2', 'BD2600', 'IRR2', 'IRR3', 'R530', 'R600', 'R1080', 'R1506', 'R2529',
109
+ 'R3920']
110
+
111
+ # Descriptions from Viaviano-Beck et al. 2014
112
+
113
+ descriptions = {'TRU': 'Enhanced true color.' ,
114
+ 'VNA': 'Photometric correct I/F, used to correlate morphology and spectral variation.' ,
115
+ 'FEM': 'Fe minerals absorption.' ,
116
+ 'FM2': 'Complementary info on Fe minerals.' ,
117
+ 'FAL': 'False color image. Red/orange -> olivine rich , blue/green -> clay , green -> carbonates , gray/brown -> basaltic .' ,
118
+ 'MAF': 'Mafic mineralogy. Green/cyan -> Low Ca Pyroxene , blue/magenta -> High Ca Pyroxene.' ,
119
+ 'HYD': 'Hydrated mineralogy. Magenta -> polyhydrated sulfates , yellow/green -> monohydrated sulfates , blue -> hydrated minerals .' ,
120
+ 'PHY': 'Phyllosilicates. Red -> non-hydr. Fe/Mg-OH minerals , magenta -> hydr. Fe/Mg-OH inerals , green -> non-hydr. Al/Si-OH minerals , cyan -> hydr. Al/Si-OH minerals , blue -> other hydrated minerals.' ,
121
+ 'PFM': 'Phyllosilicates with Fe and Mg. Red/yellow -> prehnite, chlorite, epidote or Ca/Fe carbonate, cyan -> Fe/Mg smectites of Mg carbonates.' ,
122
+ 'PAL': 'Phyllosilicates qith Al. Red/yellow -> Al smectites or hydrated silica, cyan -> alunite, light/white -> kaolinite.' ,
123
+ 'HYS': 'Hydrated silica. Used to differentiate between Al-phyl and hyd. silica. Light red/yellow -> hydrated silica, yellow -> jarosite, cyan -> Al-OH minerals, blue -> sulfates, clays, hydr. silica, carbonates or water ice.' ,
124
+ 'ICE': 'H20/CO2 ice. Red -> sulfates, clays, hydr. silica, carbonate, water ice. Green -> Water ice, Blue -> carbon dioxide ice.' ,
125
+ 'IC2': 'Complementary information about H20/CO2 ice. Red -> ice free surface, green -> water ice, blue -> carbon dioxide ice.' ,
126
+ 'CHL': 'Info about chloride deposits. Yellow/green -> hydr. minerals and phyllosilicates, blue -> chloride.' ,
127
+ 'CAR': 'Info about Mg carbonate minerals. Red/magenta -> Fe/Mg phyllosilicates, yellowish-white-bluish -> Mg carbonates, blue -> sulfates, clays, hydr. silica or carbonate.' ,
128
+ 'CR2': 'Info to distinguish carbonate minerals. Red/magenta -> Mg-carbonates, green/cyan -> Fe/Ca carbonates.'
129
+ }
130
+
131
+ names = {'TRU' : [sr.index('R600') , sr.index('R530') , sr.index('R440')] ,
132
+ 'VNA' : [sr.index('R770') , sr.index('R770') , sr.index('R770')] ,
133
+ 'FEM' : [sr.index('BD530_2') , sr.index('SH600_2') , sr.index('BDI1000VIS')] ,
134
+ 'FM2' : [sr.index('BD530_2') , sr.index('BD920_2') , sr.index('BDI1000VIS')] ,
135
+ 'FAL' : [sr.index('R2529') , sr.index('R1506') , sr.index('R1080')] ,
136
+ 'MAF' : [sr.index('OLINDEX3') , sr.index('LCPINDEX2') , sr.index('HCPINDEX2')] ,
137
+ 'HYD' : [sr.index('SINDEX2') , sr.index('BD2100_2') , sr.index('BD1900_2')] ,
138
+ 'PHY' : [sr.index('D2300') , sr.index('D2200') , sr.index('BD1900R2')] ,
139
+ 'PFM' : [sr.index('BD2355') , sr.index('D2300') , sr.index('BD2290')] ,
140
+ 'PAL' : [sr.index('BD2210_2') , sr.index('BD2190') , sr.index('BD2165')] ,
141
+ 'HYS' : [sr.index('MIN2250') , sr.index('BD2250') , sr.index('BD1900R2')] ,
142
+ 'ICE' : [sr.index('BD1900_2') , sr.index('BD1500_2') , sr.index('BD1435')] ,
143
+ 'IC2' : [sr.index('R3920') , sr.index('BD1500_2') , sr.index('BD1435')] ,
144
+ 'CHL' : [sr.index('ISLOPE1') , sr.index('BD3000') , sr.index('IRR2')] ,
145
+ 'CAR' : [sr.index('D2300') , sr.index('BD2500_2') , sr.index('BD1900_2')] ,
146
+ 'CR2' : [sr.index('MIN2295_2480') , sr.index('MIN2345_2537') , sr.index('CINDEX2')]
147
+ }
148
+
149
+ print( names[self.preset] , [ sr[names[self.preset][0]] , sr[names[self.preset][1]] , sr[names[self.preset][2]] ])
150
+ return names[self.preset] , descriptions[self.preset] , [ sr[names[self.preset][0]] , sr[names[self.preset][1]] , sr[names[self.preset][2]] ]
151
+
152
+ def f(self, RGB, min_R, min_G, min_B, max_R, max_G, max_B, clip=False):
153
+ """
154
+ This function uploads the RGB map during the customization. This function is only used inside RGB_map_slider.
155
+
156
+ Parameters
157
+ ----------
158
+ RGB : 3-dim array
159
+ The RGB image to be updated.
160
+ min_R : float
161
+ Minimum value for the Red channel.
162
+ min_G : float
163
+ Minimum value for the Green channel.
164
+ min_B : float
165
+ Minimum value for the Blue channel.
166
+ max_R : float
167
+ Maximum value for the Red channel.
168
+ max_G : float
169
+ Maximum value for the Green channel.
170
+ max_B : float
171
+ Maximum value for the Blue channel.
172
+ clip : bool
173
+ If True it clips the negative values. Default if False.
174
+
175
+ Returns
176
+ -------
177
+ RGB_raw : 3-dim array
178
+ Updated RGB map
179
+ """
180
+ stretches_min = [min_R , min_G , min_B]
181
+ stretches_max = [max_R , max_G , max_B]
182
+
183
+ stretch = np.array([ [min_R , max_R] , [min_G , max_G] , [min_B , max_B] ])
184
+
185
+ RGBs = np.where(RGB < stretch[:,0], stretches_min, RGB)
186
+ RGBs = np.where(RGB > stretch[:,1], stretches_max, RGB)
187
+
188
+ RGB_raw = (RGBs - stretch[:,0]) / (stretch[:,1] - stretch[:,0])
189
+
190
+ if clip == True:
191
+
192
+ RGB_raw = np.clip(RGB_raw , 0. , 1.)
193
+
194
+ return RGB_raw
195
+
196
+ def area(self, hist, bins, line1, line2):
197
+ """
198
+ Function to calulate the percentile area of a histogram between two lines. Only used inside RGB_map_slider.
199
+
200
+ Parameters
201
+ ----------
202
+ hist : array
203
+ Histogram of the value of the RGB channel.
204
+ bins : int
205
+ Number of bins of the histogram.
206
+ line1 : matplotlib.axes.Axes
207
+ The line object of the left percentiles.
208
+ line2 : matplotlib.axes.Axes
209
+ The line object of the right percentiles.
210
+
211
+ Returns
212
+ -------
213
+ Areas : list
214
+ List containing the area of the histogram on the left of the first line and on the right of the second line.
215
+ """
216
+ area1 , area2 , total_area = 0 , 0 , 0
217
+ for S in range(len(bins)-1):
218
+
219
+ F = S + 1
220
+
221
+ total_area += hist[S]*(bins[F] - bins[S])
222
+
223
+ if bins[S] <= line1.get_xdata():
224
+ area1 += hist[S]*(bins[F] - bins[S])
225
+ if bins[S] <= line2.get_xdata():
226
+ area2 += hist[S]*(bins[F] - bins[S])
227
+
228
+ area_inferior = area1*100/total_area
229
+ area_superior = area2*100/total_area
230
+
231
+ return [area_inferior , area_superior]
232
+
233
+ def Labels(self):
234
+ L = {'TRU': [['--' , 'white' , 'white'] , ['--' , 'white' , 'white']],#'Enhanced true color.' ,
235
+ 'VNA': [['--' , 'white' , 'white'] , ['--' , 'white' , 'white']],#'Photometric correct I/F, used to correlate morphology and spectral variation.' ,
236
+ 'FEM': [['--' , 'white' , 'white'] , ['--' , 'white' , 'white']],#'Fe minerals absorption.' ,
237
+ 'FM2': [['--' , 'white' , 'white'] , ['--' , 'white' , 'white']],#'Complementary info on Fe minerals.' ,
238
+ 'FAL': [['Olivine' , 'red' , 'orange'] , ['Clay' , 'mediumseagreen' , 'blue'] , ['Carbonates' , 'green' , 'green'] , ['Basalts' , 'gray' , 'brown']] ,
239
+ 'MAF': [['Olivine' , 'red' , 'red'] , ['Low Ca Pyroxene' , 'green' , 'cyan'] , ['High Ca Pyroxene' , 'blue' , 'magenta']],
240
+ 'HYD': [['Polyhydrated sulfates' , 'magenta' , 'magenta'] , ['Monohydrated sulfates' , 'yellow' , 'green'] , ['Hydrated minerals' , 'blue' , 'blue']],
241
+ 'PHY': [['Non hydrated Fe/Mg-OH' , 'red' , 'red'] , ['Hydrated Fe/Mg-OH' , 'magenta' , 'magenta'] , ['Non hydrated Al/Si-OH' , 'green' , 'green'] , ['Hydrated Al/Si-OH' , 'cyan' , 'cyan'] , ['Hydrated minerals' , 'blue' , 'blue']],
242
+ 'PFM': [['Prehnite' , 'red' , 'yellow'] , ['Chlorite' , 'red' , 'yellow'] , ['Epidote' , 'red' , 'yellow'] , ['Ca/Fe carbonate', 'red' , 'yellow'] , ['Fe/Mg smectites / Mg carbonates' , 'cyan' , 'cyan'] , ['Kaolinite' , 'white' , 'white']] ,
243
+ 'PAL': [['Al smectites/Hydrated silica' , 'red' , 'yellow'] , ['Alunite' , 'cyan' , 'cyan'] , ['Kaolinite' , 'white' , 'white']] ,
244
+ 'HYS': [['Hydrated silica' , 'red' , 'yellow'] , ['Jarosite' , 'yellow' , 'yellow'] , ['Al-OH minerals' , 'cyan' , 'cyan'] , ['Other hydrates' , 'blue' , 'blue']] ,
245
+ 'ICE': [['Other hydrates' , 'red' , 'red'] , ['H2O ice' , 'green' , 'green'] , ['CO2 ice' , 'blue' , 'blue'] ] ,
246
+ 'IC2': [['Ice free surface' , 'red' ,'red'] , ['H2O ice' , 'green' , 'green'] , ['CO2 ice' , 'blue' , 'blue'] ] ,
247
+ 'CHL': [['Hydr. mineral and phyllosilicates' , 'yellow' , 'green'] , ['Chloride' , 'blue' , 'blue']],
248
+ 'CAR': [['Fe/Mg phyllosilicates' , 'red' , 'magenta'] , ['Mg carbonates' , 'yellow' , 'lightblue'] , ['Other hydrates' , 'blue' , 'blue'] ] ,
249
+ 'CR2': [['Mg carbonates' , 'red' , 'magenta'] , ['Fe/Ca carbonates' , 'green' , 'cyan']]
250
+ }
251
+ return L
252
+
253
+ def RGBmapmake(self, FALSE , bi ,clip , cumhist ,preset_true_colors , use_false_color ,
254
+ R_min_in = [0,1] , R_max_in = [0,1] ,
255
+ G_min_in = [0,1] , G_max_in = [0,1] ,
256
+ B_min_in = [0,1] , B_max_in = [0,1] ,
257
+ init_R = [0,1] , init_G = [0,1] , init_B = [0,1] ,
258
+ slider_step = 0.005 ,
259
+ slider_height = 0.02 , slider_width = 0.25 , slider_spacing = 0.05):
260
+ """
261
+ Function to perform the customization of the RGB map by moving apposite sliders to enhance constrast between different colors (i.e. spectral parameters).
262
+ To finish the customization it is sufficent to close the plot window.
263
+
264
+ Parameters
265
+ ----------
266
+ FALSE : 3-dim array
267
+ RGB image to be used as background.
268
+ bi : int
269
+ Number of bins to divide the histograms into.
270
+ clip : bool
271
+ If to clip the negative values or not.
272
+ cumhist : bool
273
+ If to use cumulative histograms instead of frequency histograms.
274
+ preset_true_colors : string
275
+ If use_false_color is False, here insert the preset name from Viviano-Beck et al., 2014 that wants to be used as background true color RGB image.
276
+ use_false_color : bool
277
+ If to use a pre-computed RGB background map or to select another one from Viviano-Beck et al., 2014 as it is (without stretching).
278
+ R_min_in : list of two floats
279
+ Minimum and maximum possible values for the minimum of the Red channel. Default is [0,1].
280
+ R_max_in : list of two floats
281
+ Minimum and maximum possible values for the maximum of the Red channel. Default is [0,1].
282
+ G_min_in : list of two floats
283
+ Minimum and maximum possible values for the minimum of the Green channel. Default is [0,1].
284
+ G_max_in : list of two floats
285
+ Minimum and maximum possible values for the maximum of the Green channel. Default is [0,1].
286
+ B_min_in : list of two floats
287
+ Minimum and maximum possible values for the minimum of the Blue channel. Default is [0,1].
288
+ B_max_in : list of two floats
289
+ Minimum and maximum possible values for the maximum of the Blue channel. Default is [0,1].
290
+ init_R : list of two floats
291
+ Initial values of the Red channel. Default is [0,1].
292
+ init_G : list of two floats
293
+ Initial values of the Green channel. Default is [0,1].
294
+ init_B : list of two floats
295
+ Initial values of the Blue channel. Default is [0,1].
296
+ slider_step : float
297
+ Minimum step done by the slider while interacting with it. Default is 0.005.
298
+ slider_height : float
299
+ Height at which the sliders are posed in the plot. Default is 0.02.
300
+ slider_width : float
301
+ Width of the sliders. Default is 0.25.
302
+ slider_spacing : float
303
+ Space between the sliders. Default is 0.05.
304
+
305
+ Returns
306
+ -------
307
+ RGB : 3-dim array
308
+ Final RGB map in the form of a numpy array.
309
+ stretches : list of float
310
+ Final stretch values in the following order: final_min_R , final_min_G , final_min_B , final_max_R , final_max_G , final_max_B.
311
+ """
312
+ fig , ax = plt.subplots(1 , 4 , figsize = [10,5] , gridspec_kw={'width_ratios': [1,1,1,3]})
313
+
314
+ # setting initial stretch to float
315
+ init_min_R, init_min_G, init_min_B = float(init_R[0]) , float(init_G[0]) , float(init_B[0])
316
+ init_max_R, init_max_G, init_max_B = float(init_R[1]) , float(init_G[1]) , float(init_B[1])
317
+
318
+ # Creation of the RGB image either from coded ones or from custom select indices
319
+ if self.preset == None:
320
+
321
+ sr = ['R770', 'RBR', 'BD530_2', 'SH600_2', 'SH770', 'BD640_2', 'BD860_2', 'BD920_2', 'RPEAK1',
322
+ 'BDI1000VIS', 'R440', 'IRR1', 'BDI1000IR', 'OLINDEX3', 'R1330', 'BD1300', 'LCPINDEX2',
323
+ 'HCPINDEX2', 'VAR', 'ISLOPE1', 'BD1400', 'BD1435', 'BD1500_2', 'ICER1_2', 'BD1750_2',
324
+ 'BD1900_2', 'BD1900R2', 'BDI2000', 'BD2100_2', 'BD2165', 'BD2190', 'MIN2200', 'BD2210_2',
325
+ 'D2200', 'BD2230', 'BD2250', 'MIN2250', 'BD2265', 'BD2290', 'D2300', 'BD2355', 'SINDEX2',
326
+ 'ICER2_2', 'MIN2295_2480', 'MIN2345_2537', 'BD2500_2', 'BD3000', 'BD3100', 'BD3200',
327
+ 'BD3400_2', 'CINDEX2', 'BD2600', 'IRR2', 'IRR3', 'R530', 'R600', 'R1080', 'R1506', 'R2529',
328
+ 'R3920']
329
+
330
+ sr_channels_number = [self.ch1 , self.ch2 , self.ch3]
331
+ RGB_raw = np.array(self.img_sr[:,:,sr_channels_number].squeeze()).astype(float)
332
+
333
+ ax[0].set_xlabel(sr[self.ch1] , color = 'red' , fontsize = 15)
334
+ ax[1].set_xlabel(sr[self.ch2] , color = 'green' , fontsize = 15)
335
+ ax[2].set_xlabel(sr[self.ch3] , color = 'blue' , fontsize = 15)
336
+
337
+ else:
338
+ sr_channels_number, descr , pars = self.RGB_Viviano_Beck_2014(self.preset)
339
+ print(sr_channels_number , pars)
340
+ RGB_raw = np.array(self.img_sr[:,:,sr_channels_number].squeeze())
341
+
342
+ ax[0].set_xlabel(str(pars[0]) , color = 'red' , fontsize = 15)
343
+ ax[1].set_xlabel(str(pars[1]) , color = 'green' , fontsize = 15)
344
+ ax[2].set_xlabel(str(pars[2]) , color = 'blue' , fontsize = 15)
345
+
346
+ if use_false_color == False:
347
+
348
+ # Create the true or false color image
349
+ true_channels , true_descr , true_pars = self.RGB_Viviano_Beck_2014(preset_true_colors)
350
+ RGB_true_colors = np.array(self.img_sr[:,:,true_channels].squeeze())
351
+ RGB_true_colors[RGB_true_colors > 1.] = np.nan
352
+
353
+ else:
354
+
355
+ RGB_true_colors = FALSE
356
+
357
+ # Remove of the borders and definition of image in function of the stretches
358
+ RGB_raw[RGB_raw > 1.] = np.nan
359
+ RGB_browse_norm = self.f(RGB_raw, init_R[0] , init_G[0] , init_B[0] ,
360
+ init_R[1] , init_G[1] , init_B[1] , clip = clip)
361
+
362
+ # Choosing between cumulative histogram and frequency histogram
363
+ if cumhist == True:
364
+ hR , biR , _ = ax[0].hist( RGB_raw[:,:,0].ravel() , bi , color = 'r' , alpha = 0.5 , density = True , cumulative = True )
365
+ hG , biG , _ = ax[1].hist( RGB_raw[:,:,1].ravel() , bi , color = 'g' , alpha = 0.5 , density = True , cumulative = True )
366
+ hB , biB , _ = ax[2].hist( RGB_raw[:,:,2].ravel() , bi , color = 'b' , alpha = 0.5 , density = True , cumulative = True )
367
+ else:
368
+ hR , biR , _ = ax[0].hist( RGB_raw[:,:,0].ravel() , bi , color = 'r' , alpha = 0.5 , density = True , cumulative = False )
369
+ hG , biG , _ = ax[1].hist( RGB_raw[:,:,1].ravel() , bi , color = 'g' , alpha = 0.5 , density = True , cumulative = False )
370
+ hB , biB , _ = ax[2].hist( RGB_raw[:,:,2].ravel() , bi , color = 'b' , alpha = 0.5 , density = True , cumulative = False )
371
+
372
+ # Setting histogram graphs limits:
373
+ ax[0].set_xlim(biR[np.argmin(biR)+1] , np.max(biR))
374
+ ax[1].set_xlim(biG[np.argmin(biG)+1] , np.max(biG))
375
+ ax[2].set_xlim(biB[np.argmin(biB)+1] , np.max(biB))
376
+ ax[0].set_ylim(0 , np.max(np.sort(hR)[0:len(hR)-1]))
377
+ ax[1].set_ylim(0 , np.max(np.sort(hG)[0:len(hG)-1]))
378
+ ax[2].set_ylim(0 , np.max(np.sort(hB)[0:len(hB)-1]))
379
+
380
+ # Definition of global variables
381
+ final_min_R , final_min_G , final_min_B = None , None , None
382
+ final_max_R , final_max_G , final_max_B = None , None , None
383
+ RGB_final = None
384
+
385
+ im = ax[3].imshow(RGB_browse_norm)
386
+
387
+ im.set_data(RGB_browse_norm)
388
+
389
+ # Plot of the vertical lines on the histograms and sacing their data
390
+ vr = ax[0].axvline( init_R[0] , color = 'r' , linestyle = '--' , linewidth = 0.6 )
391
+ vR = ax[0].axvline( init_R[1] , color = 'r' , linestyle = '--' , linewidth = 0.6 )
392
+ vg = ax[1].axvline( init_G[0] , color = 'g' , linestyle = '--' , linewidth = 0.6 )
393
+ vG = ax[1].axvline( init_G[1] , color = 'g' , linestyle = '--' , linewidth = 0.6 )
394
+ vb = ax[2].axvline( init_B[0] , color = 'b' , linestyle = '--' , linewidth = 0.6 )
395
+ vB = ax[2].axvline( init_B[1] , color = 'b' , linestyle = '--' , linewidth = 0.6 )
396
+
397
+ vr.set_xdata([init_R[0]])
398
+ vR.set_xdata([init_R[1]])
399
+ vg.set_xdata([init_G[0]])
400
+ vG.set_xdata([init_G[1]])
401
+ vb.set_xdata([init_B[0]])
402
+ vB.set_xdata([init_B[1]])
403
+
404
+ # Percentile calculations
405
+ areaR , areaG , areaB = self.area(hR , biR , vr , vR) , self.area(hG , biG , vg , vG) , self.area(hB , biB , vb , vB)
406
+
407
+ aRmin , aRmax = areaR[0] , areaR[1]
408
+ aGmin , aGmax = areaG[0] , areaG[1]
409
+ aBmin , aBmax = areaB[0] , areaB[1]
410
+
411
+ # Writing percentiles and saving the value
412
+ textR = ax[0].set_title(f"{aRmin:.2f}%" + " - " + f"{aRmax:.2f}%" , color = 'r' , fontsize = 8)
413
+ textG = ax[1].set_title(f"{aGmin:.2f}%" + " - " + f"{aGmax:.2f}%" , color = 'g' , fontsize = 8)
414
+ textB = ax[2].set_title(f"{aGmin:.2f}%" + " - " + f"{aRmax:.2f}%" , color = 'b' , fontsize = 8)
415
+
416
+ textR.set_text(f"Perc.: {aRmax:.2f}%")
417
+ textG.set_text(f"Perc.: {aGmax:.2f}%")
418
+ textB.set_text(f"Perc.: {aBmax:.2f}%")
419
+
420
+ # Red channel sliders
421
+ ax_min_R = plt.axes([0.05, 0.9, slider_width, slider_height])
422
+ ax_max_R = plt.axes([0.05, 0.9 + slider_spacing, slider_width, slider_height])
423
+
424
+ min_R_slider = Slider(ax_min_R, label = 'min R',
425
+ valmin = R_min_in[0], valmax = R_min_in[1],
426
+ valinit = init_min_R , valstep = slider_step , color = 'r')
427
+
428
+ max_R_slider = Slider(ax_max_R, label = 'max R',
429
+ valmin = R_max_in[0], valmax = R_max_in[1],
430
+ valinit = init_max_R , valstep = slider_step , color = 'r')
431
+
432
+ # Green channel sliders
433
+ ax_min_G = plt.axes([0.37, 0.9, slider_width, slider_height])
434
+ ax_max_G = plt.axes([0.37, 0.9 + slider_spacing, slider_width, slider_height])
435
+
436
+ min_G_slider = Slider(ax_min_G, label = 'min G',
437
+ valmin = G_min_in[0], valmax = G_min_in[1],
438
+ valinit = init_min_G , valstep = slider_step , color = 'g')
439
+
440
+ max_G_slider = Slider(ax_max_G, label = 'max G',
441
+ valmin = G_max_in[0], valmax = G_max_in[1],
442
+ valinit = init_max_G , valstep = slider_step , color = 'g')
443
+
444
+ # Blue channel sliders
445
+ ax_min_B = plt.axes([0.69, 0.9, slider_width, slider_height])
446
+ ax_max_B = plt.axes([0.69, 0.9 + slider_spacing, slider_width, slider_height])
447
+
448
+ min_B_slider = Slider(ax_min_B, label = 'min B',
449
+ valmin = B_min_in[0] , valmax = B_min_in[1],
450
+ valinit = init_min_B , valstep = slider_step , color = 'b')
451
+
452
+ max_B_slider = Slider(ax_max_B, label = 'max B',
453
+ valmin = B_max_in[0], valmax = B_max_in[1],
454
+ valinit = init_max_B , valstep = slider_step , color = 'b')
455
+
456
+ def on_button_clicked(event):
457
+ nonlocal is_toggled
458
+ is_toggled = not is_toggled
459
+
460
+ # Define a function to update the image displayed in the plot
461
+ is_toggled = True # Varibale to see the state of the button
462
+ def update_image(event):
463
+ nonlocal is_toggled, RGB_final, final_min_R, final_min_G, final_min_B, final_max_R, final_max_G, final_max_B
464
+ if not is_toggled:
465
+ # Change the image to a new state
466
+ img_new = RGB_true_colors # define the new image here
467
+ im.set_data(img_new)
468
+ fig.canvas.draw_idle()
469
+ else:
470
+ # Retrieve current slider values
471
+ min_R, max_R = min_R_slider.val, max_R_slider.val
472
+ min_G, max_G = min_G_slider.val, max_G_slider.val
473
+ min_B, max_B = min_B_slider.val, max_B_slider.val
474
+
475
+ # Update final slider values
476
+ final_min_R, final_min_G, final_min_B = min_R, min_G, min_B
477
+ final_max_R, final_max_G, final_max_B = max_R, max_G, max_B
478
+
479
+ # Calculate updated RGB image
480
+ RGB_browse_norm = self.f(RGB_raw, min_R, min_G, min_B, max_R, max_G, max_B, clip)
481
+ im.set_data(RGB_browse_norm)
482
+
483
+ RGB_final = RGB_browse_norm
484
+
485
+ # Calculate updated vertical lines on histograms
486
+ vr.set_xdata([min_R])
487
+ vR.set_xdata([max_R])
488
+ vg.set_xdata([min_G])
489
+ vG.set_xdata([max_G])
490
+ vb.set_xdata([min_B])
491
+ vB.set_xdata([max_B])
492
+
493
+ # Calculate updated percentiles
494
+ areaR , areaG , areaB = self.area(hR , biR , vr , vR) , self.area(hG , biG , vg , vG) , self.area(hB , biB , vb , vB)
495
+
496
+ aRmin , aRmax = areaR[0] , areaR[1]
497
+ aGmin , aGmax = areaG[0] , areaG[1]
498
+ aBmin , aBmax = areaB[0] , areaB[1]
499
+
500
+ textR.set_text(f"{aRmin:.2f}%" + " - " + f"{aRmax:.2f}%")
501
+ textG.set_text(f"{aGmin:.2f}%" + " - " + f"{aGmax:.2f}%")
502
+ textB.set_text(f"{aBmin:.2f}%" + " - " + f"{aBmax:.2f}%")
503
+
504
+ # Change the image back to the original state
505
+ fig.canvas.draw_idle()
506
+
507
+ # Create button
508
+ button_ax = plt.axes([0.8, 0.82, 0.1, 0.07])
509
+ button = Button(button_ax, 'Change image')
510
+ button.on_clicked(on_button_clicked)
511
+ button.on_clicked(update_image)
512
+
513
+ # Set up update function to be called when sliders are changed
514
+ for slider in [min_R_slider, max_R_slider, min_G_slider, max_G_slider, min_B_slider, max_B_slider]:
515
+ slider.on_changed(update_image)
516
+
517
+ # Set up title
518
+ if self.preset != None:
519
+ ax[3].set_title(self.preset)
520
+ if self.preset != 'TRU' and self.preset != 'VNA' and self.preset != 'FEM' and self.preset != 'FM2':
521
+
522
+ patches = []
523
+ labe = []
524
+
525
+ L = self.Labels()[self.preset]
526
+
527
+ for i in range(len(L)):
528
+
529
+ lab , c1 , c2 = L[i][0] , L[i][1] , L[i][2]
530
+
531
+ labe.append(str(' ') + lab)
532
+
533
+ m1, = plt.plot([], [], c=c1 , marker='s', markersize=20,
534
+ fillstyle='left', linestyle='none')
535
+
536
+ m2, = plt.plot([], [], c=c2 , marker='s', markersize=20,
537
+ fillstyle='right', linestyle='none')
538
+
539
+ patches.append((m1,m2))
540
+
541
+ plt.legend(( patches ), (labe), numpoints = 1, labelspacing = 2, ncol = 2 , frameon=False ,
542
+ handletextpad = 1, handlelength = 1.5 , columnspacing = 5 ,
543
+ loc = 'lower right', fontsize = 10 , bbox_to_anchor = (1,-11) )
544
+
545
+
546
+ else:
547
+ print('No legend')
548
+ else:
549
+ ax[3].set_title('Custom map')
550
+ ax[3].axis('off')
551
+
552
+ # Creating mask to set NaN values outside the images to be shown in white instead of black
553
+ MASK = np.isnan(RGB_browse_norm[:,:,0])
554
+ ALPHA = np.zeros((RGB_browse_norm.shape[0] , RGB_browse_norm.shape[1]))
555
+ ALPHA[MASK] = 1
556
+ ax[3].imshow(ALPHA , alpha = ALPHA , cmap = 'Greys_r')
557
+
558
+ plt.show()
559
+
560
+ self.RGB_final = RGB_final
561
+
562
+ # Return the final map
563
+ return self.RGB_final , [final_min_R , final_min_G , final_min_B , final_max_R , final_max_G , final_max_B]
564
+
565
+ def savemap(self , name , folder = None , extension = None , show = False):
566
+ """
567
+ Function to save the RGB map into 3 separated .txt files (one for each channel) and, if given, also in a specific image format.
568
+
569
+ Parameters
570
+ ----------
571
+ name : string
572
+ Name with which the RGB map is saved.
573
+ folder : string
574
+ Path of the folder in which the RGB map is saved. If None it saves in home directory. Default is None.
575
+ extension : string, '.tif' or '.png' or '.jpg' or '.pdf'
576
+ Extension in which to save the RGB map as an image. If None it does not save it as an image. Default is None.
577
+ show : bool
578
+ To show or not the resulting RGB map saved as an image. Deafult is None.
579
+
580
+ Returns
581
+ -------
582
+ None
583
+ """
584
+
585
+ R = np.zeros((self.RGB_final.shape[0] , self.RGB_final.shape[1]))
586
+ G = np.zeros((self.RGB_final.shape[0] , self.RGB_final.shape[1]))
587
+ B = np.zeros((self.RGB_final.shape[0] , self.RGB_final.shape[1]))
588
+
589
+ for i in range(self.RGB_final.shape[0]):
590
+ for j in range(self.RGB_final.shape[1]):
591
+ R[i,j] = self.RGB_final[i,j,0]
592
+ G[i,j] = self.RGB_final[i,j,1]
593
+ B[i,j] = self.RGB_final[i,j,2]
594
+
595
+ if folder == None:
596
+ np.savetxt(name + '_R.txt' , R)
597
+ np.savetxt(name + '_G.txt' , G)
598
+ np.savetxt(name + '_B.txt' , B)
599
+ else:
600
+ np.savetxt(folder + name + '_R.txt' , R)
601
+ np.savetxt(folder + name + '_G.txt' , G)
602
+ np.savetxt(folder + name + '_B.txt' , B)
603
+
604
+ if extension == '.tif' or extension == '.png' or extension == '.jpg' or extension == '.pdf' or extension == '.svg':
605
+
606
+ #IMG = self.RGB_final.resize( (self.img.shape[1] , self.img.shape[0] , 3) )
607
+
608
+ #self.RGB_final.save(folder + name + extension , bbox_inches = 'tight' , pad_inches = 0 , transparent = True)
609
+ plt.imshow(self.RGB_final)
610
+ plt.axis('off')
611
+ plt.tight_layout()
612
+ plt.savefig(folder + name + extension , bbox_inches = 'tight' , pad_inches = 0 , transparent = True)
613
+ if show == True:
614
+ plt.show()
615
+ else:
616
+ plt.close()
617
+
618
+ return
619
+
620
+ def georeference(self , wkt , name , folder , min_lat , max_lat , westernmost_lon , easternmost_lon):
621
+ '''
622
+ Function to georeference a .tif image to a given reference frame.
623
+
624
+ Parameters
625
+ ----------
626
+ wkt : string
627
+ Reference system in which the image has to be georeferenced.
628
+ Example for Mars --> wkt = "GEOGCRS[\"GCS_Mars_2000\",DATUM[\"D_Mars_2000\",
629
+ ELLIPSOID[\"Mars_2000_IAU_IAG\",3396190,169.894447223612,
630
+ LENGTHUNIT[\"metre\",1]]],PRIMEM[\"Reference_Meridian\",0,
631
+ ANGLEUNIT[\"degree\",0.0174532925199433]],CS[ellipsoidal,2],
632
+ AXIS[\"geodetic latitude (Lat)\",north,ORDER[1],
633
+ ANGLEUNIT[\"degree\",0.0174532925199433]],
634
+ AXIS[\"geodetic longitude (Lon)\",east,ORDER[2],
635
+ ANGLEUNIT[\"degree\",0.0174532925199433]],
636
+ USAGE[SCOPE[\"unknown\"],AREA[\"World\"],
637
+ BBOX[-90,-180,90,180]],ID[\"ESRI\",104905]]"
638
+ name : string
639
+ Name of the .tif image you want to georeference.
640
+ fodler : string
641
+ Path of the image you want to save.
642
+ min_lat : float
643
+ Southernmost latitude of the image.
644
+ max_lat : float
645
+ Northernmost latitude of the image.
646
+ westernmost_lon : float
647
+ Westernmost longitude of the image.
648
+ Easternmost_lon : float
649
+ Easternmost longitude of the image.
650
+
651
+ Returns
652
+ -------
653
+ None
654
+ '''
655
+
656
+ height = self.img_sr.shape[0] #lines
657
+ width = self.img_sr.shape[1] #samples
658
+
659
+ tl = GroundControlPoint(0, 0, westernmost_lon, max_lat) #top left
660
+ bl = GroundControlPoint(height, 0, westernmost_lon, min_lat) #bottom left
661
+ br = GroundControlPoint(height, width, easternmost_lon, min_lat) #bottom right
662
+ tr = GroundControlPoint(0, width, easternmost_lon, max_lat) #top right
663
+
664
+ gcps = [tl, bl, br, tr]
665
+
666
+ transform = from_gcps(gcps)
667
+ crs = CRS.from_wkt(wkt)
668
+
669
+ with rasterio.open(folder + name + '.tif', 'r+') as ds:
670
+ ds.crs = crs
671
+ ds.transform = transform
672
+