Source code for spec_plots.utils.specutils_jwst.plotspec

"""
.. module:: plotspec
   :synopsis: Creates preview plots for the provided JWST spectrum.

.. moduleauthor:: Scott W. Fleming <fleming@stsci.edu>
"""

#--------------------
# Built-In Imports
#--------------------
from __future__ import absolute_import
from __future__ import division
import copy
import os
import sys
from builtins import str
#--------------------
# External Imports
#--------------------
import matplotlib
from matplotlib.ticker import FormatStrFormatter
from matplotlib import pyplot
import numpy
#--------------------
# Package Imports
#--------------------
from spec_plots.utils.specutils.specutilserror import SpecUtilsError
from spec_plots.utils.specutils.debug_oplot import debug_oplot
from spec_plots.utils.specutils.calc_covering_fraction import (
    calc_covering_fraction)
from spec_plots import __version__

if matplotlib.get_backend().lower() != 'agg':
    pyplot.switch_backend('Agg')
#--------------------

#--------------------
[docs] def plotspec(jwst_spectrum, output_type, output_file, flux_scale_factor, fluxerr_scale_factor, plot_metrics, dpi_val=96., output_size=1024, debug=False, full_ylabels=False, optimize=True): """ Accepts a JWSTSpectrum object from the READSPEC function and produces preview plots. :param jwst_spectrum: JWST spectrum as returned by READSPEC. :type jwst_spectrum: JWSTSpectrum :param output_type: What kind of output to make? :type output_type: str :param output_file: Name of output file (including full path). :type output_file: str :param flux_scale_factor: Max. allowed ratio between the flux and a median flux value, used in edge trimming. Default = 10. :type flux_scale_factor: float :param fluxerr_scale_factor: Max. allowed ratio between the flux uncertainty and a median flux uncertainty value, used in edge trimming. Default = 5. :type fluxerr_scale_factor: float :param plot_metrics: Collection of plot metrics (flux statistics, axis ranges, etc.) to use when making the plots. These are computed using `utils.specutils.calc_plot_metrics()`. :type plot_metrics: dict :param dpi_val: The DPI value of your device's monitor. Affects the size of the output plots. Default = 96. (applicable to most modern monitors). :type dpi_val: float :param output_size: Size of plot in pixels (plots are square in dimensions). Defaults to 1024. :param output_size: int :param debug: Should the output plots include debugging information (color-coded data points based on rejection criteria, shaded exclude regions)? Default = False. :type debug: bool :param full_ylabels: Should the y-labels contain the full values (including the power of 10 in scientific notation)? Default = False. :type full_ylabels: bool :param optimize: If set to True, will use a slightly optimized version of determining the plot covering fraction. :type optimize: bool :raises: OSError, utils.specutils.SpecUtilsError .. note:: This function assumes a screen resolution of 96 DPI in order to generate plots of the desired sizes. This is because matplotlib works in units of inches and DPI rather than pixels. """ # Make sure the plot size is set to an integer value. if output_size is not None: if not isinstance(output_size, int): output_size = int(round(output_size)) # Make sure the output path exists, if not, create it. if output_type != 'screen': if (os.path.dirname(output_file) != "" and not os.path.isdir(os.path.dirname(output_file))): try: os.mkdir(os.path.dirname(output_file)) except OSError as this_error: if this_error.errno == 13: sys.exit("*** MAKE_JWST_SPEC_PREVIEWS ERROR:" " Output directory could not be created," " " + repr(this_error.strerror) + "\n") else: raise is_bigplot = output_size > 128 # Start plot figure. this_figure, this_plotarea = pyplot.subplots(nrows=1, ncols=1, figsize=( output_size/dpi_val, output_size/dpi_val), dpi=dpi_val) # Adjust the plot geometry (margins, etc.) based on plot size. if is_bigplot: this_figure.subplots_adjust(hspace=0.3, top=0.915) else: this_figure.subplots_adjust(top=0.85, bottom=0.3, left=0.25, right=0.8) # Get the wavelengths, fluxes, flux uncertainties, and data quality # flags out of the spectrum. try: all_wls = jwst_spectrum.wavelengths all_fls = jwst_spectrum.fluxes all_flerrs = jwst_spectrum.fluxerrs all_dqs = jwst_spectrum.dqs except KeyError as the_error: raise SpecUtilsError("The provided stitched spectrum does not have" " the expected format, missing key " + str(the_error)+".") # Extract the optimal x-axis plot range from the plot_metrics dict, # since we use it a lot. optimal_xaxis_range = plot_metrics["optimal_xaxis_range"] # Plot the spectrum, but only if valid wavelength ranges for x-axis # are returned, otherwise plot a special "Fluxes Are All Zero or NaN" plot. if ( all(numpy.isfinite(optimal_xaxis_range)) and not all(numpy.isnan(plot_metrics["y_axis_range"])) ): # We plot the spectrum as a regular line for use # in calc_covering_fraction, it will be removed later. this_line = this_plotarea.plot(all_wls, all_fls, 'b') # Update y-axis range, but only adjust the ranges if this isn't # an all-zero flux case (and not in debug mode, in which case I want # to see the entire y-axis range). if not debug: this_plotarea.set_ylim(plot_metrics["y_axis_range"]) covering_fractions = calc_covering_fraction( this_figure, numpy.asarray([this_plotarea]), 0, optimize=optimize) # Note: here we remove the line we plotted before, it was only # so that calc_covering_fraction would have someting to draw on the # canvas and thereby determine which pixels were "blue" (i.e., part # of the plotted spectrum vs. background). this_line.pop(0).remove() # Now we plot the spectrum as a LineCollection so that the # transparency will have the desired effect, but, this is not # rendered on the canvas inside calc_covering_fraction, hence why we # need to plot it both as a regular line first. # Note: because we are re-using a LineCollection object in the # array of plot_metrics (specifically, when creating the # thumbnail-sized plot), we have to use a copy of the LineCollection # object, otherwise it will have Axes, Figure, etc. all defined and # resetting them to None does not work. Since this is only an issue # with thumbnail-sizes, this is only relevant for the first # LineCollection. this_collection = this_plotarea.add_collection(copy.copy( plot_metrics["line_collection"])) if covering_fractions > 30.: this_collection.set_alpha(0.1) # Turn on plot grid lines. this_plotarea.grid(True, linestyle="dashed") if is_bigplot: this_figure.suptitle(os.path.basename( jwst_spectrum.orig_file), fontsize=18, color='r') if debug: # Overplot points color-coded based on rejection criteria. debug_oplot(this_plotarea, "jwst", all_wls, all_fls, all_flerrs, all_dqs, plot_metrics["median_flux"], plot_metrics["median_fluxerr"], flux_scale_factor, fluxerr_scale_factor, plot_metrics["fluxerr_95th"]) # Overplot the x-axis edges that are trimmed to define the # y-axis plot range as a shaded area. this_plotarea.axvspan(numpy.nanmin(all_wls), optimal_xaxis_range[0], facecolor="lightgrey", alpha=0.5) this_plotarea.axvspan(optimal_xaxis_range[1], numpy.nanmax(all_wls), facecolor="lightgrey", alpha=0.5) # Overplot the avoid regions in a light color as a shaded area. for region in plot_metrics["avoid_regions"]: this_plotarea.axvspan(region.minwl, region.maxwl, facecolor="lightgrey", alpha=0.5) # This is where we ensure the x-axis range is based on the full # x-axis range, rather than using the optimum x-axis range. This is # done so that all the plots for a similar instrument setting will # have the same starting and ending plot values. min_wl = numpy.nanmin(all_wls) max_wl = numpy.nanmax(all_wls) xplot_buffer = (max_wl - min_wl) * 0.05 this_plotarea.set_xlim([min_wl-xplot_buffer, max_wl+xplot_buffer]) # Only use two tick labels (min and max wavelengths) for # thumbnails, because there isn't enough space otherwise. if not is_bigplot: this_plotarea.tick_params(labelsize=6., labelrotation=45., axis='y', pad=0.) minwl = numpy.nanmin(all_wls) maxwl = numpy.nanmax(all_wls) this_plotarea.set_xticks([minwl, maxwl]) this_plotarea.set_xticklabels(this_plotarea.get_xticks(), rotation=25.) this_plotarea.xaxis.set_major_formatter(FormatStrFormatter( "%6.1f")) else: # Make sure the font properties go back to normal. pyplot.rcdefaults() this_plotarea.tick_params(axis='x', labelsize=14) if jwst_spectrum.wavelengths_unit: this_plotarea.set_xlabel( f"Wavelength ({jwst_spectrum.wavelengths_unit:latex})", fontsize=16, color='k') else: this_plotarea.set_xlabel( f"Wavelength (unknown unit)", fontsize=16, color='k') if jwst_spectrum.fluxes_unit: this_plotarea.set_ylabel( f"Flux ({jwst_spectrum.fluxes_unit:latex})", fontsize=16, color='k') else: this_plotarea.set_xlabel( f"Wavelength (unknown unit)", fontsize=16, color='k') # If requested, include the powers of 10 part of the y-axis # tickmarks. if full_ylabels: this_plotarea.yaxis.set_major_formatter(FormatStrFormatter( '%3.2E')) else: # Otherwise this is a spectrum that has all zero fluxes, all NaN fluxes # or some other problem, and we make a default plot. Define the optimal # x-axis range to span the original spectrum. optimal_xaxis_range = [numpy.nanmin(all_wls), numpy.nanmax(all_wls)] this_plotarea.set_xlim(optimal_xaxis_range) # Make the plot background grey to distinguish that this is a # `special` plot. Turn off y-tick labels. this_plotarea.set_facecolor("lightgrey") this_plotarea.set_yticklabels([]) # Configure the plot units, text size, and other markings based # on whether this is a large or thumbnail-sized plot. if not is_bigplot: this_plotarea.tick_params(labelsize=6., labelrotation=45., axis='y', pad=0.) minwl = numpy.nanmin(all_wls) maxwl = numpy.nanmax(all_wls) this_plotarea.set_xticks([minwl, maxwl]) this_plotarea.set_xticklabels(this_plotarea.get_xticks(), rotation=25.) this_plotarea.xaxis.set_major_formatter(FormatStrFormatter( "%6.1f")) textsize = "small" plottext = "Fluxes are \n all 0 or NaN." else: # Make sure the font properties go back to normal. pyplot.rcdefaults() this_plotarea.tick_params(axis='x', labelsize=14) if jwst_spectrum.wavelengths_unit: this_plotarea.set_xlabel( f"Wavelength ({jwst_spectrum.wavelengths_unit:latex})", fontsize=16, color='k') else: this_plotarea.set_xlabel( f"Wavelength (unknown unit)", fontsize=16, color='k') if jwst_spectrum.fluxes_unit: this_plotarea.set_ylabel( f"Flux ({jwst_spectrum.fluxes_unit:latex})", fontsize=16, color='k') else: this_plotarea.set_xlabel( f"Wavelength (unknown unit)", fontsize=16, color='k') # If requested, include the powers of 10 part of the y-axis # tickmarks. if full_ylabels: this_plotarea.yaxis.set_major_formatter(FormatStrFormatter( '%3.2E')) textsize = "x-large" plottext = "Fluxes are all 0 or NaN." # Place the text with the informational message in the center of # the plot. this_plotarea.text(0.5, 0.5, plottext, horizontalalignment="center", verticalalignment="center", transform=this_plotarea.transAxes, size=textsize) # Display or plot to the desired format. if output_type != "screen": if output_size == 128: revised_output_file = output_file.split('.png')[0] + '_thumb.png' else: revised_output_file = output_file # Save figure. this_figure.savefig(revised_output_file, format=output_type, dpi=dpi_val) elif output_type == "screen": pyplot.show()
#--------------------