Plotting spectra with line lists#

SpecpolFlow includes a few functions for easily plotting line lists and spectra. This can be handy for identifying lines in an observed (or synthetic) spectrum.

The plots these functions generate are pretty good, particularly for a quick look. However, for more sophisticated plots you may wish to make your own plots from scratch, or use the more flexible plot_lineList() function. For plotting line identifications, there are wide variety of approaches one can use, and you may wish to consider using alternative packages such as lineid_plot (see their webpage and GitHub).

A quick look from the command line#

The quickest option is to use the command line tool spf-plotspec. Running the command spf-plotspec -h provides the help documentation for this tool. It can plot 2 and 3 column spectra (intensity only) and 6 column spectra (intensity and polarization). The line lists are in a VALD “extract stellar” “long format”. In the simplest case, you can run

spf-plotspec observation_file.s -l line_list_file.dat

Both the line list and the observation are optional. You can also plot multiple observation files, and you can include the -g flag to generate a legend with the file names:

spf-plotspec observation_file_1.s observation_file_2.s observation_file_3.s -l line_list_file.dat -g

You can plot lines with only depth values greater than a user-specified threshold with the -d flag:

spf-plotspec observation_file.s -l line_list_file.dat -d 0.1

Errorbars can be included in the plot with the -e flag. The circular polarization spectrum can be plotted with -s V, and both Stokes I and the polarization can be plotted with -s IV:

spf-plotspec observation_file.s -l line_list_file.dat -d 0.1 -e -s IV

The plotting window has a couple key commands beyond the normal matplotlib options. The arrow keys pan the plot. The i and o keys zoom in and out. The a key auto-scales both axes, and A auto-scales only the y-axis (useful if the x range is good but y is too zoomed in).

Plotting spectra and line lists with plot_obs_lines()#

Within the Python interface for SpecpolFlow, the plot_obs_lines() function is a tool for quickly plotting an observation and/or line list. There is also a function for just plotting line lists (with more flexibility) called plot_lineList(), discussed below.

First import specpolFlow and any other packages (in this case matplotlib).

Hide code cell content

## Importing Necessary Packages
import specpolFlow as pol
import numpy as np
import matplotlib.pyplot as plt

In a stand-alone script, running matplotlib’s show() function will generate an interactive window, which is handy for exploring a spectrum. The plot_obs_lines() function adds some extra key binds to that window. The arrow keys pan the plot, i and o zoom in and out, a auto-scales both axes, A auto-scales only the y-axis, and z activates matplotlib’s zoom tool.

Note

In a Jupyter notebook, matplotlib generates static images by default, not interactive figures. For interactive plots, the simplest solution is to just run a Python script rather than a notebook. Matplotlib has some documentation about interactive figures and specifically about interactive plots in Jupyter notebooks. A way to get interactive plotting working from inside a Jupyter notebook is to change the backend used. The best option may depend on the operating system, backends installed, and program you are using to view the notebook. There are several options for opening the matplotlib figures in separate windows, and the ipympl package provides an option for opening interactive windows inside the notebook, for some notebook viewers. Some example commands for setting different interactive matplotlib backends in a Jupyter Notebook are below.

# Some possible options for changing matplotlib backends in a Jupyter notebook.
#
# For the ipympl backend (you may need to install it), 
# which embeds interactive plots in the notebook:
#  %matplotlib widget
# For using the system default backend with interactive windows:
#  %matplotlib
# For specifying the tk backend (if you have it), opens new windows:
#  %matplotlib tk
# For specifying the Qt backend (if you have it), opens new windows:
#  %matplotlib qt5
# For specifying the Mac OSX backend (if you have it), opens new windows:
#  %matplotlib osx

You can run the plot_obs_lines() function with a spectrum or a List of spectra, and a line list or List of line lists. You can either provide file names for the spectrum and/or line list files, or you can provide SpecpolFlow Spectrum objects and LineList objects. The function returns a matplotlib figure and an axes object. In the examples here, we use the line list file LongList_T27000G35.dat and the observed spectra files hd46328_test_1.s, hd46328_test_2.s, and hd46328_test_3.s.

fig, ax = pol.plot_obs_lines('../GetStarted/OneObservationFlow_tutorialfiles/hd46328_test_1.s', 
                             lineList='../GetStarted/OneObservationFlow_tutorialfiles/LongList_T27000G35.dat')
ax.set_xlim(500.0, 510.0)
ax.set_ylim(0.6, 1.1)
plt.show()
Warning: line list and observations appear to have different units!
  Attempting to correct, assuming an observation in nm and a line list in A
  Scaling the line list wavelengths by 0.1
../_images/f4a7c75f2c6ff1a8a8d9b888f43c7c9d9102a52ecc2b6c03751537d9be429633.png

In this example, there is a warning message because our observation is in units of nanometers, while the line list is in angstroms. The function makes a guess about that and attempts to correct it, but it is safer to make sure the units are consistent. One way to do that is to read in the line list first, and convert the wavelength of the LineList object to nm.

