### Profile

Statistics
| Branch: | Tag: | Revision:

## pycama / src / pycama / AnalysisAndPlot.py @ 834:d989df597b80

 1 #!/usr/bin/env python3  # -*- coding: utf-8 -*-  # Copyright 2016-2017 Maarten Sneep, KNMI  #  # Redistribution and use in source and binary forms, with or without  # modification, are permitted provided that the following conditions are met:  #  # 1. Redistributions of source code must retain the above copyright notice,  # this list of conditions and the following disclaimer.  #  # 2. Redistributions in binary form must reproduce the above copyright notice,  # this list of conditions and the following disclaimer in the documentation  # and/or other materials provided with the distribution.  #  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"  # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE  # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE  # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR  # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES  # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON  # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  ## \file AnalysisAndPlot.py  # Base class for an analysis class. All 'actions' in PyCAMA are represented by a sub-class of the class defined in this file.  # @author Maarten Sneep  import math  import logging  from collections import OrderedDict  import numpy as np  from .utilities import *  ## Base class for an analysis class.  #  # All 'actions' in PyCAMA are represented by a subclass of the pycama.AnalysisAndPlot.AnalysisAndPlot class.  #  # This class (or rather its sub-classes) are responsible for extracting the summarizing  # data after the data and meta-data have been read by the Reader class.  # After extraction they write the data to the netCDF4 output file. This is the operational part of PyCAMA.  #  # Another responsibility is to be able to restore the object based on the data in the netCDF4 output file.  # This is for plotting in phase 2 of a PyCAMA run, the report generation.  #  class AnalysisAndPlot(object):   ## The Constructor   #   # @param reader_data a pycama.Reader.Reader object that has ingested data from S5P L2 files.   # @param filename a netCDF file with data for this plot type.   # @param time_index time index to read from filename   # @param kwargs Other arguments specific to the subclass, these are passed on to the setup() method.   #   # @Note Either reader_data or both filename and time_index must be supplied.   #   def __init__(self, reader_data=None, filename=None, time_index=None, exclude=None, **kwargs):   ## logging.Logger instance for messaging   self.logger = logging.getLogger('PyCAMA')   self.product = None   self.processing_mode = None   if reader_data is None:   if time_index is not None:   self.logger.info("Ingesting '%s' data for index %d", self.__class__.__name__.replace("Plot", "").lower(), time_index)   else:   self.logger.info("Ingesting '%s' data", self.__class__.__name__.replace("Plot", "").lower())     ## The list of variables (indexed by variable name)   self.variables = OrderedDict()   ## Basic metadata for the variables   self.variables_meta = OrderedDict()   ## Link to the pycama.Reader.Reader instance with the input data (or None, in case we are plotting).   self.input_variables = reader_data   if hasattr(self, 'setup'):   self.setup(**kwargs)   else:   self.logger.debug("The class %s does not have a setup() method", self.__class__.__name__)   self.success = False   if self.input_variables is not None:   ## Current time index   self.time_index_in_output = self.input_variables.time_index_in_output   self.add_raw_variables()   self.product = self.input_variables.product   self.processing_mode = self.input_variables.mode   else:   self.time_index_in_output = None   if filename is not None and time_index is not None:   self.success = self.ingest(filename, time_index, exclude=exclude)   self.product, self.processing_mode = read_product_and_mode_from_file(filename)   if not self.success:   self.logger.warning("Ingestion not successful for class %s", self.__class__.__name__)   ## Print a progress message   #   # @param pct The percentage of completion (for the current phase).   def progress(self, pct):   self.logger.progress("{0}:{1:5.1f}%".format(self.__class__.__name__.replace("Plot", "").lower(), float(pct)))   ## The name of the group in the output file where this class should write its extracted data.   #   # Subclasses should use this property to obtain a unique and consistent name for the groupd in which they write to the output.   @property   def storage_group_name(self):   return self.__class__.__name__.lower() + '_data'   ## Crude mode finder   #   # @param x the x-axis (bin centers) of the histogram   # @param h the histogram   def mode(self, x, h, bounds=None):   return x[np.argmax(h)]   ## Include variable in output?   #   # Determine if a variable should be included in the output.   #   # Part of this class and not of the pycama.Variable.Variable class because subclasses may want to override this method.   #   # @param variable a pycama.Variable.Variable instance.   #   def include_var(self, variable):   return variable.show   ## Add input variables when they should be plotted for this class.   def add_raw_variables(self):   for var in self.input_variables.variables.values():   if not self.include_var(var):   continue   self.add_variable(var)   ## A an input variable.   #   # @param var a pycama.Variable.Variable instance.   #   # This method copies metadata. Subclasses should reserve storage,   # and make sure the method in the superclass (i.e. this method) gets called.   #   def add_variable(self, var):   self.variables_meta[var.name] = dict(units=var.units,   title=var.title,   data_range=var.data_range,   color_scale=var.color_scale,   log_range=var.log_range)   ## Extract the required information from the input data.   #   # Expected to be overridden in subclasses.   def process(self):   raise NotImplementedError("Method must be overridden in a subclass.")   ## Read processed data from the input file, for specified time index.   #   # @param fname The pycama data file (netCDF4).   # @param time_index The time-index to read.   #   # Expected to be overridden in subclasses.   #   def ingest(self, fname, time_index, exclude=None):   raise NotImplementedError("Method must be overridden in a subclass.")   ## Write processed data to output netcdf file.   #   # @param fname File to write to   # @param mode Writing mode, defaults to append.   #   # Expected to be overridden in subclasses.   #   def dump(self, fname, mode='a'):   raise NotImplementedError("Method must be overridden in a subclass.")   ## Merge data into a combined dataset.   #   # @param other The object to be added to self.   #   # Expected to be overridden in subclasses.   #   def __iadd__(self, other):   raise NotImplementedError("Method must be overridden in a subclass.")   ## A list of variable names available for plotting   @property   def variable_names(self):   return list(self.variables.keys())   ## Make a plot of a specified variable   #   # @param varname The name of the variable to plot.   # @param figure The matplotlib.figure.Figure instance to plot to.   # @param ax The matplotlib.axes.Axes object to use. Default is None, in that case ax = matplotlib.pyplot.gca() shall be used.   # @param kwargs Other keyword parameters (sub-class dependent)   #   # Expected to be overridden in subclasses.   #   # The figure, and possibly the axes into which the result should   # be plotted must be passed in. Figure creation and closure must be   # done by the caller, the caller is also responsible for grouping (if required).   # If the axes are not supplied, the current graphics context is obtained by calling matplotlib.pyplot.gca().   #   # @return A boolean. When True the plotting was succesful, and the result   # can be included in the output. When False the plot was not succesful and the figure will be discarded.   def plot(self, varname, figure, ax=None, **kwargs):   raise NotImplementedError("Method must be overridden in a subclass.")   ## What is our plot like (for messages).   @property   def plot_type(self):   return self.__class__.__name__.lower().replace('plot', '')   ## Pretty-print single numbers   #   # The result is \f$v\f$ when no exponents are needed, or   # \f$v \times 10^{n}\f$ otherwise.   #   # @param v value   # @return A LaTeX formatted string   #   def number_pretty_printer(self, v, limits=None):   if v is None or not np.isfinite(float(v)):   return "-"   if limits is None:   limits = [-1, 3]   try:   l = math.log10(math.fabs(float(v)))   except ValueError:   return "{0:.1f}".format(float(v))   if limits[0] <= l <= limits[1]:   fmt = "{{0:.{0:d}f}}".format(int(limits[1]-l))   return fmt.format(float(v))   else:   w = "{0:.4e}".format(float(v)).split('e')   return r"{0:.3f}\times10^{{{1}}}".format(float(w[0]), int(w[1]))   ## Pretty-print numbers, \f$v \pm e\f$.   #   # The result is \f$v \pm e\f$ when no exponents are needed, or   # \f$(v \pm e) \times 10^{3}\f$ (or whatever the required common exponent is) otherwise.   #   # @param v value   # @param e precision   # @return A LaTeX formatted string   #   def number_pretty_printer_pair(self, v, e, limits=None):   if v is None or e is None or not np.isfinite(float(v)) or not np.isfinite(float(e)):   return "-"   if limits is None:   limits = [-1, 3]   try:   l = math.log10(math.fabs(float(v)))   except ValueError:   return r"{0:.1f}\pm{1:.2f}".format(float(v), float(e))   if limits[0] <= l <= limits[1]:   fmt = "{{0:.{0:d}f}}\pm{{1:.{0:d}f}}".format(int(limits[1]-l))   return fmt.format(float(v), float(e))   else:   scale_exp = int(math.floor(l) if l < 0 else math.ceil(l))   return r"({0:.3f}\pm{1:.3f})\times10^{{{2}}}".format(float(v)/10**scale_exp, float(e)/10**scale_exp, scale_exp)