Source code for pyspeckit.spectrum.plotters

"""
=======
Plotter
=======

.. moduleauthor:: Adam Ginsburg <adam.g.ginsburg@gmail.com>
"""
from __future__ import print_function
import matplotlib
import matplotlib.figure
import numpy as np
import astropy.units as u
import copy
import inspect
from astropy import log

from . import widgets
from ..specwarnings import warn

interactive_help_message = """
Interactive key commands for plotter.  An additional help message may appear if
you have initiated the fitter.
'?' - bring up this message
'f' - initiate the /f/itter
'b' - initiate the /b/aseliner
'B' - initiate the /b/aseliner (reset the selection too)
'r' - re-attach matplotlib keys
'R' - redraw the plot cleanly
'i' : individual components / show each fitted component
"""

xlabel_table = {'speed': 'Velocity'}

[docs] class Plotter(object): """ Class to plot a spectrum """ def __init__(self, Spectrum, autorefresh=True, title="", xlabel=None, silent=True, plotscale=1.0, **kwargs): import matplotlib.pyplot self._pyplot = matplotlib.pyplot self.figure = None self.axis = None self.Spectrum = Spectrum # plot parameters self.offset = 0.0 # vertical offset self.autorefresh = autorefresh self.xlabel = xlabel self.title = title self.errorplot = None self.plotkwargs = kwargs self._xlim = [None,None] self._ylim = [None,None] self.debug = False self.keyclick = None self.silent = silent self.plotscale = plotscale self._xclick1 = None self._xclick2 = None self.automake_fitter_tool = False self._active_gui = None @property def _xunit(self): return self.Spectrum.xarr.unit def _get_prop(xy, minmax): def getprop(self): if xy == 'x': if minmax == 'min': if self._xlim[0] is not None and self._xunit: try: self._xlim[0]._unit = self._xunit except AttributeError: self._xlim[0] = u.Quantity(self._xlim[0], self._xunit) return self._xlim[0] elif minmax == 'max': if self._xlim[1] is not None and self._xunit: try: self._xlim[1]._unit = self._xunit except AttributeError: self._xlim[1] = u.Quantity(self._xlim[1], self._xunit) return self._xlim[1] elif xy == 'y': if minmax == 'min': return self._ylim[0] elif minmax == 'max': return self._ylim[1] return getprop def _set_prop(xy, minmax): def setprop(self, value): if self.debug: frm = inspect.stack() print(frm[1],"Setting %s%s to %s" % (xy,minmax,value)) if xy == 'x': if minmax == 'min': self._xlim[0] = value elif minmax == 'max': self._xlim[1] = value elif xy == 'y': if minmax == 'min': self._ylim[0] = value elif minmax == 'max': self._ylim[1] = value return setprop xmin = property(fget=_get_prop('x','min'),fset=_set_prop('x','min')) xmax = property(fget=_get_prop('x','max'),fset=_set_prop('x','max')) ymin = property(fget=_get_prop('y','min'),fset=_set_prop('y','min')) ymax = property(fget=_get_prop('y','max'),fset=_set_prop('y','max')) def _disconnect_matplotlib_keys(self): """ Disconnect matplotlib's built-in key-press handler (the one that implements the default 'k', 'l', 'g', 's', ... shortcuts) so it does not conflict with pyspeckit's interactive keys while an interactive session is active. The handler is restored by `_reconnect_matplotlib_keys`. """ if self.figure is None: return canvas = self.figure.canvas manager = getattr(canvas, 'manager', None) handler_id = getattr(manager, 'key_press_handler_id', None) if handler_id is not None: # mpl_disconnect is a no-op if the callback id is stale, so this # is safe even if something else already disconnected it canvas.mpl_disconnect(handler_id) manager.key_press_handler_id = None self._mpl_keys_disconnected = True def _reconnect_matplotlib_keys(self): """ Reconnect the default matplotlib key-press handler that was disconnected by `_disconnect_matplotlib_keys`. """ if self.figure is None or not getattr(self, '_mpl_keys_disconnected', False): return canvas = self.figure.canvas manager = getattr(canvas, 'manager', None) if manager is None: return if manager.key_press_handler_id is None: if hasattr(manager, 'key_press'): # matplotlib < 3.8: the default handler is the manager's # (bound) key_press method callback = manager.key_press else: # matplotlib >= 3.8 removed FigureManagerBase.key_press; the # default handler is the key_press_handler function, which # pulls the canvas from the event from matplotlib.backend_bases import key_press_handler callback = key_press_handler manager.key_press_handler_id = canvas.mpl_connect( 'key_press_event', callback) self._mpl_keys_disconnected = False def __call__(self, figure=None, axis=None, clear=True, autorefresh=None, plotscale=1.0, override_plotkwargs=False, **kwargs): """ Plot a spectrum Keywords: figure - either a matplotlib figure instance or a figure number to pass into pyplot.figure. axis - Alternative to figure, can pass an axis instance and use it as the plotting canvas clear - Clear the axis before plotting? """ # figure out where to put the plot if isinstance(figure,matplotlib.figure.Figure): self.figure = figure self.axis = self.figure.gca() elif type(figure) is int: self.figure = self._pyplot.figure(figure) self.axis = self.figure.gca() elif self.figure is None: if isinstance(axis,matplotlib.axes.Axes): self.axis = axis self.figure = axis.figure else: self.figure = self._pyplot.figure() if hasattr(self.figure, 'number') and not self._pyplot.fignum_exists(self.figure.number): self.figure = self._pyplot.figure(self.figure.number) # always re-connect the interactive keys to avoid frustration... self._mpl_reconnect() if axis is not None: #self._mpl_disconnect() self.axis = axis self.figure = axis.figure #self._mpl_connect() elif len(self.figure.axes) > 0 and self.axis is None: self.axis = self.figure.axes[0] # default to first axis elif self.axis is None: self.axis = self.figure.gca() # A check to deal with issue #117: if you close the figure, the axis # still exists, but it cannot be reattached to a figure if (hasattr(self.axis.get_figure(), 'number') and not (self.axis.get_figure() is self._pyplot.figure(self.axis.get_figure().number))): self.axis = self.figure.gca() if self.axis is not None and self.axis not in self.figure.axes: # if you've cleared the axis, but the figure is still open, you # need a new axis self.figure.add_axes(self.axis) if clear and self.axis is not None: self.axis.clear() # Need to empty the stored model plots if hasattr(self.Spectrum, 'fitter'): self.Spectrum.fitter.clear() if autorefresh is not None: self.autorefresh = autorefresh self.plotscale = plotscale if self.plotkwargs and not override_plotkwargs: self.plotkwargs.update(kwargs) else: self.plotkwargs = kwargs self.plot(**kwargs) def _mpl_connect(self): if self.keyclick is None: self.keyclick = self.figure.canvas.mpl_connect('key_press_event',self.parse_keys) def _mpl_disconnect(self): self.figure.canvas.mpl_disconnect(self.keyclick) self.keyclick = None
[docs] def disconnect(self): """ Disconnect the matplotlib interactivity of this pyspeckit plotter. """ self._mpl_disconnect()
[docs] def connect(self): """ Connect to the matplotlib key-parsing interactivity """ self._mpl_connect()
def _mpl_reconnect(self): self._mpl_disconnect() self._mpl_connect() # disable fullscreen & grid self._pyplot.rcParams['keymap.fullscreen'] = 'ctrl+f' self._pyplot.rcParams['keymap.grid'] = 'ctrl+g'
[docs] def plot(self, offset=0.0, xoffset=0.0, color='k', drawstyle='steps-mid', linewidth=0.5, errstyle=None, erralpha=0.2, errcolor=None, silent=None, reset=True, refresh=True, use_window_limits=None, useOffset=False, **kwargs): """ Plot the spectrum! Tries to automatically find a reasonable plotting range if one is not set. Parameters ---------- offset : float vertical offset to add to the spectrum before plotting. Useful if you want to overlay multiple spectra on a single plot xoffset: float An x-axis shift. I don't know why you'd want this... color : str default to plotting spectrum in black drawstyle : 'steps-mid' or str 'steps-mid' for histogram-style plotting. See matplotlib's plot for more information linewidth : float Line width in pixels. Narrow lines are helpful when histo-plotting errstyle : 'fill', 'bars', or None can be "fill", which draws partially transparent boxes around the data to show the error region, or "bars" which draws standard errorbars. ``None`` will display no errorbars useOffset : bool Use offset-style X/Y coordinates (e.g., 1 + 1.483e10)? Defaults to False because these are usually quite annoying. xmin/xmax/ymin/ymax : float override defaults for plot range. Once set, these parameters are sticky (i.e., replotting will use the same ranges). Passed to `reset_limits` reset_[xy]limits : bool Reset the limits to "sensible defaults". Passed to `reset_limits` ypeakscale : float Scale up the Y maximum value. Useful to keep the annotations away from the data. Passed to `reset_limits` reset : bool Reset the x/y axis limits? If set, `reset_limits` will be called. """ if self.axis is None: raise Exception("You must call the Plotter class to initiate the canvas before plotting.") self.offset = offset # there is a bug where this only seems to update the second time it is called self.label(**kwargs) self.label(**kwargs) for arg in ['title','xlabel','ylabel']: if arg in kwargs: kwargs.pop(arg) reset_kwargs = {} for arg in ['xmin', 'xmax', 'ymin', 'ymax', 'reset_xlimits', 'reset_ylimits', 'ypeakscale']: if arg in kwargs: reset_kwargs[arg] = kwargs.pop(arg) if (use_window_limits is None and any(k in reset_kwargs for k in ('xmin','xmax','reset_xlimits'))): use_window_limits = False if use_window_limits: self._stash_window_limits() # for filled errorbars, order matters. inds = np.argsort(self.Spectrum.xarr) if errstyle is not None: if errcolor is None: errcolor = color if errstyle == 'fill': self.errorplot = [self.axis.fill_between(steppify(self.Spectrum.xarr.value[inds]+xoffset, isX=True), steppify((self.Spectrum.data*self.plotscale+self.offset-self.Spectrum.error*self.plotscale)[inds]), steppify((self.Spectrum.data*self.plotscale+self.offset+self.Spectrum.error*self.plotscale)[inds]), facecolor=errcolor, edgecolor=errcolor, alpha=erralpha, **kwargs)] elif errstyle == 'bars': self.errorplot = self.axis.errorbar(self.Spectrum.xarr[inds].value+xoffset, self.Spectrum.data[inds]*self.plotscale+self.offset, yerr=self.Spectrum.error[inds]*self.plotscale, ecolor=errcolor, fmt='none', **kwargs) self._spectrumplot = self.axis.plot(self.Spectrum.xarr.value[inds]+xoffset, self.Spectrum.data[inds]*self.plotscale+self.offset, color=color, drawstyle=drawstyle, linewidth=linewidth, **kwargs) self.axis.ticklabel_format(useOffset=useOffset) if use_window_limits: self._reset_to_stashed_limits() if silent is not None: self.silent = silent if reset: self.reset_limits(use_window_limits=use_window_limits, **reset_kwargs) if self.autorefresh and refresh: self.refresh()
# Maybe it's OK to call 'plot' when there is an active gui tool # (e.g., baseline or specfit)? #if self._active_gui: # self._active_gui = None # warn("An active GUI was found while initializing the " # "plot. This is somewhat dangerous and may result " # "in broken interactivity.") def _stash_window_limits(self): self._window_limits = self.axis.get_xlim(),self.axis.get_ylim() if self.debug: print("Stashed window limits: ",self._window_limits) def _reset_to_stashed_limits(self): self.axis.set_xlim(*self._window_limits[0]) self.axis.set_ylim(*self._window_limits[1]) self.xmin,self.xmax = self._window_limits[0] self.ymin,self.ymax = self._window_limits[1] if self.debug: print("Recovered window limits: ",self._window_limits)
[docs] def reset_limits(self, xmin=None, xmax=None, ymin=None, ymax=None, reset_xlimits=True, reset_ylimits=True, ypeakscale=1.2, silent=None, use_window_limits=False, **kwargs): """ Automatically or manually reset the plot limits """ # if not use_window_limits: use_window_limits = False if self.debug: frame = inspect.currentframe() args, _, _, values = inspect.getargvalues(frame) print(zip(args,values)) if use_window_limits: # this means DO NOT reset! # it simply sets self.[xy][min/max] = current value self.set_limits_from_visible_window() else: if silent is not None: self.silent = silent # if self.xmin and self.xmax: # self.xmin/self.xmax may be None or Quantity; compare plain values xminval = getattr(self.xmin, 'value', self.xmin) xmaxval = getattr(self.xmax, 'value', self.xmax) if (reset_xlimits or xminval is None or xmaxval is None or self.Spectrum.xarr.min().value < xminval or self.Spectrum.xarr.max().value > xmaxval): if not self.silent: warn("Resetting X-axis min/max because the plot is out of bounds.") self.xmin = None self.xmax = None if xmin is not None: self.xmin = u.Quantity(xmin, self._xunit) elif self.xmin is None: self.xmin = u.Quantity(self.Spectrum.xarr.min().value, self._xunit) if xmax is not None: self.xmax = u.Quantity(xmax, self._xunit) elif self.xmax is None: self.xmax = u.Quantity(self.Spectrum.xarr.max().value, self._xunit) xpixmin = np.argmin(np.abs(self.Spectrum.xarr.value-self.xmin.value)) xpixmax = np.argmin(np.abs(self.Spectrum.xarr.value-self.xmax.value)) if xpixmin>xpixmax: xpixmin,xpixmax = xpixmax,xpixmin elif xpixmin == xpixmax: if reset_xlimits: raise Exception("Infinite recursion error. Maybe there are no valid data?") if not self.silent: warn("ERROR: the X axis limits specified were invalid. Resetting.") self.reset_limits(reset_xlimits=True, ymin=ymin, ymax=ymax, reset_ylimits=reset_ylimits, ypeakscale=ypeakscale, **kwargs) return if self.ymin is not None and self.ymax is not None: # this is utter nonsense.... if (np.nanmax(self.Spectrum.data) < self.ymin or np.nanmin(self.Spectrum.data) > self.ymax or reset_ylimits): if not self.silent and not reset_ylimits: warn("Resetting Y-axis min/max because the plot is out of bounds.") self.ymin = None self.ymax = None if ymin is not None: self.ymin = ymin elif self.ymin is None: yminval = np.nanmin(self.Spectrum.data[xpixmin:xpixmax]) # Increase the range fractionally. This means dividing a positive #, multiplying a negative # if yminval < 0: self.ymin = float(yminval)*float(ypeakscale) else: self.ymin = float(yminval)/float(ypeakscale) if ymax is not None: self.ymax = ymax elif self.ymax is None: ymaxval = (np.nanmax(self.Spectrum.data[xpixmin:xpixmax])-self.ymin) if ymaxval > 0: self.ymax = float(ymaxval) * float(ypeakscale) + self.ymin else: self.ymax = float(ymaxval) / float(ypeakscale) + self.ymin self.ymin += self.offset self.ymax += self.offset self.axis.set_xlim(self.xmin.value if hasattr(self.xmin, 'value') else self.xmin, self.xmax.value if hasattr(self.xmax, 'value') else self.xmax) self.axis.set_ylim(self.ymin, self.ymax)
[docs] def label(self, title=None, xlabel=None, ylabel=None, verbose_label=False, **kwargs): """ Label the plot, with an attempt to parse standard units into nice latex labels Parameters ---------- title : str xlabel : str ylabel : str verbose_label: bool """ if title is not None: self.title = title elif hasattr(self.Spectrum,'specname'): self.title = self.Spectrum.specname if self.title != "": self.axis.set_title(self.title) if xlabel is not None: log.debug("setting xlabel={0}".format(xlabel)) self.xlabel = xlabel elif self._xunit: try: self.xlabel = xlabel_table[str(self._xunit.physical_type).lower()] except KeyError: self.xlabel = str(self._xunit.physical_type) # WAS: self.xlabel += " ("+u.Unit(self._xunit).to_string()+")" self.xlabel += " ({0})".format(self._xunit.to_string()) log.debug("xunit is {1}. set xlabel={0}".format(self.xlabel, self._xunit)) if verbose_label: self.xlabel = "%s %s" % (str(self.Spectrum.xarr.velocity_convention), self.xlabel) else: log.warning("Plotter: xlabel was not set") if self.xlabel is not None: self.axis.set_xlabel(self.xlabel) if ylabel is not None: self.axis.set_ylabel(ylabel) elif self.Spectrum.unit in ['Ta*','Tastar']: self.axis.set_ylabel("$T_A^*$ (K)") elif self.Spectrum.unit in ['K']: self.axis.set_ylabel("Brightness Temperature $T$ (K)") elif self.Spectrum.unit == 'mJy': self.axis.set_ylabel("$S_\\nu$ (mJy)") elif self.Spectrum.unit == 'Jy': self.axis.set_ylabel("$S_\\nu$ (Jy)") else: if isinstance(self.Spectrum.unit, str) and "$" in self.Spectrum.unit: # assume LaTeX already self.axis.set_ylabel(self.Spectrum.unit) elif isinstance(self.Spectrum.unit, str): self.axis.set_ylabel(self.Spectrum.unit) else: label_units = self.Spectrum.unit.to_string(format='latex') if 'mathring{A}' in label_units: label_units = label_units.replace('\\mathring{A}', 'A') if '\\overset' in label_units: label_units = label_units.replace('\\overset', '^') self.axis.set_ylabel(label_units)
@property def ylabel(self): return self.axis.get_ylabel() def refresh(self): if self.axis is not None: self.axis.figure.canvas.draw()
[docs] def savefig(self,fname,bbox_inches='tight',**kwargs): """ simple wrapper of maplotlib's savefig. """ self.axis.figure.savefig(fname,bbox_inches=bbox_inches,**kwargs)
[docs] def parse_keys(self,event): """ Parse key commands entered from the keyboard """ if hasattr(event,'key'): if event.key == '?': print(interactive_help_message) elif event.key == 'f': print("\n\nFitter initiated from the interactive plotter.") # extra optional text: # Matplotlib shortcut keys ('g','l','p',etc.) are disabled. Re-enable with 'r'" if self._active_gui == self.Spectrum.specfit and self._active_gui._check_connections(verbose=False): print("Fitter is already active. Use 'q' to quit the fitter.") elif self._active_gui == self.Spectrum.specfit and not self._active_gui._check_connections(verbose=False): # forcibly clear connections self._active_gui.clear_all_connections() # the 'clear_all_connections' code *explicitly* makes the # following line correct, except in the case that there is # no canvas... assert self._active_gui is None self.activate_interactive_fitter() else: self.activate_interactive_fitter() assert self._active_gui == self.Spectrum.specfit assert self._active_gui._check_connections(verbose=False) if not hasattr(self,'FitterTool') and self.automake_fitter_tool: self.FitterTool = widgets.FitterTools(self.Spectrum.specfit, self.figure) elif hasattr(self,'FitterTool') and self.FitterTool.toolfig.number not in self._pyplot.get_fignums(): self.FitterTool = widgets.FitterTools(self.Spectrum.specfit, self.figure) elif event.key is not None and event.key.lower() == 'b': if event.key == 'b': print("\n\nBaseline initiated from the interactive plotter") elif event.key == 'B': print("\n\nBaseline initiated from the interactive plotter (with reset)") print("Matplotlib shortcut keys ('g','l','p',etc.) are disabled. Re-enable with 'r'") self.activate_interactive_baseline_fitter(reset_selection=(event.key=='B')) if not hasattr(self,'FitterTool') and self.automake_fitter_tool: self.FitterTool = widgets.FitterTools(self.Spectrum.specfit, self.figure) elif hasattr(self,'FitterTool') and self.FitterTool.toolfig.number not in self._pyplot.get_fignums(): self.FitterTool = widgets.FitterTools(self.Spectrum.specfit, self.figure) elif event.key == 'r': # print("\n\nReconnected matplotlib shortcut keys.") self._reconnect_matplotlib_keys() elif event.key == 'R': self() elif event.key == 'i': self.Spectrum.specfit.plot_fit(show_components=True)
def get_two_clicks(self,event): if self._xclick1 is None: self._xclick1 = event.xdata elif self._xclick2 is None: self._xclick2 = event.xdata
[docs] def set_limits_from_visible_window(self, debug=False): """ Hopefully self-descriptive: set the x and y limits from the currently visible window (use this if you use the pan/zoom tools or manually change the limits) """ if debug: print("Changing x limits from {},{} to {},{}".format(self.xmin,self.xmax,self.axis.get_xlim()[0],self.axis.get_xlim()[1])) print("Changing y limits from {},{} to {},{}".format(self.ymin,self.ymax,self.axis.get_ylim()[0],self.axis.get_ylim()[1])) self.xmin, self.xmax = self.axis.get_xlim() self.ymin, self.ymax = self.axis.get_ylim() if debug: print("New x limits {},{} == {},{}".format(self.xmin,self.xmax,self.axis.get_xlim()[0],self.axis.get_xlim()[1])) print("New y limits {},{} == {},{}".format(self.ymin,self.ymax,self.axis.get_ylim()[0],self.axis.get_ylim()[1]))
[docs] def copy(self, parent=None): """ Create a copy of the plotter with blank (uninitialized) axis & figure [ parent ] A spectroscopic axis instance that is the parent of the specfit instance. This needs to be specified at some point, but defaults to None to prevent overwriting a previous plot. """ newplotter = copy.copy(self) newplotter.Spectrum = parent newplotter.axis = None newplotter.figure = None return newplotter
[docs] def line_ids(self, line_names, line_xvals, xval_units=None, auto_yloc=True, velocity_offset=None, velocity_convention='radio', auto_yloc_fraction=0.9, **kwargs): """ Add line ID labels to a plot using lineid_plot http://oneau.wordpress.com/2011/10/01/line-id-plot/ https://github.com/phn/lineid_plot http://packages.python.org/lineid_plot/ Parameters ---------- line_names : list A list of strings to label the specified x-axis values line_xvals : list List of x-axis values (e.g., wavelengths) at which to label the lines. Can be a list of quantities. xval_units : string The unit of the line_xvals if they are not given as quantities velocity_offset : quantity A velocity offset to apply to the inputs if they are in frequency or wavelength units velocity_convention : 'radio' or 'optical' or 'doppler' Used if the velocity offset is given auto_yloc : bool If set, overrides box_loc and arrow_tip (the vertical position of the lineid labels) in kwargs to be `auto_yloc_fraction` of the plot range auto_yloc_fraction: float in range [0,1] The fraction of the plot (vertically) at which to place labels Examples -------- >>> import numpy as np >>> import pyspeckit >>> sp = pyspeckit.Spectrum( xarr=pyspeckit.units.SpectroscopicAxis(np.linspace(-50,50,101), unit='km/s', refX=6562.8, refX_unit='angstrom'), data=np.random.randn(101), error=np.ones(101)) >>> sp.plotter() >>> sp.plotter.line_ids(['H$\\alpha$'],[6562.8],xval_units='angstrom') """ import lineid_plot if velocity_offset is not None: assert velocity_offset.unit.is_equivalent(u.km/u.s) doppler = getattr(u, 'doppler_{0}'.format(velocity_convention)) if self.Spectrum.xarr.refX is not None: equivalency = doppler(self.Spectrum.xarr.refX) else: equivalency = doppler(self.Spectrum.xarr.as_unit(u.GHz)[0]) xvals = [] linenames_toplot = [] for xv,ln in zip(line_xvals, line_names): if hasattr(xv, 'unit'): pass else: xv = u.Quantity(xv, xval_units) xv = xv.to(u.km/u.s, equivalencies=equivalency) if velocity_offset is not None: xv = xv + velocity_offset xv = xv.to(self.Spectrum.xarr.unit, equivalencies=equivalency) if self.Spectrum.xarr.in_range(xv): xvals.append(xv.value) linenames_toplot.append(ln) if len(xvals) != len(line_xvals): log.warning("Skipped {0} out-of-bounds lines when plotting line IDs." .format(len(line_xvals)-len(xvals))) if auto_yloc: yr = self.axis.get_ylim() kwargs['box_loc'] = (yr[1]-yr[0])*auto_yloc_fraction + yr[0] kwargs['arrow_tip'] = (yr[1]-yr[0])*(auto_yloc_fraction*0.9) + yr[0] lineid_plot.plot_line_ids(self.Spectrum.xarr, self.Spectrum.data, xvals, linenames_toplot, ax=self.axis, **kwargs)
[docs] def line_ids_from_measurements(self, auto_yloc=True, auto_yloc_fraction=0.9, **kwargs): """ Add line ID labels to a plot using lineid_plot http://oneau.wordpress.com/2011/10/01/line-id-plot/ https://github.com/phn/lineid_plot http://packages.python.org/lineid_plot/ Parameters ---------- auto_yloc : bool If set, overrides box_loc and arrow_tip (the vertical position of the lineid labels) in kwargs to be `auto_yloc_fraction` of the plot range auto_yloc_fraction: float in range [0,1] The fraction of the plot (vertically) at which to place labels Examples -------- >>> import numpy as np >>> import pyspeckit >>> sp = pyspeckit.Spectrum( xarr=pyspeckit.units.SpectroscopicAxis(np.linspace(-50,50,101), units='km/s', refX=6562.8, refX_unit='angstroms'), data=np.random.randn(101), error=np.ones(101)) >>> sp.plotter() >>> sp.specfit(multifit=None, fittype='gaussian', guesses=[1,0,1]) # fitting noise.... >>> sp.measure() >>> sp.plotter.line_ids_from_measurements() """ import lineid_plot if hasattr(self.Spectrum,'measurements'): measurements = self.Spectrum.measurements if auto_yloc: yr = self.axis.get_ylim() kwargs['box_loc'] = (yr[1]-yr[0])*auto_yloc_fraction + yr[0] kwargs['arrow_tip'] = (yr[1]-yr[0])*(auto_yloc_fraction*0.9) + yr[0] lineid_plot.plot_line_ids(self.Spectrum.xarr, self.Spectrum.data, [v['pos'] for v in measurements.lines.values()], measurements.lines.keys(), ax=self.axis, **kwargs) else: warn("Cannot add line IDs from measurements unless measurements have been made!")
[docs] def activate_interactive_fitter(self): """ Attempt to activate the interactive fitter """ if self._active_gui is not None: # This should not be reachable. Clearing connections is the # "right" behavior if this becomes reachable, but I'd rather raise # an exception because I don't want to get here ever self._active_gui.clear_all_connections() raise ValueError("GUI was active when 'f' key pressed") self._activate_interactive(self.Spectrum.specfit, interactive=True)
[docs] def activate_interactive_baseline_fitter(self, **kwargs): """ Attempt to activate the interactive baseline fitter """ if self._active_gui is not None: # This should not be reachable. Clearing connections is the # "right" behavior if this becomes reachable, but I'd rather raise # an exception because I don't want to get here ever gui_was = self._active_gui self._active_gui.clear_all_connections() raise ValueError("GUI {0} was active when 'b' key pressed" .format(gui_was)) self._activate_interactive(self.Spectrum.baseline, interactive=True, **kwargs)
def _activate_interactive(self, object_to_activate, **kwargs): self._disconnect_matplotlib_keys() self._active_gui = object_to_activate # activating the gui calls clear_all_connections, which disconnects the # gui try: self._active_gui(**kwargs) self._active_gui = object_to_activate assert self._active_gui is not None except Exception as ex: self._active_gui = None raise ex
def parse_units(labelstring): import re labelstring = re.sub("um","$\\mu$m",labelstring) labelstring = re.sub("-1","$^{-1}$",labelstring) labelstring = re.sub("-2","$^{-2}$",labelstring) labelstring = re.sub("-3","$^{-3}$",labelstring) labelstring = re.sub("ergss","ergs s",labelstring) return labelstring
[docs] def parse_norm(norm): """ Expected format: norm = 10E15 """ try: base, exp = norm.split('E') except ValueError: base, exp = norm.split('e') if float(base) == 1.0: norm = '10' else: norm = base norm += '^{%s}' % exp return norm
[docs] def steppify(arr,isX=False): """ *support function* Converts an array to double-length for step plotting """ if isX: interval = abs(arr[1:]-arr[:-1]) / 2.0 newarr = np.array(list(zip(arr[:-1]-interval,arr[:-1]+interval))).ravel() newarr = np.concatenate([newarr,2*[newarr[-1]+interval[-1]]]) else: newarr = np.array(list(zip(arr,arr))).ravel() return newarr