lineList = pol.read_VALD('../GetStarted/OneObservationFlow_tutorialfiles/LongList_T27000G35.dat')
lineList.wl = 0.1*lineList.wl

We can limit the line list to only lines with stronger depths, as estimated by VALD, with the depthCut parameter.

We can also plot a couple different observations by providing them as a list. In this case, the star pulsates with a large velocity amplitude, and you can see the lines shift over time. There is an option with showLegend to include a legend with the file names of those spectra.

fig, ax = pol.plot_obs_lines(['../GetStarted/OneObservationFlow_tutorialfiles/hd46328_test_1.s',
                              '../GetStarted/OneObservationFlow_tutorialfiles/hd46328_test_2.s',
                              '../GetStarted/OneObservationFlow_tutorialfiles/hd46328_test_3.s'],
                             lineList=lineList, showLegend=True,
                             depthCut=0.05)
ax.set_xlim(500.0, 510.0)
ax.set_ylim(0.6, 1.1)
ax.set_xlabel('Wavelength (nm)')
plt.show()
../_images/1c6a1d5c2b4aebddba1a55454beff24354f0239448cfa71ed642e350ee5e6552.png

We can apply a Doppler shift to the spectrum using velSpec or to the line list using velLines (for velocity in km/s). If we look at one of the observations where the radial velocity of the star is higher this can be handy.

We can also plot multiple line lists: for example, here we make two line lists, one containing only He lines, the other containing everything else. This could be handy for finding lines of a particular ion.

# Split the line list into He lines and other lines
isLineHe = (lineList.ion == 'He 1') | (lineList.ion == 'He 2')
lineListHe = lineList[isLineHe]
lineListOther = lineList[np.logical_not(isLineHe)]

# Plot the two line lists
fig, ax = pol.plot_obs_lines('../GetStarted/OneObservationFlow_tutorialfiles/hd46328_test_2.s',
                             velSpec=-42.0,
                             lineList=[lineListOther, lineListHe],
                             depthCut=0.05)
ax.set_xlim(490.0, 510.0)
ax.set_ylim(0.5, 1.15)
plt.show()
../_images/7c83ae2d09342ce9d6e5d2b0d62612a416394580c5625b4d923ec5b2f6cfba34.png

There is an option for plotting Stokes V, Null 1, or Null 2 spectra, using stokes, which can be 'I', 'V', 'N1', 'N2', or 'IV'. The 'IV' option will plot both the Stokes I and V spectra together, with the V spectrum shifted vertically above I. This can be useful if you are searching for Stokes V signatures in individual lines.

# plotting Stokes V
fig, ax = pol.plot_obs_lines('../GetStarted/OneObservationFlow_tutorialfiles/hd46328_test_1.s', velSpec=-12.0,
                             lineList=lineList, depthCut=0.05, 
                             stokes='IV')
ax.set_xlim(500.0, 505.0)
ax.set_ylim(0.7, 1.1)

# plotting Stokes V, but scaled up by a factor of 5, and showing errorbars
spec = pol.read_spectrum('../GetStarted/OneObservationFlow_tutorialfiles/hd46328_test_1.s')
spec.specV = spec.specV*5.0
spec.specN1 = spec.specN1*5.0
spec.specSig = spec.specSig*5.0
fig, ax = pol.plot_obs_lines(spec, velSpec=-12.0,
                             lineList=lineList, depthCut=0.05, 
                             stokes='IV', showErr=True)
ax.set_xlim(504.0, 505.5)
ax.set_ylim(0.7, 1.1)
plt.show()
../_images/afdc7a1116058e01acf1b679a27ee9d965aef1b62ae68aebd166aff4fc18771c.png ../_images/d13b40a9f286172f80dee23e759cb35cb0c6be3fcedfcd356430ad16aaa0d2de.png

This function can also be useful for comparing synthetic spectra to observations. If the model is in a 2 column format (or 3 or 6 columns), then you can just use pol.plot_obs_lines(['observation_file', 'model_file'], lineList='linelist_file'). If the model file is in a more complex format, you can still pack it into a Spectrum object and plot that.

Plotting line lists with plot_lineList()#

The function plot_lineList() plots only a line list, and it has a lot more formatting options. It will either make a new matplotlib figure and axes if none is provided, or you can provide an axes object for the line list to be drawn in. It provides options for how and where line labels are drawn, as well as how tick marks are drawn.

In a basic case it can be used similarly to the above examples. In this example, we plot the observation with matplotlib, then add the line list to that matplotlib axes object using plot_lineList().

# Read an observation and a line list, and shift them all into units of nm
spec = pol.read_spectrum('../GetStarted/OneObservationFlow_tutorialfiles/hd46328_test_1.s')
spec = spec.merge_orders('trim') # merge spectral orders for nicer plots
lineList = pol.read_VALD('../GetStarted/OneObservationFlow_tutorialfiles/LongList_T27000G35.dat')
lineList.wl = 0.1*lineList.wl #convert from A to nm

