hylite.hyfeature
Fit and visualise individual hyperspectral features.
1""" 2Fit and visualise individual hyperspectral features. 3""" 4 5import numpy as np 6 7from hylite._deps import require 8 9class HyFeature(object): 10 """ 11 Utility class for representing and fitting individual or multiple absorption features. 12 """ 13 14 def __init__(self, name, pos, width, depth=1, data=None, color='g'): 15 """ 16 Args: 17 name (str) a name for this feature. 18 pos (float): the position of this feature (in nm). 19 width (float): the width of this feature (in nm). 20 data (ndarray): a real spectra associated with this feature (e.g. for feature fitting or from reference libraries). 21 Should be a numpy array such that data[0,:] gives wavelength and data[1,:] gives reflectance. 22 """ 23 24 self.name = name 25 self.pos = pos 26 self.width = width 27 self.depth = depth 28 self.color = color 29 self.data = data 30 self.mae = -1 31 self.strength = -1 32 self.components = None 33 self.endmembers = None 34 35 def get_start(self): 36 """ 37 Get start of feature. 38 39 Returns: 40 the feature position - 0.5 * feature width. 41 """ 42 43 return self.pos - self.width * 0.5 44 45 def get_end(self): 46 """ 47 Get approximate end of feature 48 49 Returns: 50 returns feature position - 0.5 * feature width. 51 """ 52 53 return self.pos + self.width * 0.5 54 55 56 ###################### 57 ## Feature models 58 ###################### 59 @classmethod 60 def gaussian(cls, _x, pos, width, depth): 61 """ 62 Static function for evaluating a gaussian feature model 63 64 Args: 65 x (ndarray): wavelengths (nanometres) to evaluate the feature over 66 pos (float): position for the gaussian function (nanometres) 67 width (float): width for the gaussian function. 68 depth (float): depth for the gaussian function (max to min) 69 offset (float): the vertical offset of the functions. Default is 1.0. 70 """ 71 return 1 - depth * np.exp( -(_x - pos)**2 / width ) 72 73 @classmethod 74 def multi_gauss(cls, x, pos, width, depth, asym=None): 75 """ 76 Static function for evaluating a multi-gaussian feature model 77 78 Args: 79 x (ndarray): wavelengths (nanometres) to evaluate the feature over 80 pos (list): a list of positions for each individual gaussian function (nanometres) 81 width (list): a list of widths for each individual gaussian function. 82 depth (list): a list of depths for each individual gaussian function (max to min) 83 asym (list): a list of feature asymmetries. The right-hand width will be calculated as: 84 w2 = asym * width. Default is 1.0. 85 """ 86 if asym is None: 87 asym = np.ones( len(width) ) 88 M = np.hstack( [[depth[i], pos[i], width[i], width[i]*asym[i]] for i in range(len(depth))] ) 89 evaluate = require("gfit").evaluate 90 y = evaluate( x, M, sym=False ) 91 return 1 - y 92 93 # noinspection PyDefaultArgument 94 def quick_plot(self, method='gauss', ax=None, label='top', lab_kwds={}, **kwds): 95 """ 96 Quickly plot this feature. 97 98 Args: 99 method (str): the method used to represent this feature. Options are: 100 101 - 'gauss' = represent using a gaussian function 102 - 'range' = draw vertical lines at pos - width / 2 and pos + width / 2. 103 - 'fill' = fill a rectangle in the region dominated by the feature with 'color' specifed in kwds. 104 - 'line' = plot a (vertical) line at the position of this feature. 105 - 'all' = plot with all of the above methods. 106 107 ax: an axis to add the plot to. If None (default) a new axis is created. 108 label (float): Label this feature (using it's name?). Options are None (no label), 'top', 'middle' or 'lower'. Or, 109 if an integer is passed, odd integers will be plotted as 'top' and even integers as 'lower'. 110 lab_kwds (dict): Dictionary of keywords to pass to plt.text( ... ) for controlling labels. 111 **kwds: Keywords are passed to ax.axvline(...) if method=='range' or ax.plot(...) otherwise. 112 113 Returns: 114 Tuple containing 115 116 - fig: the figure that was plotted to. 117 - ax: the axis that was plotted to. 118 """ 119 120 plt = require("matplotlib.pyplot") 121 122 if ax is None: 123 fig, ax = plt.subplots() 124 125 # plot reference spectra and get _x for plotting 126 if self.data is not None: 127 _x = self.data[0, : ] 128 ax.plot(_x, self.data[1, :], color='k', **kwds) 129 else: 130 _x = np.linspace(self.pos - self.width, self.pos + self.width) 131 132 # set color 133 if 'c' in kwds: 134 kwds['color'] = kwds['c'] 135 del kwds['c'] 136 kwds['color'] = kwds.get('color', self.color) 137 138 # get _x for plotting 139 if 'range' in method.lower() or 'all' in method.lower(): 140 ax.axvline(self.pos - self.width / 2, **kwds) 141 ax.axvline(self.pos + self.width / 2, **kwds) 142 if 'line' in method.lower() or 'all' in method.lower(): 143 ax.axvline(self.pos, color='k', alpha=0.4) 144 if 'gauss' in method.lower() or 'all' in method.lower(): 145 if self.components is None: # plot single feature 146 _y = HyFeature.gaussian(_x, self.pos, self.width, self.depth) 147 else: 148 _y = HyFeature.multi_gauss(_x, [c.pos for c in self.components], 149 [c.width for c in self.components], 150 [c.depth for c in self.components] ) 151 ax.plot(_x, _y, **kwds) 152 if 'fill' in method.lower() or 'all' in method.lower(): 153 kwds['alpha'] = kwds.get('alpha', 0.25) 154 ax.axvspan(self.pos - self.width / 2, self.pos + self.width / 2, **kwds) 155 156 # label 157 if not label is None: 158 159 # calculate label position 160 rnge = ax.get_ylim()[1] - ax.get_ylim()[0] 161 if isinstance(label, int): 162 if label % 2 == 0: 163 label = 'top' # even 164 else: 165 label = 'low' # odd 166 if 'top' in label.lower(): 167 _y = ax.get_ylim()[1] - 0.05 * rnge 168 va = lab_kwds.get('va', 'top') 169 elif 'mid' in label.lower(): 170 _y = ax.get_ylim()[0] + 0.5 * rnge 171 va = lab_kwds.get('va', 'center') 172 elif 'low' in label.lower(): 173 _y = ax.get_ylim()[0] + 0.05 * rnge 174 va = lab_kwds.get('va', 'bottom') 175 else: 176 assert False, "Error - invalid label position '%s'" % label.lower() 177 178 # plot label 179 lab_kwds['rotation'] = lab_kwds.get('rotation', 90) 180 lab_kwds['alpha'] = lab_kwds.get('alpha', 0.5) 181 ha = lab_kwds.get('ha', 'center') 182 if 'ha' in lab_kwds: del lab_kwds['ha'] 183 if 'va' in lab_kwds: del lab_kwds['va'] 184 lab_kwds['bbox'] = lab_kwds.get('bbox', dict(boxstyle="round", 185 ec=(0.2, 0.2, 0.2), 186 fc=(1., 1., 1.), 187 )) 188 ax.text(self.pos, _y, self.name, va=va, ha=ha, **lab_kwds) 189 190 return ax.get_figure(), ax 191 192class MultiFeature(HyFeature): 193 """ 194 A spectral feature with variable position due to a solid solution between known end-members. 195 """ 196 197 def __init__(self, name, endmembers): 198 """ 199 Args: 200 endmembers (list): a list of `hylite.hyfeature.HyFeature` objects representing each end-member. 201 """ 202 203 # init this feature so that it ~ covers all of its 'sub-features' 204 minw = min([e.pos - e.width / 2 for e in endmembers]) 205 maxw = max([e.pos + e.width / 2 for e in endmembers]) 206 depth = np.mean([e.depth for e in endmembers]) 207 super().__init__(name, pos=(minw + maxw) / 2, width=maxw - minw, depth=depth, color=endmembers[0].color) 208 209 # store endmemebers 210 self.endmembers = endmembers 211 212 def count(self): 213 return len(self.endmembers) 214 215 def quick_plot(self, method='fill+line', ax=None, suplabel=None, sublabel=('alternate', {}), **kwds): 216 """ 217 Quickly plot this feature. 218 219 Args: 220 method (str): the method used to represent this feature. Default is 'fill+line'. Options are: 221 222 - 'gauss' = represent using a gaussian function at each endmember. 223 - 'range' = draw vertical lines at pos - width / 2 and pos + width / 2. 224 - 'fill' = fill a rectangle in the region dominated by the feature with 'color' specifed in kwds. 225 - 'line' = plot a (vertical) line at the position of each feature. 226 - 'all' = plot with all of the above methods. 227 228 ax: an axis to add the plot to. If None (default) a new axis is created. 229 suplabel (str): Label positions for this feature. Default is None (no labels). Options are 'top', 'middle' or 'lower'. 230 sublabel (str): Label positions for endmembers. Options are None (no labels), 'top', 'middle', 'lower' or 'alternate'. Or, if an integer 231 is passed then it will be used to initialise an alternating pattern (even = top, odd = lower). 232 lab_kwds (dict): Dictionary of keywords to pass to plt.text( ... ) for controlling labels. 233 **kwds: Keywords are passed to ax.axvline(...) if method=='range' or ax.plot(...) otherwise. 234 235 Returns: 236 Tuple containing 237 238 - fig: the figure that was plotted to 239 - ax: the axis that was plotted to 240 """ 241 242 plt = require("matplotlib.pyplot") 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 272 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 `hylite.hyfeature.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)
10class HyFeature(object): 11 """ 12 Utility class for representing and fitting individual or multiple absorption features. 13 """ 14 15 def __init__(self, name, pos, width, depth=1, data=None, color='g'): 16 """ 17 Args: 18 name (str) a name for this feature. 19 pos (float): the position of this feature (in nm). 20 width (float): the width of this feature (in nm). 21 data (ndarray): a real spectra associated with this feature (e.g. for feature fitting or from reference libraries). 22 Should be a numpy array such that data[0,:] gives wavelength and data[1,:] gives reflectance. 23 """ 24 25 self.name = name 26 self.pos = pos 27 self.width = width 28 self.depth = depth 29 self.color = color 30 self.data = data 31 self.mae = -1 32 self.strength = -1 33 self.components = None 34 self.endmembers = None 35 36 def get_start(self): 37 """ 38 Get start of feature. 39 40 Returns: 41 the feature position - 0.5 * feature width. 42 """ 43 44 return self.pos - self.width * 0.5 45 46 def get_end(self): 47 """ 48 Get approximate end of feature 49 50 Returns: 51 returns feature position - 0.5 * feature width. 52 """ 53 54 return self.pos + self.width * 0.5 55 56 57 ###################### 58 ## Feature models 59 ###################### 60 @classmethod 61 def gaussian(cls, _x, pos, width, depth): 62 """ 63 Static function for evaluating a gaussian feature model 64 65 Args: 66 x (ndarray): wavelengths (nanometres) to evaluate the feature over 67 pos (float): position for the gaussian function (nanometres) 68 width (float): width for the gaussian function. 69 depth (float): depth for the gaussian function (max to min) 70 offset (float): the vertical offset of the functions. Default is 1.0. 71 """ 72 return 1 - depth * np.exp( -(_x - pos)**2 / width ) 73 74 @classmethod 75 def multi_gauss(cls, x, pos, width, depth, asym=None): 76 """ 77 Static function for evaluating a multi-gaussian feature model 78 79 Args: 80 x (ndarray): wavelengths (nanometres) to evaluate the feature over 81 pos (list): a list of positions for each individual gaussian function (nanometres) 82 width (list): a list of widths for each individual gaussian function. 83 depth (list): a list of depths for each individual gaussian function (max to min) 84 asym (list): a list of feature asymmetries. The right-hand width will be calculated as: 85 w2 = asym * width. Default is 1.0. 86 """ 87 if asym is None: 88 asym = np.ones( len(width) ) 89 M = np.hstack( [[depth[i], pos[i], width[i], width[i]*asym[i]] for i in range(len(depth))] ) 90 evaluate = require("gfit").evaluate 91 y = evaluate( x, M, sym=False ) 92 return 1 - y 93 94 # noinspection PyDefaultArgument 95 def quick_plot(self, method='gauss', ax=None, label='top', lab_kwds={}, **kwds): 96 """ 97 Quickly plot this feature. 98 99 Args: 100 method (str): the method used to represent this feature. Options are: 101 102 - 'gauss' = represent using a gaussian function 103 - 'range' = draw vertical lines at pos - width / 2 and pos + width / 2. 104 - 'fill' = fill a rectangle in the region dominated by the feature with 'color' specifed in kwds. 105 - 'line' = plot a (vertical) line at the position of this feature. 106 - 'all' = plot with all of the above methods. 107 108 ax: an axis to add the plot to. If None (default) a new axis is created. 109 label (float): Label this feature (using it's name?). Options are None (no label), 'top', 'middle' or 'lower'. Or, 110 if an integer is passed, odd integers will be plotted as 'top' and even integers as 'lower'. 111 lab_kwds (dict): Dictionary of keywords to pass to plt.text( ... ) for controlling labels. 112 **kwds: Keywords are passed to ax.axvline(...) if method=='range' or ax.plot(...) otherwise. 113 114 Returns: 115 Tuple containing 116 117 - fig: the figure that was plotted to. 118 - ax: the axis that was plotted to. 119 """ 120 121 plt = require("matplotlib.pyplot") 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
Utility class for representing and fitting individual or multiple absorption features.
15 def __init__(self, name, pos, width, depth=1, data=None, color='g'): 16 """ 17 Args: 18 name (str) a name for this feature. 19 pos (float): the position of this feature (in nm). 20 width (float): the width of this feature (in nm). 21 data (ndarray): a real spectra associated with this feature (e.g. for feature fitting or from reference libraries). 22 Should be a numpy array such that data[0,:] gives wavelength and data[1,:] gives reflectance. 23 """ 24 25 self.name = name 26 self.pos = pos 27 self.width = width 28 self.depth = depth 29 self.color = color 30 self.data = data 31 self.mae = -1 32 self.strength = -1 33 self.components = None 34 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.
36 def get_start(self): 37 """ 38 Get start of feature. 39 40 Returns: 41 the feature position - 0.5 * feature width. 42 """ 43 44 return self.pos - self.width * 0.5
Get start of feature.
Returns:
the feature position - 0.5 * feature width.
46 def get_end(self): 47 """ 48 Get approximate end of feature 49 50 Returns: 51 returns feature position - 0.5 * feature width. 52 """ 53 54 return self.pos + self.width * 0.5
Get approximate end of feature
Returns: returns feature position - 0.5 * feature width.
60 @classmethod 61 def gaussian(cls, _x, pos, width, depth): 62 """ 63 Static function for evaluating a gaussian feature model 64 65 Args: 66 x (ndarray): wavelengths (nanometres) to evaluate the feature over 67 pos (float): position for the gaussian function (nanometres) 68 width (float): width for the gaussian function. 69 depth (float): depth for the gaussian function (max to min) 70 offset (float): the vertical offset of the functions. Default is 1.0. 71 """ 72 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.
74 @classmethod 75 def multi_gauss(cls, x, pos, width, depth, asym=None): 76 """ 77 Static function for evaluating a multi-gaussian feature model 78 79 Args: 80 x (ndarray): wavelengths (nanometres) to evaluate the feature over 81 pos (list): a list of positions for each individual gaussian function (nanometres) 82 width (list): a list of widths for each individual gaussian function. 83 depth (list): a list of depths for each individual gaussian function (max to min) 84 asym (list): a list of feature asymmetries. The right-hand width will be calculated as: 85 w2 = asym * width. Default is 1.0. 86 """ 87 if asym is None: 88 asym = np.ones( len(width) ) 89 M = np.hstack( [[depth[i], pos[i], width[i], width[i]*asym[i]] for i in range(len(depth))] ) 90 evaluate = require("gfit").evaluate 91 y = evaluate( x, M, sym=False ) 92 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.
95 def quick_plot(self, method='gauss', ax=None, label='top', lab_kwds={}, **kwds): 96 """ 97 Quickly plot this feature. 98 99 Args: 100 method (str): the method used to represent this feature. Options are: 101 102 - 'gauss' = represent using a gaussian function 103 - 'range' = draw vertical lines at pos - width / 2 and pos + width / 2. 104 - 'fill' = fill a rectangle in the region dominated by the feature with 'color' specifed in kwds. 105 - 'line' = plot a (vertical) line at the position of this feature. 106 - 'all' = plot with all of the above methods. 107 108 ax: an axis to add the plot to. If None (default) a new axis is created. 109 label (float): Label this feature (using it's name?). Options are None (no label), 'top', 'middle' or 'lower'. Or, 110 if an integer is passed, odd integers will be plotted as 'top' and even integers as 'lower'. 111 lab_kwds (dict): Dictionary of keywords to pass to plt.text( ... ) for controlling labels. 112 **kwds: Keywords are passed to ax.axvline(...) if method=='range' or ax.plot(...) otherwise. 113 114 Returns: 115 Tuple containing 116 117 - fig: the figure that was plotted to. 118 - ax: the axis that was plotted to. 119 """ 120 121 plt = require("matplotlib.pyplot") 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
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 deprecated 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 deprecated soon.
40class Themes: 41 """ 42 Some useful 'themes' (for plotting etc) 43 """ 44 ATMOSPHERE = Features.H2O #[HyFeature("H2O", 975, 30), HyFeature("H2O", 1395, 120), HyFeature("H2O", 1885, 180), HyFeature("H2O", 2450, 100)] 45 OH = Features.AlOH + Features.FeOH + Features.MgOH 46 DIAGNOSTIC = Features.Ferrous + Features.AlOH+Features.FeOH+Features.MgOH
Some useful 'themes' (for plotting etc)
Inherited Members
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 `hylite.hyfeature.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 plt = require("matplotlib.pyplot") 244 245 if ax is None: 246 fig, ax = plt.subplots() 247 248 # plot 249 if 'range' in method.lower() or 'all' in method.lower(): 250 super().quick_plot(method='range', ax=ax, label=None, **kwds) 251 if 'line' in method.lower() or 'all' in method.lower(): 252 for e in self.endmembers: # plot line for each end-member 253 e.quick_plot(method='line', ax=ax, label=None, **kwds) 254 if 'gauss' in method.lower() or 'all' in method.lower(): 255 for e in self.endmembers: # plot gaussian for each end-member 256 e.quick_plot(method='gauss', ax=ax, label=None, **kwds) 257 if isinstance(sublabel, int): sublabel += 1 258 if 'fill' in method.lower() or 'all' in method.lower(): 259 super().quick_plot(method='fill', ax=ax, label=None, **kwds) 260 261 # and do labels 262 if not suplabel is None: 263 if not isinstance(suplabel, tuple): suplabel = (suplabel, {}) 264 super().quick_plot(method='label', ax=ax, label=suplabel[0], lab_kwds=suplabel[1]) 265 if not sublabel is None: 266 if not isinstance(sublabel, tuple): sublabel = (sublabel, {}) 267 if isinstance(sublabel[0], str) and 'alt' in sublabel[0].lower(): 268 sublabel = (1, sublabel[1]) # alternate labelling 269 for e in self.endmembers: 270 e.quick_plot(method='label', ax=ax, label=sublabel[0], lab_kwds=sublabel[1]) 271 sublabel = (sublabel[0] + 1, sublabel[1]) 272 return ax.get_figure(), ax
A spectral feature with variable position due to a solid solution between known end-members.
198 def __init__(self, name, endmembers): 199 """ 200 Args: 201 endmembers (list): a list of `hylite.hyfeature.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
Arguments:
- endmembers (list): a list of
hylite.hyfeature.HyFeatureobjects representing each end-member.
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 plt = require("matplotlib.pyplot") 244 245 if ax is None: 246 fig, ax = plt.subplots() 247 248 # plot 249 if 'range' in method.lower() or 'all' in method.lower(): 250 super().quick_plot(method='range', ax=ax, label=None, **kwds) 251 if 'line' in method.lower() or 'all' in method.lower(): 252 for e in self.endmembers: # plot line for each end-member 253 e.quick_plot(method='line', ax=ax, label=None, **kwds) 254 if 'gauss' in method.lower() or 'all' in method.lower(): 255 for e in self.endmembers: # plot gaussian for each end-member 256 e.quick_plot(method='gauss', ax=ax, label=None, **kwds) 257 if isinstance(sublabel, int): sublabel += 1 258 if 'fill' in method.lower() or 'all' in method.lower(): 259 super().quick_plot(method='fill', ax=ax, label=None, **kwds) 260 261 # and do labels 262 if not suplabel is None: 263 if not isinstance(suplabel, tuple): suplabel = (suplabel, {}) 264 super().quick_plot(method='label', ax=ax, label=suplabel[0], lab_kwds=suplabel[1]) 265 if not sublabel is None: 266 if not isinstance(sublabel, tuple): sublabel = (sublabel, {}) 267 if isinstance(sublabel[0], str) and 'alt' in sublabel[0].lower(): 268 sublabel = (1, sublabel[1]) # alternate labelling 269 for e in self.endmembers: 270 e.quick_plot(method='label', ax=ax, label=sublabel[0], lab_kwds=sublabel[1]) 271 sublabel = (sublabel[0] + 1, sublabel[1]) 272 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
274class MixedFeature(HyFeature): 275 """ 276 A spectral feature resulting from a mixture of known sub-features. 277 """ 278 279 def __init__(self, name, components, **kwds): 280 """ 281 Args: 282 components: a list of `hylite.hyfeature.HyFeature` objects representing each end-member. 283 **kwds: keywords are passed to HyFeature.init() 284 """ 285 286 # init this feature so that it ~ covers all of its 'sub-features' 287 minw = min([e.pos - e.width / 2 for e in components]) 288 maxw = max([e.pos + e.width / 2 for e in components]) 289 depth = np.mean([e.depth for e in components]) 290 291 if not 'color' in kwds: 292 kwds['color'] = components[0].color 293 super().__init__(name, pos=(minw + maxw) / 2, width=maxw - minw, depth=depth, **kwds) 294 295 # store components 296 self.components = components 297 298 def count(self): 299 return len(self.components)
A spectral feature resulting from a mixture of known sub-features.
279 def __init__(self, name, components, **kwds): 280 """ 281 Args: 282 components: a list of `hylite.hyfeature.HyFeature` objects representing each end-member. 283 **kwds: keywords are passed to HyFeature.init() 284 """ 285 286 # init this feature so that it ~ covers all of its 'sub-features' 287 minw = min([e.pos - e.width / 2 for e in components]) 288 maxw = max([e.pos + e.width / 2 for e in components]) 289 depth = np.mean([e.depth for e in components]) 290 291 if not 'color' in kwds: 292 kwds['color'] = components[0].color 293 super().__init__(name, pos=(minw + maxw) / 2, width=maxw - minw, depth=depth, **kwds) 294 295 # store components 296 self.components = components
Arguments:
- components: a list of
hylite.hyfeature.HyFeatureobjects representing each end-member. - **kwds: keywords are passed to HyFeature.init()