hylite documentation|Stable (master)Development (dev)

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)
class HyFeature:
 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.

HyFeature(name, pos, width, depth=1, data=None, color='g')
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.
name
pos
width
depth
color
data
mae
strength
components
endmembers
def get_start(self):
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.

def get_end(self):
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.

@classmethod
def gaussian(cls, _x, pos, width, depth):
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.
@classmethod
def multi_gauss(cls, x, pos, width, depth, asym=None):
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.
def quick_plot(self, method='gauss', ax=None, label='top', lab_kwds={}, **kwds):
 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.
class HyFeature.Features:
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.

class HyFeature.Themes:
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)

class MultiFeature(HyFeature):
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.

MultiFeature(name, endmembers)
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
def count(self):
213    def count(self):
214        return len(self.endmembers)
def quick_plot( self, method='fill+line', ax=None, suplabel=None, sublabel=('alternate', {}), **kwds):
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
class MixedFeature(HyFeature):
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.

MixedFeature(name, components, **kwds)
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.HyFeature objects representing each end-member.
  • **kwds: keywords are passed to HyFeature.init()
components
def count(self):
298    def count(self):
299        return len(self.components)