# plot the observation
fig, ax = plt.subplots(figsize=(12,6))
ax.plot(spec.wl, spec.specI, 'k')
ax.set_xlim(500.0, 510.0)
ax.set_ylim(0.6, 1.1)

# plot the line list, using the same axes as the observation
pol.plot_lineList(lineList, ax=ax)
plt.show()
../_images/fb383d967236c4d555d4d036d93d2bb78d74bdfd44ddf2829a57000669d18306.png

With this function we can change the color of the tick marks (using linecolor) and the labels (using color), rotate the labels horizontally (using rotation), and use a roman numeral for the ionization stage (using romanIon). We also apply a depth cutoff (using depthCut), selecting only lines with a depth estimate greater than this value to show only the more important labels.

# plot the observation
fig, ax = plt.subplots(figsize=(12,6))
ax.plot(spec.wl, spec.specI, 'k')
ax.set_xlim(500.0, 510.0)
ax.set_ylim(0.6, 1.1)

# plot the line list, using the same axes as the observation
pol.plot_lineList(lineList, depthCut=0.1, 
                  linecolor='orchid', color='purple',
                  rotation='horizontal', romanIon=True,
                  ax=ax)
plt.show()
../_images/d32f828a1fe29d183ebfec6770d9b2ea5c799e86b31141e47d4ec4634ceaa42c.png

You can change how deep the tickmarks are with scaleDepths, how high up the tick marks are drawn with cont, and how high above that the labels are placed with rise. The label font size can be changed with fontsize and spacing between labels can be changed with padding. The tick marks are drawn down to a value proportional to the depth of the line. If you want all of the tick marks drawn down to the same point, you can set the depth values to the same number (e.g. lineList.depth[:] = 0.5; although in this case using depthCut won’t be effective).

# plot the observation
fig, ax = plt.subplots(figsize=(12,6))
ax.plot(spec.wl, spec.specI, 'k')
ax.set_xlim(500.0, 510.0)
ax.set_ylim(0.6, 1.1)

# plot the line list, showing stronger lines with big symbols
pol.plot_lineList(lineList, depthCut=0.1, 
                  rotation='horizontal', romanIon=True,
                  scaleDepths=0.9, linecolor='slateblue',
                  cont=1.05, rise=0.01,
                  fontsize=12.0, padding=10.0,
                  ax=ax)

# plot a second line list showing only weaker lines, all with the same depth plotted
lineList2 = lineList[lineList.depth < 0.1]
lineList2.depth[:] = 0.2
pol.plot_lineList(lineList2,
                  rotation='horizontal', romanIon=True,
                  cont=1.01, rise=0.02, fontsize=6.0,
                  ax=ax)
plt.show()
../_images/6e0b93b0a2c2aa69f83458a8f6b8f3d64ef4dc625f93b0961215e1311038f495.png

If you are trying to fit a lot of labels onto a plot, you can also include multiple rows of labels using nrows, although this can be visually a bit more confusing.

# plot the observation
fig, ax = plt.subplots(figsize=(12,6))
ax.plot(spec.wl, spec.specI, 'k')
ax.set_xlim(500.0, 510.0)
ax.set_ylim(0.6, 1.13)

# plot the line list, using the same axes as the observation
pol.plot_lineList(lineList, rotation='horizontal',
                  nrows=3,
                  ax=ax)
plt.show()
../_images/d6550283697db500612669736eaadad619ab9803cbbb47cacfb870dfb86cf52f.png

If you are trying to plot a very large number of lines and labels, then drawing all the labels can become quite slow. To keep the function running fast, the number of labels drawn can be limited with maxLabels. When this is used, only that many of the strongest lines will have labels drawn, but tick marks will still be drawn for all lines. The plot_lineList() function will try to plot all labels by default, but often it is worth limiting this. (The plot_obs_lines() function by default limits maxLabels to 100.)

When many labels are drawn they will start to overlap on the plot. The function will try to avoid overlapping labels by shifting them horizontally in the plot (unless you set avoidOverlaps=False), but when there are too many labels and not enough space labels are just drawn at the position of lines.

In this example we plot a large number of lines lines for a large region of spectrum, but limit the total number of labels drawn.

# plot the observation
fig, ax = plt.subplots(figsize=(12,6))
ax.plot(spec.wl, spec.specI, 'k')
ax.set_xlim(500.0, 600.0)
ax.set_ylim(0.6, 1.1)

# plot the line list, using the same axes as the observation
pol.plot_lineList(lineList, linecolor='orchid',
                  maxLabels=50, fontsize=6,
                  ax=ax)
plt.show()
../_images/47b73fa8c1aebb89f99291634727d795e5aa5480ab7f2d6d4adaf13ad9dca450.png

When there is an interactive matplotlib window, the plot_lineList function binds several keys (arrow keys to panning, i and o to zoom in and out, a autoscale the axes, A autoscale just the y-axis, and z activate matplotlib’s zoom tool). This uses matplotlib’s event handling and callback tools. To prevent this behaviour, e.g. for embedding inside another program, use the bindKeys=False option.