"""RGBmap module for FRESCO package.
"""
import matplotlib
matplotlib.use('TkAgg')
import numpy as np
import spectral.io.envi as envi
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
import rasterio
from rasterio.control import GroundControlPoint
from rasterio.transform import from_gcps
from rasterio.crs import CRS
[docs]
def open_raw(path_img_IF , path_hdr_IF , path_img_SR , path_hdr_SR):
"""
Function to open the spectral parameters and spectral reflectance datacubes.
Parameters
----------
path_img_IF : string
Complete path of the reflectance datacube.
path_hdr_IF : string
Complete path of the reflectance datacube header.
path_img_SR : string
Complete path of the spectral parameters datacube.
path_hdr_SR : string
Complete path of the spectral parameter datacube header.
Returns
-------
img : python spectral.io.bsqfile.BsqFile object
Spectral reflectances datacube.
img_sr : python spectral.io.bsqfile.BsqFile object
Spectral parameters datacube.
wavelength : list
List of the CRISM observation wavelengths.
sr_names : list
List of the CRISM spectral parameters.
"""
img = envi.open(path_hdr_IF , path_img_IF)
img_sr = envi.open(path_hdr_SR , path_img_SR)
wavelength = np.array(img.metadata['wavelength']).astype(float)
sr_names = img_sr.metadata['band names']
return img , img_sr , wavelength , sr_names
[docs]
class RGBImageManipulator():
"""
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.
Parameters
----------
img : python spectral.io.bsqfile.BsqFile object
The spectral reflectance datacube.
img_sr : python spectral.io.bsqfile.BsqFile object
The spectral parameter datacube.
preset : string
The preset as given in Viviano-Beck et al., 2014 (https://doi.org/10.1002/2014JE004627). If None then no preset is selected.
ch1 : int
If preset is None, this is the index of the spectral parameter used in the Red channel.
ch2 : int
If preset is None, this is the index of the spectral parameter used in the Green channel.
ch3 : int
If preset is None, this is the index of the spectral parameter used in the Blue channel.
wavelength : list or array
The wavelengths detected by CRISM.
"""
def __init__(self , img , img_sr , preset , ch1 , ch2 , ch3 , wavelength):
self.img = img
self.img_sr = img_sr
self.preset = preset
self.ch1 = ch1
self.ch2 = ch2
self.ch3 = ch3
self.w = wavelength
[docs]
def RGB_Viviano_Beck_2014(self, preset):
"""
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).
Used in the function RGB_map_slider.
Parameters
----------
preset : string
Pre-selected spectral parameters for RGB map generation as given in Viviano-Beck et al., 2014.
Returns
-------
names : list
List of the spectral parameters corresponding to the RGB channels.
descriptions : string
Description of the RGB map as given in Viviano-Beck et al., 2014.
indexes : list
Indexes of the spectral parameters for the RGB map as they sorted in the spectral parameter datacube.
"""
sr = ['R770', 'RBR', 'BD530_2', 'SH600_2', 'SH770', 'BD640_2', 'BD860_2', 'BD920_2', 'RPEAK1',
'BDI1000VIS', 'R440', 'IRR1', 'BDI1000IR', 'OLINDEX3', 'R1330', 'BD1300', 'LCPINDEX2',
'HCPINDEX2', 'VAR', 'ISLOPE1', 'BD1400', 'BD1435', 'BD1500_2', 'ICER1_2', 'BD1750_2',
'BD1900_2', 'BD1900R2', 'BDI2000', 'BD2100_2', 'BD2165', 'BD2190', 'MIN2200', 'BD2210_2',
'D2200', 'BD2230', 'BD2250', 'MIN2250', 'BD2265', 'BD2290', 'D2300', 'BD2355', 'SINDEX2',
'ICER2_2', 'MIN2295_2480', 'MIN2345_2537', 'BD2500_2', 'BD3000', 'BD3100', 'BD3200',
'BD3400_2', 'CINDEX2', 'BD2600', 'IRR2', 'IRR3', 'R530', 'R600', 'R1080', 'R1506', 'R2529',
'R3920']
# Descriptions from Viaviano-Beck et al. 2014
descriptions = {'TRU': 'Enhanced true color.' ,
'VNA': 'Photometric correct I/F, used to correlate morphology and spectral variation.' ,
'FEM': 'Fe minerals absorption.' ,
'FM2': 'Complementary info on Fe minerals.' ,
'FAL': 'False color image. Red/orange -> olivine rich , blue/green -> clay , green -> carbonates , gray/brown -> basaltic .' ,
'MAF': 'Mafic mineralogy. Green/cyan -> Low Ca Pyroxene , blue/magenta -> High Ca Pyroxene.' ,
'HYD': 'Hydrated mineralogy. Magenta -> polyhydrated sulfates , yellow/green -> monohydrated sulfates , blue -> hydrated minerals .' ,
'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.' ,
'PFM': 'Phyllosilicates with Fe and Mg. Red/yellow -> prehnite, chlorite, epidote or Ca/Fe carbonate, cyan -> Fe/Mg smectites of Mg carbonates.' ,
'PAL': 'Phyllosilicates qith Al. Red/yellow -> Al smectites or hydrated silica, cyan -> alunite, light/white -> kaolinite.' ,
'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.' ,
'ICE': 'H20/CO2 ice. Red -> sulfates, clays, hydr. silica, carbonate, water ice. Green -> Water ice, Blue -> carbon dioxide ice.' ,
'IC2': 'Complementary information about H20/CO2 ice. Red -> ice free surface, green -> water ice, blue -> carbon dioxide ice.' ,
'CHL': 'Info about chloride deposits. Yellow/green -> hydr. minerals and phyllosilicates, blue -> chloride.' ,
'CAR': 'Info about Mg carbonate minerals. Red/magenta -> Fe/Mg phyllosilicates, yellowish-white-bluish -> Mg carbonates, blue -> sulfates, clays, hydr. silica or carbonate.' ,
'CR2': 'Info to distinguish carbonate minerals. Red/magenta -> Mg-carbonates, green/cyan -> Fe/Ca carbonates.'
}
names = {'TRU' : [sr.index('R600') , sr.index('R530') , sr.index('R440')] ,
'VNA' : [sr.index('R770') , sr.index('R770') , sr.index('R770')] ,
'FEM' : [sr.index('BD530_2') , sr.index('SH600_2') , sr.index('BDI1000VIS')] ,
'FM2' : [sr.index('BD530_2') , sr.index('BD920_2') , sr.index('BDI1000VIS')] ,
'FAL' : [sr.index('R2529') , sr.index('R1506') , sr.index('R1080')] ,
'MAF' : [sr.index('OLINDEX3') , sr.index('LCPINDEX2') , sr.index('HCPINDEX2')] ,
'HYD' : [sr.index('SINDEX2') , sr.index('BD2100_2') , sr.index('BD1900_2')] ,
'PHY' : [sr.index('D2300') , sr.index('D2200') , sr.index('BD1900R2')] ,
'PFM' : [sr.index('BD2355') , sr.index('D2300') , sr.index('BD2290')] ,
'PAL' : [sr.index('BD2210_2') , sr.index('BD2190') , sr.index('BD2165')] ,
'HYS' : [sr.index('MIN2250') , sr.index('BD2250') , sr.index('BD1900R2')] ,
'ICE' : [sr.index('BD1900_2') , sr.index('BD1500_2') , sr.index('BD1435')] ,
'IC2' : [sr.index('R3920') , sr.index('BD1500_2') , sr.index('BD1435')] ,
'CHL' : [sr.index('ISLOPE1') , sr.index('BD3000') , sr.index('IRR2')] ,
'CAR' : [sr.index('D2300') , sr.index('BD2500_2') , sr.index('BD1900_2')] ,
'CR2' : [sr.index('MIN2295_2480') , sr.index('MIN2345_2537') , sr.index('CINDEX2')]
}
print( names[self.preset] , [ sr[names[self.preset][0]] , sr[names[self.preset][1]] , sr[names[self.preset][2]] ])
return names[self.preset] , descriptions[self.preset] , [ sr[names[self.preset][0]] , sr[names[self.preset][1]] , sr[names[self.preset][2]] ]
[docs]
def f(self, RGB, min_R, min_G, min_B, max_R, max_G, max_B, clip=False):
"""
This function uploads the RGB map during the customization. This function is only used inside RGB_map_slider.
Parameters
----------
RGB : 3-dim array
The RGB image to be updated.
min_R : float
Minimum value for the Red channel.
min_G : float
Minimum value for the Green channel.
min_B : float
Minimum value for the Blue channel.
max_R : float
Maximum value for the Red channel.
max_G : float
Maximum value for the Green channel.
max_B : float
Maximum value for the Blue channel.
clip : bool
If True it clips the negative values. Default if False.
Returns
-------
RGB_raw : 3-dim array
Updated RGB map
"""
stretches_min = [min_R , min_G , min_B]
stretches_max = [max_R , max_G , max_B]
stretch = np.array([ [min_R , max_R] , [min_G , max_G] , [min_B , max_B] ])
RGBs = np.where(RGB < stretch[:,0], stretches_min, RGB)
RGBs = np.where(RGB > stretch[:,1], stretches_max, RGB)
RGB_raw = (RGBs - stretch[:,0]) / (stretch[:,1] - stretch[:,0])
if clip == True:
RGB_raw = np.clip(RGB_raw , 0. , 1.)
return RGB_raw
[docs]
def area(self, hist, bins, line1, line2):
"""
Function to calulate the percentile area of a histogram between two lines. Only used inside RGB_map_slider.
Parameters
----------
hist : array
Histogram of the value of the RGB channel.
bins : int
Number of bins of the histogram.
line1 : matplotlib.axes.Axes
The line object of the left percentiles.
line2 : matplotlib.axes.Axes
The line object of the right percentiles.
Returns
-------
Areas : list
List containing the area of the histogram on the left of the first line and on the right of the second line.
"""
area1 , area2 , total_area = 0 , 0 , 0
for S in range(len(bins)-1):
F = S + 1
total_area += hist[S]*(bins[F] - bins[S])
if bins[S] <= line1.get_xdata():
area1 += hist[S]*(bins[F] - bins[S])
if bins[S] <= line2.get_xdata():
area2 += hist[S]*(bins[F] - bins[S])
area_inferior = area1*100/total_area
area_superior = area2*100/total_area
return [area_inferior , area_superior]
[docs]
def Labels(self):
L = {'TRU': [['--' , 'white' , 'white'] , ['--' , 'white' , 'white']],#'Enhanced true color.' ,
'VNA': [['--' , 'white' , 'white'] , ['--' , 'white' , 'white']],#'Photometric correct I/F, used to correlate morphology and spectral variation.' ,
'FEM': [['--' , 'white' , 'white'] , ['--' , 'white' , 'white']],#'Fe minerals absorption.' ,
'FM2': [['--' , 'white' , 'white'] , ['--' , 'white' , 'white']],#'Complementary info on Fe minerals.' ,
'FAL': [['Olivine' , 'red' , 'orange'] , ['Clay' , 'mediumseagreen' , 'blue'] , ['Carbonates' , 'green' , 'green'] , ['Basalts' , 'gray' , 'brown']] ,
'MAF': [['Olivine' , 'red' , 'red'] , ['Low Ca Pyroxene' , 'green' , 'cyan'] , ['High Ca Pyroxene' , 'blue' , 'magenta']],
'HYD': [['Polyhydrated sulfates' , 'magenta' , 'magenta'] , ['Monohydrated sulfates' , 'yellow' , 'green'] , ['Hydrated minerals' , 'blue' , 'blue']],
'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']],
'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']] ,
'PAL': [['Al smectites/Hydrated silica' , 'red' , 'yellow'] , ['Alunite' , 'cyan' , 'cyan'] , ['Kaolinite' , 'white' , 'white']] ,
'HYS': [['Hydrated silica' , 'red' , 'yellow'] , ['Jarosite' , 'yellow' , 'yellow'] , ['Al-OH minerals' , 'cyan' , 'cyan'] , ['Other hydrates' , 'blue' , 'blue']] ,
'ICE': [['Other hydrates' , 'red' , 'red'] , ['H2O ice' , 'green' , 'green'] , ['CO2 ice' , 'blue' , 'blue'] ] ,
'IC2': [['Ice free surface' , 'red' ,'red'] , ['H2O ice' , 'green' , 'green'] , ['CO2 ice' , 'blue' , 'blue'] ] ,
'CHL': [['Hydr. mineral and phyllosilicates' , 'yellow' , 'green'] , ['Chloride' , 'blue' , 'blue']],
'CAR': [['Fe/Mg phyllosilicates' , 'red' , 'magenta'] , ['Mg carbonates' , 'yellow' , 'lightblue'] , ['Other hydrates' , 'blue' , 'blue'] ] ,
'CR2': [['Mg carbonates' , 'red' , 'magenta'] , ['Fe/Ca carbonates' , 'green' , 'cyan']]
}
return L
[docs]
def RGBmapmake(self, FALSE , bi ,clip , cumhist ,preset_true_colors , use_false_color ,
R_min_in = [0,1] , R_max_in = [0,1] ,
G_min_in = [0,1] , G_max_in = [0,1] ,
B_min_in = [0,1] , B_max_in = [0,1] ,
init_R = [0,1] , init_G = [0,1] , init_B = [0,1] ,
slider_step = 0.005 ,
slider_height = 0.02 , slider_width = 0.25 , slider_spacing = 0.05):
"""
Function to perform the customization of the RGB map by moving apposite sliders to enhance constrast between different colors (i.e. spectral parameters).
To finish the customization it is sufficent to close the plot window.
Parameters
----------
FALSE : 3-dim array
RGB image to be used as background.
bi : int
Number of bins to divide the histograms into.
clip : bool
If to clip the negative values or not.
cumhist : bool
If to use cumulative histograms instead of frequency histograms.
preset_true_colors : string
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.
use_false_color : bool
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).
R_min_in : list of two floats
Minimum and maximum possible values for the minimum of the Red channel. Default is [0,1].
R_max_in : list of two floats
Minimum and maximum possible values for the maximum of the Red channel. Default is [0,1].
G_min_in : list of two floats
Minimum and maximum possible values for the minimum of the Green channel. Default is [0,1].
G_max_in : list of two floats
Minimum and maximum possible values for the maximum of the Green channel. Default is [0,1].
B_min_in : list of two floats
Minimum and maximum possible values for the minimum of the Blue channel. Default is [0,1].
B_max_in : list of two floats
Minimum and maximum possible values for the maximum of the Blue channel. Default is [0,1].
init_R : list of two floats
Initial values of the Red channel. Default is [0,1].
init_G : list of two floats
Initial values of the Green channel. Default is [0,1].
init_B : list of two floats
Initial values of the Blue channel. Default is [0,1].
slider_step : float
Minimum step done by the slider while interacting with it. Default is 0.005.
slider_height : float
Height at which the sliders are posed in the plot. Default is 0.02.
slider_width : float
Width of the sliders. Default is 0.25.
slider_spacing : float
Space between the sliders. Default is 0.05.
Returns
-------
RGB : 3-dim array
Final RGB map in the form of a numpy array.
stretches : list of float
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.
"""
fig , ax = plt.subplots(1 , 4 , figsize = [10,5] , gridspec_kw={'width_ratios': [1,1,1,3]})
# setting initial stretch to float
init_min_R, init_min_G, init_min_B = float(init_R[0]) , float(init_G[0]) , float(init_B[0])
init_max_R, init_max_G, init_max_B = float(init_R[1]) , float(init_G[1]) , float(init_B[1])
# Creation of the RGB image either from coded ones or from custom select indices
if self.preset == None:
sr = ['R770', 'RBR', 'BD530_2', 'SH600_2', 'SH770', 'BD640_2', 'BD860_2', 'BD920_2', 'RPEAK1',
'BDI1000VIS', 'R440', 'IRR1', 'BDI1000IR', 'OLINDEX3', 'R1330', 'BD1300', 'LCPINDEX2',
'HCPINDEX2', 'VAR', 'ISLOPE1', 'BD1400', 'BD1435', 'BD1500_2', 'ICER1_2', 'BD1750_2',
'BD1900_2', 'BD1900R2', 'BDI2000', 'BD2100_2', 'BD2165', 'BD2190', 'MIN2200', 'BD2210_2',
'D2200', 'BD2230', 'BD2250', 'MIN2250', 'BD2265', 'BD2290', 'D2300', 'BD2355', 'SINDEX2',
'ICER2_2', 'MIN2295_2480', 'MIN2345_2537', 'BD2500_2', 'BD3000', 'BD3100', 'BD3200',
'BD3400_2', 'CINDEX2', 'BD2600', 'IRR2', 'IRR3', 'R530', 'R600', 'R1080', 'R1506', 'R2529',
'R3920']
sr_channels_number = [self.ch1 , self.ch2 , self.ch3]
RGB_raw = np.array(self.img_sr[:,:,sr_channels_number].squeeze()).astype(float)
ax[0].set_xlabel(sr[self.ch1] , color = 'red' , fontsize = 15)
ax[1].set_xlabel(sr[self.ch2] , color = 'green' , fontsize = 15)
ax[2].set_xlabel(sr[self.ch3] , color = 'blue' , fontsize = 15)
else:
sr_channels_number, descr , pars = self.RGB_Viviano_Beck_2014(self.preset)
print(sr_channels_number , pars)
RGB_raw = np.array(self.img_sr[:,:,sr_channels_number].squeeze())
ax[0].set_xlabel(str(pars[0]) , color = 'red' , fontsize = 15)
ax[1].set_xlabel(str(pars[1]) , color = 'green' , fontsize = 15)
ax[2].set_xlabel(str(pars[2]) , color = 'blue' , fontsize = 15)
if use_false_color == False:
# Create the true or false color image
true_channels , true_descr , true_pars = self.RGB_Viviano_Beck_2014(preset_true_colors)
RGB_true_colors = np.array(self.img_sr[:,:,true_channels].squeeze())
RGB_true_colors[RGB_true_colors > 1.] = np.nan
else:
RGB_true_colors = FALSE
# Remove of the borders and definition of image in function of the stretches
RGB_raw[RGB_raw > 1.] = np.nan
RGB_browse_norm = self.f(RGB_raw, init_R[0] , init_G[0] , init_B[0] ,
init_R[1] , init_G[1] , init_B[1] , clip = clip)
# Choosing between cumulative histogram and frequency histogram
if cumhist == True:
hR , biR , _ = ax[0].hist( RGB_raw[:,:,0].ravel() , bi , color = 'r' , alpha = 0.5 , density = True , cumulative = True )
hG , biG , _ = ax[1].hist( RGB_raw[:,:,1].ravel() , bi , color = 'g' , alpha = 0.5 , density = True , cumulative = True )
hB , biB , _ = ax[2].hist( RGB_raw[:,:,2].ravel() , bi , color = 'b' , alpha = 0.5 , density = True , cumulative = True )
else:
hR , biR , _ = ax[0].hist( RGB_raw[:,:,0].ravel() , bi , color = 'r' , alpha = 0.5 , density = True , cumulative = False )
hG , biG , _ = ax[1].hist( RGB_raw[:,:,1].ravel() , bi , color = 'g' , alpha = 0.5 , density = True , cumulative = False )
hB , biB , _ = ax[2].hist( RGB_raw[:,:,2].ravel() , bi , color = 'b' , alpha = 0.5 , density = True , cumulative = False )
# Setting histogram graphs limits:
ax[0].set_xlim(biR[np.argmin(biR)+1] , np.max(biR))
ax[1].set_xlim(biG[np.argmin(biG)+1] , np.max(biG))
ax[2].set_xlim(biB[np.argmin(biB)+1] , np.max(biB))
ax[0].set_ylim(0 , np.max(np.sort(hR)[0:len(hR)-1]))
ax[1].set_ylim(0 , np.max(np.sort(hG)[0:len(hG)-1]))
ax[2].set_ylim(0 , np.max(np.sort(hB)[0:len(hB)-1]))
# Definition of global variables
final_min_R , final_min_G , final_min_B = None , None , None
final_max_R , final_max_G , final_max_B = None , None , None
RGB_final = None
im = ax[3].imshow(RGB_browse_norm)
im.set_data(RGB_browse_norm)
# Plot of the vertical lines on the histograms and sacing their data
vr = ax[0].axvline( init_R[0] , color = 'r' , linestyle = '--' , linewidth = 0.6 )
vR = ax[0].axvline( init_R[1] , color = 'r' , linestyle = '--' , linewidth = 0.6 )
vg = ax[1].axvline( init_G[0] , color = 'g' , linestyle = '--' , linewidth = 0.6 )
vG = ax[1].axvline( init_G[1] , color = 'g' , linestyle = '--' , linewidth = 0.6 )
vb = ax[2].axvline( init_B[0] , color = 'b' , linestyle = '--' , linewidth = 0.6 )
vB = ax[2].axvline( init_B[1] , color = 'b' , linestyle = '--' , linewidth = 0.6 )
vr.set_xdata([init_R[0]])
vR.set_xdata([init_R[1]])
vg.set_xdata([init_G[0]])
vG.set_xdata([init_G[1]])
vb.set_xdata([init_B[0]])
vB.set_xdata([init_B[1]])
# Percentile calculations
areaR , areaG , areaB = self.area(hR , biR , vr , vR) , self.area(hG , biG , vg , vG) , self.area(hB , biB , vb , vB)
aRmin , aRmax = areaR[0] , areaR[1]
aGmin , aGmax = areaG[0] , areaG[1]
aBmin , aBmax = areaB[0] , areaB[1]
# Writing percentiles and saving the value
textR = ax[0].set_title(f"{aRmin:.2f}%" + " - " + f"{aRmax:.2f}%" , color = 'r' , fontsize = 8)
textG = ax[1].set_title(f"{aGmin:.2f}%" + " - " + f"{aGmax:.2f}%" , color = 'g' , fontsize = 8)
textB = ax[2].set_title(f"{aGmin:.2f}%" + " - " + f"{aRmax:.2f}%" , color = 'b' , fontsize = 8)
textR.set_text(f"Perc.: {aRmax:.2f}%")
textG.set_text(f"Perc.: {aGmax:.2f}%")
textB.set_text(f"Perc.: {aBmax:.2f}%")
# Red channel sliders
ax_min_R = plt.axes([0.05, 0.9, slider_width, slider_height])
ax_max_R = plt.axes([0.05, 0.9 + slider_spacing, slider_width, slider_height])
min_R_slider = Slider(ax_min_R, label = 'min R',
valmin = R_min_in[0], valmax = R_min_in[1],
valinit = init_min_R , valstep = slider_step , color = 'r')
max_R_slider = Slider(ax_max_R, label = 'max R',
valmin = R_max_in[0], valmax = R_max_in[1],
valinit = init_max_R , valstep = slider_step , color = 'r')
# Green channel sliders
ax_min_G = plt.axes([0.37, 0.9, slider_width, slider_height])
ax_max_G = plt.axes([0.37, 0.9 + slider_spacing, slider_width, slider_height])
min_G_slider = Slider(ax_min_G, label = 'min G',
valmin = G_min_in[0], valmax = G_min_in[1],
valinit = init_min_G , valstep = slider_step , color = 'g')
max_G_slider = Slider(ax_max_G, label = 'max G',
valmin = G_max_in[0], valmax = G_max_in[1],
valinit = init_max_G , valstep = slider_step , color = 'g')
# Blue channel sliders
ax_min_B = plt.axes([0.69, 0.9, slider_width, slider_height])
ax_max_B = plt.axes([0.69, 0.9 + slider_spacing, slider_width, slider_height])
min_B_slider = Slider(ax_min_B, label = 'min B',
valmin = B_min_in[0] , valmax = B_min_in[1],
valinit = init_min_B , valstep = slider_step , color = 'b')
max_B_slider = Slider(ax_max_B, label = 'max B',
valmin = B_max_in[0], valmax = B_max_in[1],
valinit = init_max_B , valstep = slider_step , color = 'b')
def on_button_clicked(event):
nonlocal is_toggled
is_toggled = not is_toggled
# Define a function to update the image displayed in the plot
is_toggled = True # Varibale to see the state of the button
def update_image(event):
nonlocal is_toggled, RGB_final, final_min_R, final_min_G, final_min_B, final_max_R, final_max_G, final_max_B
if not is_toggled:
# Change the image to a new state
img_new = RGB_true_colors # define the new image here
im.set_data(img_new)
fig.canvas.draw_idle()
else:
# Retrieve current slider values
min_R, max_R = min_R_slider.val, max_R_slider.val
min_G, max_G = min_G_slider.val, max_G_slider.val
min_B, max_B = min_B_slider.val, max_B_slider.val
# Update final slider values
final_min_R, final_min_G, final_min_B = min_R, min_G, min_B
final_max_R, final_max_G, final_max_B = max_R, max_G, max_B
# Calculate updated RGB image
RGB_browse_norm = self.f(RGB_raw, min_R, min_G, min_B, max_R, max_G, max_B, clip)
im.set_data(RGB_browse_norm)
RGB_final = RGB_browse_norm
# Calculate updated vertical lines on histograms
vr.set_xdata([min_R])
vR.set_xdata([max_R])
vg.set_xdata([min_G])
vG.set_xdata([max_G])
vb.set_xdata([min_B])
vB.set_xdata([max_B])
# Calculate updated percentiles
areaR , areaG , areaB = self.area(hR , biR , vr , vR) , self.area(hG , biG , vg , vG) , self.area(hB , biB , vb , vB)
aRmin , aRmax = areaR[0] , areaR[1]
aGmin , aGmax = areaG[0] , areaG[1]
aBmin , aBmax = areaB[0] , areaB[1]
textR.set_text(f"{aRmin:.2f}%" + " - " + f"{aRmax:.2f}%")
textG.set_text(f"{aGmin:.2f}%" + " - " + f"{aGmax:.2f}%")
textB.set_text(f"{aBmin:.2f}%" + " - " + f"{aBmax:.2f}%")
# Change the image back to the original state
fig.canvas.draw_idle()
# Create button
button_ax = plt.axes([0.8, 0.82, 0.1, 0.07])
button = Button(button_ax, 'Change image')
button.on_clicked(on_button_clicked)
button.on_clicked(update_image)
# Set up update function to be called when sliders are changed
for slider in [min_R_slider, max_R_slider, min_G_slider, max_G_slider, min_B_slider, max_B_slider]:
slider.on_changed(update_image)
# Set up title
if self.preset != None:
ax[3].set_title(self.preset)
if self.preset != 'TRU' and self.preset != 'VNA' and self.preset != 'FEM' and self.preset != 'FM2':
patches = []
labe = []
L = self.Labels()[self.preset]
for i in range(len(L)):
lab , c1 , c2 = L[i][0] , L[i][1] , L[i][2]
labe.append(str(' ') + lab)
m1, = plt.plot([], [], c=c1 , marker='s', markersize=20,
fillstyle='left', linestyle='none')
m2, = plt.plot([], [], c=c2 , marker='s', markersize=20,
fillstyle='right', linestyle='none')
patches.append((m1,m2))
plt.legend(( patches ), (labe), numpoints = 1, labelspacing = 2, ncol = 2 , frameon=False ,
handletextpad = 1, handlelength = 1.5 , columnspacing = 5 ,
loc = 'lower right', fontsize = 10 , bbox_to_anchor = (1,-11) )
else:
print('No legend')
else:
ax[3].set_title('Custom map')
ax[3].axis('off')
# Creating mask to set NaN values outside the images to be shown in white instead of black
MASK = np.isnan(RGB_browse_norm[:,:,0])
ALPHA = np.zeros((RGB_browse_norm.shape[0] , RGB_browse_norm.shape[1]))
ALPHA[MASK] = 1
ax[3].imshow(ALPHA , alpha = ALPHA , cmap = 'Greys_r')
plt.show()
self.RGB_final = RGB_final
# Return the final map
return self.RGB_final , [final_min_R , final_min_G , final_min_B , final_max_R , final_max_G , final_max_B]
[docs]
def savemap(self , name , folder = None , extension = None , show = False):
"""
Function to save the RGB map into 3 separated .txt files (one for each channel) and, if given, also in a specific image format.
Parameters
----------
name : string
Name with which the RGB map is saved.
folder : string
Path of the folder in which the RGB map is saved. If None it saves in home directory. Default is None.
extension : string, '.tif' or '.png' or '.jpg' or '.pdf'
Extension in which to save the RGB map as an image. If None it does not save it as an image. Default is None.
show : bool
To show or not the resulting RGB map saved as an image. Deafult is None.
Returns
-------
None
"""
R = np.zeros((self.RGB_final.shape[0] , self.RGB_final.shape[1]))
G = np.zeros((self.RGB_final.shape[0] , self.RGB_final.shape[1]))
B = np.zeros((self.RGB_final.shape[0] , self.RGB_final.shape[1]))
for i in range(self.RGB_final.shape[0]):
for j in range(self.RGB_final.shape[1]):
R[i,j] = self.RGB_final[i,j,0]
G[i,j] = self.RGB_final[i,j,1]
B[i,j] = self.RGB_final[i,j,2]
if folder == None:
np.savetxt(name + '_R.txt' , R)
np.savetxt(name + '_G.txt' , G)
np.savetxt(name + '_B.txt' , B)
else:
np.savetxt(folder + name + '_R.txt' , R)
np.savetxt(folder + name + '_G.txt' , G)
np.savetxt(folder + name + '_B.txt' , B)
if extension == '.tif' or extension == '.png' or extension == '.jpg' or extension == '.pdf' or extension == '.svg':
#IMG = self.RGB_final.resize( (self.img.shape[1] , self.img.shape[0] , 3) )
#self.RGB_final.save(folder + name + extension , bbox_inches = 'tight' , pad_inches = 0 , transparent = True)
plt.imshow(self.RGB_final)
plt.axis('off')
plt.tight_layout()
plt.savefig(folder + name + extension , bbox_inches = 'tight' , pad_inches = 0 , transparent = True)
if show == True:
plt.show()
else:
plt.close()
return
[docs]
def georeference(self , wkt , name , folder , min_lat , max_lat , westernmost_lon , easternmost_lon):
'''
Function to georeference a .tif image to a given reference frame.
Parameters
----------
wkt : string
Reference system in which the image has to be georeferenced.
Example for Mars --> wkt = "GEOGCRS[\"GCS_Mars_2000\",DATUM[\"D_Mars_2000\",
ELLIPSOID[\"Mars_2000_IAU_IAG\",3396190,169.894447223612,
LENGTHUNIT[\"metre\",1]]],PRIMEM[\"Reference_Meridian\",0,
ANGLEUNIT[\"degree\",0.0174532925199433]],CS[ellipsoidal,2],
AXIS[\"geodetic latitude (Lat)\",north,ORDER[1],
ANGLEUNIT[\"degree\",0.0174532925199433]],
AXIS[\"geodetic longitude (Lon)\",east,ORDER[2],
ANGLEUNIT[\"degree\",0.0174532925199433]],
USAGE[SCOPE[\"unknown\"],AREA[\"World\"],
BBOX[-90,-180,90,180]],ID[\"ESRI\",104905]]"
name : string
Name of the .tif image you want to georeference.
fodler : string
Path of the image you want to save.
min_lat : float
Southernmost latitude of the image.
max_lat : float
Northernmost latitude of the image.
westernmost_lon : float
Westernmost longitude of the image.
Easternmost_lon : float
Easternmost longitude of the image.
Returns
-------
None
'''
height = self.img_sr.shape[0] #lines
width = self.img_sr.shape[1] #samples
tl = GroundControlPoint(0, 0, westernmost_lon, max_lat) #top left
bl = GroundControlPoint(height, 0, westernmost_lon, min_lat) #bottom left
br = GroundControlPoint(height, width, easternmost_lon, min_lat) #bottom right
tr = GroundControlPoint(0, width, easternmost_lon, max_lat) #top right
gcps = [tl, bl, br, tr]
transform = from_gcps(gcps)
crs = CRS.from_wkt(wkt)
with rasterio.open(folder + name + '.tif', 'r+') as ds:
ds.crs = crs
ds.transform = transform