hylite.hyfeature
Fit and visualise individual hyperspectral features.
1""" 2Fit and visualise individual hyperspectral features. 3""" 4 5import numpy as np 6import matplotlib.pyplot as plt 7from scipy.optimize import least_squares 8from scipy.signal import argrelmin 9from tqdm import tqdm 10 11from gfit import gfit, initialise, evaluate 12 13class HyFeature(object): 14 """ 15 Utility class for representing and fitting individual or multiple absorption features. 16 """ 17 18 def __init__(self, name, pos, width, depth=1, data=None, color='g'): 19 """ 20 Args: 21 name (str) a name for this feature. 22 pos (float): the position of this feature (in nm). 23 width (float): the width of this feature (in nm). 24 data (ndarray): a real spectra associated with this feature (e.g. for feature fitting or from reference libraries). 25 Should be a numpy array such that data[0,:] gives wavelength and data[1,:] gives reflectance. 26 """ 27 28 self.name = name 29 self.pos = pos 30 self.width = width 31 self.depth = depth 32 self.color = color 33 self.data = data 34 self.mae = -1 35 self.strength = -1 36 self.components = None 37 self.endmembers = None 38 39 def get_start(self): 40 """ 41 Get start of feature. 42 43 Returns: 44 the feature position - 0.5 * feature width. 45 """ 46 47 return self.pos - self.width * 0.5 48 49 def get_end(self): 50 """ 51 Get approximate end of feature 52 53 Returns: 54 returns feature position - 0.5 * feature width. 55 """ 56 57 return self.pos + self.width * 0.5 58 59 60 ###################### 61 ## Feature models 62 ###################### 63 @classmethod 64 def gaussian(cls, _x, pos, width, depth): 65 """ 66 Static function for evaluating a gaussian feature model 67 68 Args: 69 x (ndarray): wavelengths (nanometres) to evaluate the feature over 70 pos (float): position for the gaussian function (nanometres) 71 width (float): width for the gaussian function. 72 depth (float): depth for the gaussian function (max to min) 73 offset (float): the vertical offset of the functions. Default is 1.0. 74 """ 75 return 1 - depth * np.exp( -(_x - pos)**2 / width ) 76 77 @classmethod 78 def multi_gauss(cls, x, pos, width, depth, asym=None): 79 """ 80 Static function for evaluating a multi-gaussian feature model 81 82 Args: 83 x (ndarray): wavelengths (nanometres) to evaluate the feature over 84 pos (list): a list of positions for each individual gaussian function (nanometres) 85 width (list): a list of widths for each individual gaussian function. 86 depth (list): a list of depths for each individual gaussian function (max to min) 87 asym (list): a list of feature asymmetries. The right-hand width will be calculated as: 88 w2 = asym * width. Default is 1.0. 89 """ 90 if asym is None: 91 asym = np.ones( len(width) ) 92 M = np.hstack( [[depth[i], pos[i], width[i], width[i]*asym[i]] for i in range(len(depth))] ) 93 y = evaluate( x, M, sym=False ) 94 return 1 - y 95 96 # noinspection PyDefaultArgument 97 def quick_plot(self, method='gauss', ax=None, label='top', lab_kwds={}, **kwds): 98 """ 99 Quickly plot this feature. 100 101 Args: 102 method (str): the method used to represent this feature. Options are: 103 104 - 'gauss' = represent using a gaussian function 105 - 'range' = draw vertical lines at pos - width / 2 and pos + width / 2. 106 - 'fill' = fill a rectangle in the region dominated by the feature with 'color' specifed in kwds. 107 - 'line' = plot a (vertical) line at the position of this feature. 108 - 'all' = plot with all of the above methods. 109 110 ax: an axis to add the plot to. If None (default) a new axis is created. 111 label (float): Label this feature (using it's name?). Options are None (no label), 'top', 'middle' or 'lower'. Or, 112 if an integer is passed, odd integers will be plotted as 'top' and even integers as 'lower'. 113 lab_kwds (dict): Dictionary of keywords to pass to plt.text( ... ) for controlling labels. 114 **kwds: Keywords are passed to ax.axvline(...) if method=='range' or ax.plot(...) otherwise. 115 116 Returns: 117 Tuple containing 118 119 - fig: the figure that was plotted to. 120 - ax: the axis that was plotted to. 121 """ 122 123 if ax is None: 124 fig, ax = plt.subplots() 125 126 # plot reference spectra and get _x for plotting 127 if self.data is not None: 128 _x = self.data[0, : ] 129 ax.plot(_x, self.data[1, :], color='k', **kwds) 130 else: 131 _x = np.linspace(self.pos - self.width, self.pos + self.width) 132 133 # set color 134 if 'c' in kwds: 135 kwds['color'] = kwds['c'] 136 del kwds['c'] 137 kwds['color'] = kwds.get('color', self.color) 138 139 # get _x for plotting 140 if 'range' in method.lower() or 'all' in method.lower(): 141 ax.axvline(self.pos - self.width / 2, **kwds) 142 ax.axvline(self.pos + self.width / 2, **kwds) 143 if 'line' in method.lower() or 'all' in method.lower(): 144 ax.axvline(self.pos, color='k', alpha=0.4) 145 if 'gauss' in method.lower() or 'all' in method.lower(): 146 if self.components is None: # plot single feature 147 _y = HyFeature.gaussian(_x, self.pos, self.width, self.depth) 148 else: 149 _y = HyFeature.multi_gauss(_x, [c.pos for c in self.components], 150 [c.width for c in self.components], 151 [c.depth for c in self.components] ) 152 ax.plot(_x, _y, **kwds) 153 if 'fill' in method.lower() or 'all' in method.lower(): 154 kwds['alpha'] = kwds.get('alpha', 0.25) 155 ax.axvspan(self.pos - self.width / 2, self.pos + self.width / 2, **kwds) 156 157 # label 158 if not label is None: 159 160 # calculate label position 161 rnge = ax.get_ylim()[1] - ax.get_ylim()[0] 162 if isinstance(label, int): 163 if label % 2 == 0: 164 label = 'top' # even 165 else: 166 label = 'low' # odd 167 if 'top' in label.lower(): 168 _y = ax.get_ylim()[1] - 0.05 * rnge 169 va = lab_kwds.get('va', 'top') 170 elif 'mid' in label.lower(): 171 _y = ax.get_ylim()[0] + 0.5 * rnge 172 va = lab_kwds.get('va', 'center') 173 elif 'low' in label.lower(): 174 _y = ax.get_ylim()[0] + 0.05 * rnge 175 va = lab_kwds.get('va', 'bottom') 176 else: 177 assert False, "Error - invalid label position '%s'" % label.lower() 178 179 # plot label 180 lab_kwds['rotation'] = lab_kwds.get('rotation', 90) 181 lab_kwds['alpha'] = lab_kwds.get('alpha', 0.5) 182 ha = lab_kwds.get('ha', 'center') 183 if 'ha' in lab_kwds: del lab_kwds['ha'] 184 if 'va' in lab_kwds: del lab_kwds['va'] 185 lab_kwds['bbox'] = lab_kwds.get('bbox', dict(boxstyle="round", 186 ec=(0.2, 0.2, 0.2), 187 fc=(1., 1., 1.), 188 )) 189 ax.text(self.pos, _y, self.name, va=va, ha=ha, **lab_kwds) 190 191 return ax.get_figure(), ax 192 193class MultiFeature(HyFeature): 194 """ 195 A spectral feature with variable position due to a solid solution between known end-members. 196 """ 197 198 def __init__(self, name, endmembers): 199 """ 200 Args: 201 endmembers (list): a list of HyFeature objects representing each end-member. 202 """ 203 204 # init this feature so that it ~ covers all of its 'sub-features' 205 minw = min([e.pos - e.width / 2 for e in endmembers]) 206 maxw = max([e.pos + e.width / 2 for e in endmembers]) 207 depth = np.mean([e.depth for e in endmembers]) 208 super().__init__(name, pos=(minw + maxw) / 2, width=maxw - minw, depth=depth, color=endmembers[0].color) 209 210 # store endmemebers 211 self.endmembers = endmembers 212 213 def count(self): 214 return len(self.endmembers) 215 216 def quick_plot(self, method='fill+line', ax=None, suplabel=None, sublabel=('alternate', {}), **kwds): 217 """ 218 Quickly plot this feature. 219 220 Args: 221 method (str): the method used to represent this feature. Default is 'fill+line'. Options are: 222 223 - 'gauss' = represent using a gaussian function at each endmember. 224 - 'range' = draw vertical lines at pos - width / 2 and pos + width / 2. 225 - 'fill' = fill a rectangle in the region dominated by the feature with 'color' specifed in kwds. 226 - 'line' = plot a (vertical) line at the position of each feature. 227 - 'all' = plot with all of the above methods. 228 229 ax: an axis to add the plot to. If None (default) a new axis is created. 230 suplabel (str): Label positions for this feature. Default is None (no labels). Options are 'top', 'middle' or 'lower'. 231 sublabel (str): Label positions for endmembers. Options are None (no labels), 'top', 'middle', 'lower' or 'alternate'. Or, if an integer 232 is passed then it will be used to initialise an alternating pattern (even = top, odd = lower). 233 lab_kwds (dict): Dictionary of keywords to pass to plt.text( ... ) for controlling labels. 234 **kwds: Keywords are passed to ax.axvline(...) if method=='range' or ax.plot(...) otherwise. 235 236 Returns: 237 Tuple containing 238 239 - fig: the figure that was plotted to 240 - ax: the axis that was plotted to 241 """ 242 243 if ax is None: 244 fig, ax = plt.subplots() 245 246 # plot 247 if 'range' in method.lower() or 'all' in method.lower(): 248 super().quick_plot(method='range', ax=ax, label=None, **kwds) 249 if 'line' in method.lower() or 'all' in method.lower(): 250 for e in self.endmembers: # plot line for each end-member 251 e.quick_plot(method='line', ax=ax, label=None, **kwds) 252 if 'gauss' in method.lower() or 'all' in method.lower(): 253 for e in self.endmembers: # plot gaussian for each end-member 254 e.quick_plot(method='gauss', ax=ax, label=None, **kwds) 255 if isinstance(sublabel, int): sublabel += 1 256 if 'fill' in method.lower() or 'all' in method.lower(): 257 super().quick_plot(method='fill', ax=ax, label=None, **kwds) 258 259 # and do labels 260 if not suplabel is None: 261 if not isinstance(suplabel, tuple): suplabel = (suplabel, {}) 262 super().quick_plot(method='label', ax=ax, label=suplabel[0], lab_kwds=suplabel[1]) 263 if not sublabel is None: 264 if not isinstance(sublabel, tuple): sublabel = (sublabel, {}) 265 if isinstance(sublabel[0], str) and 'alt' in sublabel[0].lower(): 266 sublabel = (1, sublabel[1]) # alternate labelling 267 for e in self.endmembers: 268 e.quick_plot(method='label', ax=ax, label=sublabel[0], lab_kwds=sublabel[1]) 269 sublabel = (sublabel[0] + 1, sublabel[1]) 270 return ax.get_figure(), ax 271 272class MixedFeature(HyFeature): 273 """ 274 A spectral feature resulting from a mixture of known sub-features. 275 """ 276 277 def __init__(self, name, components, **kwds): 278 """ 279 Args: 280 components: a list of HyFeature objects representing each end-member. 281 **kwds: keywords are passed to HyFeature.init() 282 """ 283 284 # init this feature so that it ~ covers all of its 'sub-features' 285 minw = min([e.pos - e.width / 2 for e in components]) 286 maxw = max([e.pos + e.width / 2 for e in components]) 287 depth = np.mean([e.depth for e in components]) 288 289 if not 'color' in kwds: 290 kwds['color'] = components[0].color 291 super().__init__(name, pos=(minw + maxw) / 2, width=maxw - minw, depth=depth, **kwds) 292 293 # store components 294 self.components = components 295 296 def count(self): 297 return len(self.components)
14class HyFeature(object): 15 """ 16 Utility class for representing and fitting individual or multiple absorption features. 17 """ 18 19 def __init__(self, name, pos, width, depth=1, data=None, color='g'): 20 """ 21 Args: 22 name (str) a name for this feature. 23 pos (float): the position of this feature (in nm). 24 width (float): the width of this feature (in nm). 25 data (ndarray): a real spectra associated with this feature (e.g. for feature fitting or from reference libraries). 26 Should be a numpy array such that data[0,:] gives wavelength and data[1,:] gives reflectance. 27 """ 28 29 self.name = name 30 self.pos = pos 31 self.width = width 32 self.depth = depth 33 self.color = color 34 self.data = data 35 self.mae = -1 36 self.strength = -1 37 self.components = None 38 self.endmembers = None 39 40 def get_start(self): 41 """ 42 Get start of feature. 43 44 Returns: 45 the feature position - 0.5 * feature width. 46 """ 47 48 return self.pos - self.width * 0.5 49 50 def get_end(self): 51 """ 52 Get approximate end of feature 53 54 Returns: 55 returns feature position - 0.5 * feature width. 56 """ 57 58 return self.pos + self.width * 0.5 59 60 61 ###################### 62 ## Feature models 63 ###################### 64 @classmethod 65 def gaussian(cls, _x, pos, width, depth): 66 """ 67 Static function for evaluating a gaussian feature model 68 69 Args: 70 x (ndarray): wavelengths (nanometres) to evaluate the feature over 71 pos (float): position for the gaussian function (nanometres) 72 width (float): width for the gaussian function. 73 depth (float): depth for the gaussian function (max to min) 74 offset (float): the vertical offset of the functions. Default is 1.0. 75 """ 76 return 1 - depth * np.exp( -(_x - pos)**2 / width ) 77 78 @classmethod 79 def multi_gauss(cls, x, pos, width, depth, asym=None): 80 """ 81 Static function for evaluating a multi-gaussian feature model 82 83 Args: 84 x (ndarray): wavelengths (nanometres) to evaluate the feature over 85 pos (list): a list of positions for each individual gaussian function (nanometres) 86 width (list): a list of widths for each individual gaussian function. 87 depth (list): a list of depths for each individual gaussian function (max to min) 88 asym (list): a list of feature asymmetries. The right-hand width will be calculated as: 89 w2 = asym * width. Default is 1.0. 90 """ 91 if asym is None: 92 asym = np.ones( len(width) ) 93 M = np.hstack( [[depth[i], pos[i], width[i], width[i]*asym[i]] for i in range(len(depth))] ) 94 y = evaluate( x, M, sym=False ) 95 return 1 - y 96 97 # noinspection PyDefaultArgument 98 def quick_plot(self, method='gauss', ax=None, label='top', lab_kwds={}, **kwds): 99 """ 100 Quickly plot this feature. 101 102 Args: 103 method (str): the method used to represent this feature. Options are: 104 105 - 'gauss' = represent using a gaussian function 106 - 'range' = draw vertical lines at pos - width / 2 and pos + width / 2. 107 - 'fill' = fill a rectangle in the region dominated by the feature with 'color' specifed in kwds. 108 - 'line' = plot a (vertical) line at the position of this feature. 109 - 'all' = plot with all of the above methods. 110 111 ax: an axis to add the plot to. If None (default) a new axis is created. 112 label (float): Label this feature (using it's name?). Options are None (no label), 'top', 'middle' or 'lower'. Or, 113 if an integer is passed, odd integers will be plotted as 'top' and even integers as 'lower'. 114 lab_kwds (dict): Dictionary of keywords to pass to plt.text( ... ) for controlling labels. 115 **kwds: Keywords are passed to ax.axvline(...) if method=='range' or ax.plot(...) otherwise. 116 117 Returns: 118 Tuple containing 119 120 - fig: the figure that was plotted to. 121 - ax: the axis that was plotted to. 122 """ 123 124 if ax is None: 125 fig, ax = plt.subplots() 126 127 # plot reference spectra and get _x for plotting 128 if self.data is not None: 129 _x = self.data[0, : ] 130 ax.plot(_x, self.data[1, :], color='k', **kwds) 131 else: 132 _x = np.linspace(self.pos - self.width, self.pos + self.width) 133 134 # set color 135 if 'c' in kwds: 136 kwds['color'] = kwds['c'] 137 del kwds['c'] 138 kwds['color'] = kwds.get('color', self.color) 139 140 # get _x for plotting 141 if 'range' in method.lower() or 'all' in method.lower(): 142 ax.axvline(self.pos - self.width / 2, **kwds) 143 ax.axvline(self.pos + self.width / 2, **kwds) 144 if 'line' in method.lower() or 'all' in method.lower(): 145 ax.axvline(self.pos, color='k', alpha=0.4) 146 if 'gauss' in method.lower() or 'all' in method.lower(): 147 if self.components is None: # plot single feature 148 _y = HyFeature.gaussian(_x, self.pos, self.width, self.depth) 149 else: 150 _y = HyFeature.multi_gauss(_x, [c.pos for c in self.components], 151 [c.width for c in self.components], 152 [c.depth for c in self.components] ) 153 ax.plot(_x, _y, **kwds) 154 if 'fill' in method.lower() or 'all' in method.lower(): 155 kwds['alpha'] = kwds.get('alpha', 0.25) 156 ax.axvspan(self.pos - self.width / 2, self.pos + self.width / 2, **kwds) 157 158 # label 159 if not label is None: 160 161 # calculate label position 162 rnge = ax.get_ylim()[1] - ax.get_ylim()[0] 163 if isinstance(label, int): 164 if label % 2 == 0: 165 label = 'top' # even 166 else: 167 label = 'low' # odd 168 if 'top' in label.lower(): 169 _y = ax.get_ylim()[1] - 0.05 * rnge 170 va = lab_kwds.get('va', 'top') 171 elif 'mid' in label.lower(): 172 _y = ax.get_ylim()[0] + 0.5 * rnge 173 va = lab_kwds.get('va', 'center') 174 elif 'low' in label.lower(): 175 _y = ax.get_ylim()[0] + 0.05 * rnge 176 va = lab_kwds.get('va', 'bottom') 177 else: 178 assert False, "Error - invalid label position '%s'" % label.lower() 179 180 # plot label 181 lab_kwds['rotation'] = lab_kwds.get('rotation', 90) 182 lab_kwds['alpha'] = lab_kwds.get('alpha', 0.5) 183 ha = lab_kwds.get('ha', 'center') 184 if 'ha' in lab_kwds: del lab_kwds['ha'] 185 if 'va' in lab_kwds: del lab_kwds['va'] 186 lab_kwds['bbox'] = lab_kwds.get('bbox', dict(boxstyle="round", 187 ec=(0.2, 0.2, 0.2), 188 fc=(1., 1., 1.), 189 )) 190 ax.text(self.pos, _y, self.name, va=va, ha=ha, **lab_kwds) 191 192 return ax.get_figure(), ax
Utility class for representing and fitting individual or multiple absorption features.
19 def __init__(self, name, pos, width, depth=1, data=None, color='g'): 20 """ 21 Args: 22 name (str) a name for this feature. 23 pos (float): the position of this feature (in nm). 24 width (float): the width of this feature (in nm). 25 data (ndarray): a real spectra associated with this feature (e.g. for feature fitting or from reference libraries). 26 Should be a numpy array such that data[0,:] gives wavelength and data[1,:] gives reflectance. 27 """ 28 29 self.name = name 30 self.pos = pos 31 self.width = width 32 self.depth = depth 33 self.color = color 34 self.data = data 35 self.mae = -1 36 self.strength = -1 37 self.components = None 38 self.endmembers = None
Arguments:
- name (str) a name for this feature.
- pos (float): the position of this feature (in nm).
- width (float): the width of this feature (in nm).
- data (ndarray): a real spectra associated with this feature (e.g. for feature fitting or from reference libraries). Should be a numpy array such that data[0,:] gives wavelength and data[1,:] gives reflectance.
40 def get_start(self): 41 """ 42 Get start of feature. 43 44 Returns: 45 the feature position - 0.5 * feature width. 46 """ 47 48 return self.pos - self.width * 0.5
Get start of feature.
Returns:
the feature position - 0.5 * feature width.
50 def get_end(self): 51 """ 52 Get approximate end of feature 53 54 Returns: 55 returns feature position - 0.5 * feature width. 56 """ 57 58 return self.pos + self.width * 0.5
Get approximate end of feature
Returns: returns feature position - 0.5 * feature width.
64 @classmethod 65 def gaussian(cls, _x, pos, width, depth): 66 """ 67 Static function for evaluating a gaussian feature model 68 69 Args: 70 x (ndarray): wavelengths (nanometres) to evaluate the feature over 71 pos (float): position for the gaussian function (nanometres) 72 width (float): width for the gaussian function. 73 depth (float): depth for the gaussian function (max to min) 74 offset (float): the vertical offset of the functions. Default is 1.0. 75 """ 76 return 1 - depth * np.exp( -(_x - pos)**2 / width )
Static function for evaluating a gaussian feature model
Arguments:
- x (ndarray): wavelengths (nanometres) to evaluate the feature over
- pos (float): position for the gaussian function (nanometres)
- width (float): width for the gaussian function.
- depth (float): depth for the gaussian function (max to min)
- offset (float): the vertical offset of the functions. Default is 1.0.
78 @classmethod 79 def multi_gauss(cls, x, pos, width, depth, asym=None): 80 """ 81 Static function for evaluating a multi-gaussian feature model 82 83 Args: 84 x (ndarray): wavelengths (nanometres) to evaluate the feature over 85 pos (list): a list of positions for each individual gaussian function (nanometres) 86 width (list): a list of widths for each individual gaussian function. 87 depth (list): a list of depths for each individual gaussian function (max to min) 88 asym (list): a list of feature asymmetries. The right-hand width will be calculated as: 89 w2 = asym * width. Default is 1.0. 90 """ 91 if asym is None: 92 asym = np.ones( len(width) ) 93 M = np.hstack( [[depth[i], pos[i], width[i], width[i]*asym[i]] for i in range(len(depth))] ) 94 y = evaluate( x, M, sym=False ) 95 return 1 - y
Static function for evaluating a multi-gaussian feature model
Arguments:
- x (ndarray): wavelengths (nanometres) to evaluate the feature over
- pos (list): a list of positions for each individual gaussian function (nanometres)
- width (list): a list of widths for each individual gaussian function.
- depth (list): a list of depths for each individual gaussian function (max to min)
- asym (list): a list of feature asymmetries. The right-hand width will be calculated as: w2 = asym * width. Default is 1.0.
98 def quick_plot(self, method='gauss', ax=None, label='top', lab_kwds={}, **kwds): 99 """ 100 Quickly plot this feature. 101 102 Args: 103 method (str): the method used to represent this feature. Options are: 104 105 - 'gauss' = represent using a gaussian function 106 - 'range' = draw vertical lines at pos - width / 2 and pos + width / 2. 107 - 'fill' = fill a rectangle in the region dominated by the feature with 'color' specifed in kwds. 108 - 'line' = plot a (vertical) line at the position of this feature. 109 - 'all' = plot with all of the above methods. 110 111 ax: an axis to add the plot to. If None (default) a new axis is created. 112 label (float): Label this feature (using it's name?). Options are None (no label), 'top', 'middle' or 'lower'. Or, 113 if an integer is passed, odd integers will be plotted as 'top' and even integers as 'lower'. 114 lab_kwds (dict): Dictionary of keywords to pass to plt.text( ... ) for controlling labels. 115 **kwds: Keywords are passed to ax.axvline(...) if method=='range' or ax.plot(...) otherwise. 116 117 Returns: 118 Tuple containing 119 120 - fig: the figure that was plotted to. 121 - ax: the axis that was plotted to. 122 """ 123 124 if ax is None: 125 fig, ax = plt.subplots() 126 127 # plot reference spectra and get _x for plotting 128 if self.data is not None: 129 _x = self.data[0, : ] 130 ax.plot(_x, self.data[1, :], color='k', **kwds) 131 else: 132 _x = np.linspace(self.pos - self.width, self.pos + self.width) 133 134 # set color 135 if 'c' in kwds: 136 kwds['color'] = kwds['c'] 137 del kwds['c'] 138 kwds['color'] = kwds.get('color', self.color) 139 140 # get _x for plotting 141 if 'range' in method.lower() or 'all' in method.lower(): 142 ax.axvline(self.pos - self.width / 2, **kwds) 143 ax.axvline(self.pos + self.width / 2, **kwds) 144 if 'line' in method.lower() or 'all' in method.lower(): 145 ax.axvline(self.pos, color='k', alpha=0.4) 146 if 'gauss' in method.lower() or 'all' in method.lower(): 147 if self.components is None: # plot single feature 148 _y = HyFeature.gaussian(_x, self.pos, self.width, self.depth) 149 else: 150 _y = HyFeature.multi_gauss(_x, [c.pos for c in self.components], 151 [c.width for c in self.components], 152 [c.depth for c in self.components] ) 153 ax.plot(_x, _y, **kwds) 154 if 'fill' in method.lower() or 'all' in method.lower(): 155 kwds['alpha'] = kwds.get('alpha', 0.25) 156 ax.axvspan(self.pos - self.width / 2, self.pos + self.width / 2, **kwds) 157 158 # label 159 if not label is None: 160 161 # calculate label position 162 rnge = ax.get_ylim()[1] - ax.get_ylim()[0] 163 if isinstance(label, int): 164 if label % 2 == 0: 165 label = 'top' # even 166 else: 167 label = 'low' # odd 168 if 'top' in label.lower(): 169 _y = ax.get_ylim()[1] - 0.05 * rnge 170 va = lab_kwds.get('va', 'top') 171 elif 'mid' in label.lower(): 172 _y = ax.get_ylim()[0] + 0.5 * rnge 173 va = lab_kwds.get('va', 'center') 174 elif 'low' in label.lower(): 175 _y = ax.get_ylim()[0] + 0.05 * rnge 176 va = lab_kwds.get('va', 'bottom') 177 else: 178 assert False, "Error - invalid label position '%s'" % label.lower() 179 180 # plot label 181 lab_kwds['rotation'] = lab_kwds.get('rotation', 90) 182 lab_kwds['alpha'] = lab_kwds.get('alpha', 0.5) 183 ha = lab_kwds.get('ha', 'center') 184 if 'ha' in lab_kwds: del lab_kwds['ha'] 185 if 'va' in lab_kwds: del lab_kwds['va'] 186 lab_kwds['bbox'] = lab_kwds.get('bbox', dict(boxstyle="round", 187 ec=(0.2, 0.2, 0.2), 188 fc=(1., 1., 1.), 189 )) 190 ax.text(self.pos, _y, self.name, va=va, ha=ha, **lab_kwds) 191 192 return ax.get_figure(), ax
Quickly plot this feature.
Arguments:
method (str): the method used to represent this feature. Options are:
- 'gauss' = represent using a gaussian function
- 'range' = draw vertical lines at pos - width / 2 and pos + width / 2.
- 'fill' = fill a rectangle in the region dominated by the feature with 'color' specifed in kwds.
- 'line' = plot a (vertical) line at the position of this feature.
- 'all' = plot with all of the above methods.
- ax: an axis to add the plot to. If None (default) a new axis is created.
- label (float): Label this feature (using it's name?). Options are None (no label), 'top', 'middle' or 'lower'. Or, if an integer is passed, odd integers will be plotted as 'top' and even integers as 'lower'.
- lab_kwds (dict): Dictionary of keywords to pass to plt.text( ... ) for controlling labels.
- **kwds: Keywords are passed to ax.axvline(...) if method=='range' or ax.plot(...) otherwise.
Returns:
Tuple containing
- fig: the figure that was plotted to.
- ax: the axis that was plotted to.
12class Features: 13 """ 14 Specific absorption types. Useful for plotting etc. Not really used for anything and will probably be depreciated soon. 15 """ 16 17 H2O = [ HyFeature("H2O", p, w, color='skyblue') for p,w in [(825,50), (940,75), (1130,100), (1400,150), (1900,200), (2700,150)] ] 18 OH = [HyFeature("OH", 1400, 50, color='aquamarine'), HyFeature("OH", 1550, 50, color='aquamarine'), HyFeature("OH", 1800, 100, color='aquamarine')] 19 AlOH = [HyFeature("AlOH", 2190, 60, color='salmon')] 20 FeOH = [HyFeature("FeOH", 2265, 70, color='orange')] 21 MgOH = [HyFeature("MgOH", 2330, 60, color='blue'), HyFeature("MgOH", 2385, 30,color='blue')] 22 MgCO3 = [HyFeature("MgCO3", 2320, 20, color='green')] 23 CaCO3 = [HyFeature("CaCO3", 2340, 20, color='blue')] 24 FeCO3 = [HyFeature("FeCO3", 2350, 20, color='steelblue')] 25 Ferrous = [HyFeature("Fe2+", 1000, 400, color='green')] 26 Ferric = [HyFeature("Fe3+", 650, 170, color='green')] 27 28 # REE features 29 Pr = [HyFeature("Pr", w, 5, color=(74 / 256., 155 / 256., 122 / 256., 1)) for w in [457, 485, 473, 595, 1017] ] 30 Nd = [HyFeature("Nd", w, 5, color=(116 / 256., 114 / 256., 174 / 256., 1)) for w in [430, 463, 475, 514, 525, 580, 627, 680, 750, 800, 880, 1430, 1720, 2335, 2470]] 31 Sm = [HyFeature("Sm", w, 5, color=(116 / 256., 114 / 256., 174 / 256., 1)) for w in [945, 959, 1085, 1235, 1257, 1400, 1550]] 32 Eu = [HyFeature("Eu", w, 5, color=(213 / 256., 64 / 256., 136 / 256., 1)) for w in [385, 405, 470, 530, 1900, 2025, 2170, 2400, 2610]] 33 Dy = [HyFeature("Dy", w, 5, color=(117 / 256., 163 / 256., 58 / 256., 1)) for w in [368, 390, 403, 430, 452, 461, 475, 760, 810, 830, 915, 1117, 1276, 1725]] 34 Ho = [HyFeature("Ho", w, 5, color=(222 / 256., 172 / 256., 59 / 256., 1)) for w in [363, 420, 458, 545, 650, 900, 1130, 1180, 1870, 1930, 2005]] 35 Er = [HyFeature("Er", w, 5, color=(159 / 256., 119 / 256., 49 / 256., 1)) for w in [390, 405, 455, 490, 522, 540, 652, 805, 985, 1485, 1545]] 36 Tm = [HyFeature("Tm", w, 5, color=(102 / 256., 102 / 256., 102 / 256., 1)) for w in [390, 470, 660, 685, 780, 1190, 1640, 1750]] 37 Yb = [HyFeature("Yb", w, 5, color=(209 / 256., 53 / 256., 43 / 256., 1)) for w in [955, 975, 1004 ]]
Specific absorption types. Useful for plotting etc. Not really used for anything and will probably be depreciated soon.
Inherited Members
40class Minerals: 41 """ 42 Common mineral absorption features. Useful for plotting etc. Not really used for anything and will probably be depreciated soon. 43 """ 44 45 # Kaolin clays (dominant SWIR feature) 46 Kaolinite = [HyFeature("Kaolinite/Halloysite", 2200, 100, color='aquamarine')] 47 Halloysite = [Kaolinite] 48 Dickite = [HyFeature("Dickite/Nacrite", 2180, 100, color='aquamarine')] 49 Nacrite = [Dickite] 50 KAOLIN = MultiFeature("Kaolin", Kaolinite + Dickite) 51 52 Pyrophyllite = HyFeature("Pyrophyllite", 2160.0, 150, color='aquamarine') 53 54 #Smectite clays (dominant SWIR feature) 55 Montmorillonite = [HyFeature("Montmorillonite", 2210.0, 125, color='orange')] 56 Nontronite = [HyFeature("Nontronite", 2280, 125, color='orange')] 57 Saponite = [HyFeature("Saponite", 2309, 100, color='orange')] 58 SMECTITE = MultiFeature("Smectite", Montmorillonite + Nontronite + Saponite) 59 60 # white micas (dominant SWIR feature) 61 Mica_Na = [HyFeature("Mica (Na)", 2150, 150, color='coral' )] 62 Mica_K = [HyFeature("Mica (K)", 2190, 150, color='lightcoral' )] 63 Mica_MgFe = [HyFeature("Mica (Mg, Fe)", 2225, 150 , color='sandybrown')] 64 MICA = MultiFeature("White mica", Mica_Na + Mica_K +Mica_MgFe) 65 66 # chlorite 67 Chlorite_Mg = [ HyFeature("Chlorite (Mg)", 2245.0, 50, color='seagreen'), HyFeature("Chlorite (Mg)", 2325.0, 50, color='seagreen') ] 68 Chlorite_Fe = [HyFeature("Chlorite (Fe)", 2261.0, 50, color='seagreen'), HyFeature("Chlorite (Fe)", 2355.0, 50, color='seagreen') ] 69 CHLORITE = [MultiFeature("Chlorite (FeOH)", [Chlorite_Mg[0], Chlorite_Fe[0]]), 70 MultiFeature("Chlorite (MgOH)", [Chlorite_Mg[1], Chlorite_Fe[1]])] 71 72 # biotite 73 Biotite_Mg = [ HyFeature("Biotite (Mg)", 2326, 50, color='firebrick'), HyFeature("Biotite (Mg)", 2377, 50, color='firebrick') ] 74 Biotite_Fe = [ HyFeature("Biotite (Fe)", 2250, 50, color='firebrick'), HyFeature("Biotite (Fe)", 2350, 50, color='firebrick') ] 75 BIOTITE = [MultiFeature("Biotite (FeOH)", [Biotite_Mg[0], Biotite_Fe[0]]), 76 MultiFeature("Biotite (MgOH)", [Biotite_Mg[1], Biotite_Fe[1]]) ] 77 78 # amphiboles Tremolite, hornblende, actinolite 79 Amphibole_Mg = [HyFeature("Amphibole (Mg)", 2320.0, 50, color='royalblue')] 80 Amphibole_Fe = [HyFeature("Amphibole (Fe)", 2345.0, 50, color='royalblue')] 81 AMPHIBOLE = MultiFeature("Amphibole", Amphibole_Mg + Amphibole_Fe) 82 83 # carbonate minerals 84 Dolomite = [HyFeature("Dolomite", 2320, 20, color='green')] 85 Calcite = [HyFeature("Calcite", 2345, 20, color='blue')] 86 Ankerite = [HyFeature("Ankerite", 2330, 20, color='steelblue')] 87 CARBONATE = MultiFeature("Carbonate", Dolomite+ Ankerite+ Calcite) 88 89 #Sulphates Jarosite 90 Gypsum = [HyFeature("Gypsum", 1449.0, 50, color='gold'), HyFeature("Gypsum", 1750, 50, color='gold'), HyFeature("Gypsum", 1948.0, 50, color='gold')] 91 Jarosite = [HyFeature("Jarosite", 1470.0, 50, color='orange'), HyFeature("Jarosite", 1850, 50, color='orange'), HyFeature("Jarosite", 2270.0, 50, color='orange')] 92 SULPHATE = MultiFeature( "Sulphate", Gypsum + Jarosite ) 93 94 # misc 95 Epidote = [ HyFeature("Epidote", 2256.0, 40, color='green'), HyFeature("Epidote", 2340.0, 40, color='green')]
Common mineral absorption features. Useful for plotting etc. Not really used for anything and will probably be depreciated soon.
Inherited Members
98class Themes: 99 """ 100 Some useful 'themes' (for plotting etc) 101 """ 102 ATMOSPHERE = Features.H2O #[HyFeature("H2O", 975, 30), HyFeature("H2O", 1395, 120), HyFeature("H2O", 1885, 180), HyFeature("H2O", 2450, 100)] 103 CARBONATE = [Minerals.CARBONATE] 104 OH = Features.AlOH + Features.FeOH + Features.MgOH 105 CLAY = [ Minerals.KAOLIN, Minerals.SMECTITE ] 106 DIAGNOSTIC = Features.Ferrous + Features.AlOH+Features.FeOH+Features.MgOH
Some useful 'themes' (for plotting etc)
Inherited Members
194class MultiFeature(HyFeature): 195 """ 196 A spectral feature with variable position due to a solid solution between known end-members. 197 """ 198 199 def __init__(self, name, endmembers): 200 """ 201 Args: 202 endmembers (list): a list of HyFeature objects representing each end-member. 203 """ 204 205 # init this feature so that it ~ covers all of its 'sub-features' 206 minw = min([e.pos - e.width / 2 for e in endmembers]) 207 maxw = max([e.pos + e.width / 2 for e in endmembers]) 208 depth = np.mean([e.depth for e in endmembers]) 209 super().__init__(name, pos=(minw + maxw) / 2, width=maxw - minw, depth=depth, color=endmembers[0].color) 210 211 # store endmemebers 212 self.endmembers = endmembers 213 214 def count(self): 215 return len(self.endmembers) 216 217 def quick_plot(self, method='fill+line', ax=None, suplabel=None, sublabel=('alternate', {}), **kwds): 218 """ 219 Quickly plot this feature. 220 221 Args: 222 method (str): the method used to represent this feature. Default is 'fill+line'. Options are: 223 224 - 'gauss' = represent using a gaussian function at each endmember. 225 - 'range' = draw vertical lines at pos - width / 2 and pos + width / 2. 226 - 'fill' = fill a rectangle in the region dominated by the feature with 'color' specifed in kwds. 227 - 'line' = plot a (vertical) line at the position of each feature. 228 - 'all' = plot with all of the above methods. 229 230 ax: an axis to add the plot to. If None (default) a new axis is created. 231 suplabel (str): Label positions for this feature. Default is None (no labels). Options are 'top', 'middle' or 'lower'. 232 sublabel (str): Label positions for endmembers. Options are None (no labels), 'top', 'middle', 'lower' or 'alternate'. Or, if an integer 233 is passed then it will be used to initialise an alternating pattern (even = top, odd = lower). 234 lab_kwds (dict): Dictionary of keywords to pass to plt.text( ... ) for controlling labels. 235 **kwds: Keywords are passed to ax.axvline(...) if method=='range' or ax.plot(...) otherwise. 236 237 Returns: 238 Tuple containing 239 240 - fig: the figure that was plotted to 241 - ax: the axis that was plotted to 242 """ 243 244 if ax is None: 245 fig, ax = plt.subplots() 246 247 # plot 248 if 'range' in method.lower() or 'all' in method.lower(): 249 super().quick_plot(method='range', ax=ax, label=None, **kwds) 250 if 'line' in method.lower() or 'all' in method.lower(): 251 for e in self.endmembers: # plot line for each end-member 252 e.quick_plot(method='line', ax=ax, label=None, **kwds) 253 if 'gauss' in method.lower() or 'all' in method.lower(): 254 for e in self.endmembers: # plot gaussian for each end-member 255 e.quick_plot(method='gauss', ax=ax, label=None, **kwds) 256 if isinstance(sublabel, int): sublabel += 1 257 if 'fill' in method.lower() or 'all' in method.lower(): 258 super().quick_plot(method='fill', ax=ax, label=None, **kwds) 259 260 # and do labels 261 if not suplabel is None: 262 if not isinstance(suplabel, tuple): suplabel = (suplabel, {}) 263 super().quick_plot(method='label', ax=ax, label=suplabel[0], lab_kwds=suplabel[1]) 264 if not sublabel is None: 265 if not isinstance(sublabel, tuple): sublabel = (sublabel, {}) 266 if isinstance(sublabel[0], str) and 'alt' in sublabel[0].lower(): 267 sublabel = (1, sublabel[1]) # alternate labelling 268 for e in self.endmembers: 269 e.quick_plot(method='label', ax=ax, label=sublabel[0], lab_kwds=sublabel[1]) 270 sublabel = (sublabel[0] + 1, sublabel[1]) 271 return ax.get_figure(), ax
A spectral feature with variable position due to a solid solution between known end-members.
199 def __init__(self, name, endmembers): 200 """ 201 Args: 202 endmembers (list): a list of HyFeature objects representing each end-member. 203 """ 204 205 # init this feature so that it ~ covers all of its 'sub-features' 206 minw = min([e.pos - e.width / 2 for e in endmembers]) 207 maxw = max([e.pos + e.width / 2 for e in endmembers]) 208 depth = np.mean([e.depth for e in endmembers]) 209 super().__init__(name, pos=(minw + maxw) / 2, width=maxw - minw, depth=depth, color=endmembers[0].color) 210 211 # store endmemebers 212 self.endmembers = endmembers
Arguments:
- endmembers (list): a list of HyFeature objects representing each end-member.
217 def quick_plot(self, method='fill+line', ax=None, suplabel=None, sublabel=('alternate', {}), **kwds): 218 """ 219 Quickly plot this feature. 220 221 Args: 222 method (str): the method used to represent this feature. Default is 'fill+line'. Options are: 223 224 - 'gauss' = represent using a gaussian function at each endmember. 225 - 'range' = draw vertical lines at pos - width / 2 and pos + width / 2. 226 - 'fill' = fill a rectangle in the region dominated by the feature with 'color' specifed in kwds. 227 - 'line' = plot a (vertical) line at the position of each feature. 228 - 'all' = plot with all of the above methods. 229 230 ax: an axis to add the plot to. If None (default) a new axis is created. 231 suplabel (str): Label positions for this feature. Default is None (no labels). Options are 'top', 'middle' or 'lower'. 232 sublabel (str): Label positions for endmembers. Options are None (no labels), 'top', 'middle', 'lower' or 'alternate'. Or, if an integer 233 is passed then it will be used to initialise an alternating pattern (even = top, odd = lower). 234 lab_kwds (dict): Dictionary of keywords to pass to plt.text( ... ) for controlling labels. 235 **kwds: Keywords are passed to ax.axvline(...) if method=='range' or ax.plot(...) otherwise. 236 237 Returns: 238 Tuple containing 239 240 - fig: the figure that was plotted to 241 - ax: the axis that was plotted to 242 """ 243 244 if ax is None: 245 fig, ax = plt.subplots() 246 247 # plot 248 if 'range' in method.lower() or 'all' in method.lower(): 249 super().quick_plot(method='range', ax=ax, label=None, **kwds) 250 if 'line' in method.lower() or 'all' in method.lower(): 251 for e in self.endmembers: # plot line for each end-member 252 e.quick_plot(method='line', ax=ax, label=None, **kwds) 253 if 'gauss' in method.lower() or 'all' in method.lower(): 254 for e in self.endmembers: # plot gaussian for each end-member 255 e.quick_plot(method='gauss', ax=ax, label=None, **kwds) 256 if isinstance(sublabel, int): sublabel += 1 257 if 'fill' in method.lower() or 'all' in method.lower(): 258 super().quick_plot(method='fill', ax=ax, label=None, **kwds) 259 260 # and do labels 261 if not suplabel is None: 262 if not isinstance(suplabel, tuple): suplabel = (suplabel, {}) 263 super().quick_plot(method='label', ax=ax, label=suplabel[0], lab_kwds=suplabel[1]) 264 if not sublabel is None: 265 if not isinstance(sublabel, tuple): sublabel = (sublabel, {}) 266 if isinstance(sublabel[0], str) and 'alt' in sublabel[0].lower(): 267 sublabel = (1, sublabel[1]) # alternate labelling 268 for e in self.endmembers: 269 e.quick_plot(method='label', ax=ax, label=sublabel[0], lab_kwds=sublabel[1]) 270 sublabel = (sublabel[0] + 1, sublabel[1]) 271 return ax.get_figure(), ax
Quickly plot this feature.
Arguments:
method (str): the method used to represent this feature. Default is 'fill+line'. Options are:
- 'gauss' = represent using a gaussian function at each endmember.
- 'range' = draw vertical lines at pos - width / 2 and pos + width / 2.
- 'fill' = fill a rectangle in the region dominated by the feature with 'color' specifed in kwds.
- 'line' = plot a (vertical) line at the position of each feature.
- 'all' = plot with all of the above methods.
- ax: an axis to add the plot to. If None (default) a new axis is created.
- suplabel (str): Label positions for this feature. Default is None (no labels). Options are 'top', 'middle' or 'lower'.
- sublabel (str): Label positions for endmembers. Options are None (no labels), 'top', 'middle', 'lower' or 'alternate'. Or, if an integer is passed then it will be used to initialise an alternating pattern (even = top, odd = lower).
- lab_kwds (dict): Dictionary of keywords to pass to plt.text( ... ) for controlling labels.
- **kwds: Keywords are passed to ax.axvline(...) if method=='range' or ax.plot(...) otherwise.
Returns:
Tuple containing
- fig: the figure that was plotted to
- ax: the axis that was plotted to
273class MixedFeature(HyFeature): 274 """ 275 A spectral feature resulting from a mixture of known sub-features. 276 """ 277 278 def __init__(self, name, components, **kwds): 279 """ 280 Args: 281 components: a list of HyFeature objects representing each end-member. 282 **kwds: keywords are passed to HyFeature.init() 283 """ 284 285 # init this feature so that it ~ covers all of its 'sub-features' 286 minw = min([e.pos - e.width / 2 for e in components]) 287 maxw = max([e.pos + e.width / 2 for e in components]) 288 depth = np.mean([e.depth for e in components]) 289 290 if not 'color' in kwds: 291 kwds['color'] = components[0].color 292 super().__init__(name, pos=(minw + maxw) / 2, width=maxw - minw, depth=depth, **kwds) 293 294 # store components 295 self.components = components 296 297 def count(self): 298 return len(self.components)
A spectral feature resulting from a mixture of known sub-features.
278 def __init__(self, name, components, **kwds): 279 """ 280 Args: 281 components: a list of HyFeature objects representing each end-member. 282 **kwds: keywords are passed to HyFeature.init() 283 """ 284 285 # init this feature so that it ~ covers all of its 'sub-features' 286 minw = min([e.pos - e.width / 2 for e in components]) 287 maxw = max([e.pos + e.width / 2 for e in components]) 288 depth = np.mean([e.depth for e in components]) 289 290 if not 'color' in kwds: 291 kwds['color'] = components[0].color 292 super().__init__(name, pos=(minw + maxw) / 2, width=maxw - minw, depth=depth, **kwds) 293 294 # store components 295 self.components = components
Arguments:
- components: a list of HyFeature objects representing each end-member.
- **kwds: keywords are passed to HyFeature.init()