#!/usr/bin/env python # encoding: utf-8 """ Plotting figure generation functions. Created by evas Dec 2009. Copyright (c) 2009 University of Wisconsin SSEC. All rights reserved. """ import matplotlib.colors as colors import matplotlib.cm as colormapinfo import logging import random as random import numpy as np import glance.graphics as maps import glance.delta as delta import glance.figures as figures from glance.constants import * LOG = logging.getLogger(__name__) # a constant for the larger size dpi fullSizeDPI = 150 # 200 # a constant for the thumbnail size dpi thumbSizeDPI = 50 # ********************* Section of utility functions *********************** # a method to stop people from calling my fake abstract methods def _abstract( ) : raise NotImplementedError('Method must be implemented in subclass.') def _make_shared_range(aData, goodInAMask, bData, goodInBMask, shouldUseSharedRangeForOriginal=False) : """ If a shared range is desired, figure out what the shared range including all the data in both sets is and return it. If it is not desired, return None. """ # if we have no data in either set, don't try to make a shared range if (not np.any(goodInAMask)) or (not np.any(goodInBMask)) : return None # figure out the shared range for A and B's data, by default don't share a range sharedRange = None if shouldUseSharedRangeForOriginal : sharedRange = figures.make_range(aData, goodInAMask, 50, offset_to_range=figures.offsetToRange, data_b=bData, valid_b_mask=goodInBMask) return sharedRange def _get_extents_and_projections(lonLatDataDict, goodInAMask, goodInBMask=None, variableDisplayName=None) : """ Determine the appropriate extents for the given data and create the appropriate projections. Returns the bounds that include all the lon/lat data, the input projection and the output projection objects """ nameMessage = '' if variableDisplayName is not None: nameMessage = " (" + variableDisplayName + ")" # figure out our bounds aAxis = maps.get_extents(lonLatDataDict[A_FILE_KEY][LON_KEY], lonLatDataDict[A_FILE_KEY][LAT_KEY], lon_good_mask=goodInAMask, lat_good_mask=goodInAMask, ) fullAxis = aAxis bAxis = None if goodInBMask is not None : bAxis = maps.get_extents(lonLatDataDict[B_FILE_KEY][LON_KEY], lonLatDataDict[B_FILE_KEY][LAT_KEY], lon_good_mask=goodInBMask, lat_good_mask=goodInBMask, ) fullAxis = maps.get_extents(lonLatDataDict[A_FILE_KEY][LON_KEY], lonLatDataDict[A_FILE_KEY][LAT_KEY], lon_good_mask=goodInAMask, lat_good_mask=goodInAMask, longitude_data_b=lonLatDataDict[B_FILE_KEY][LON_KEY], latitude_data_b=lonLatDataDict[B_FILE_KEY][LAT_KEY], lon_good_mask_b=goodInBMask, lat_good_mask_b=goodInBMask, ) else : LOG.debug("No file b valid mask provided, using visible axes boundaries from file a.") LOG.debug("Visible axes for file A variable data" + nameMessage + " are: " + str(aAxis)) if goodInBMask is not None : LOG.debug("Visible axes for file B variable data" + nameMessage + " are: " + str(bAxis)) LOG.debug("Visible axes shared for both file's variable data" + nameMessage + " are: " + str(fullAxis)) if (fullAxis[0] is None) or (fullAxis[1] is None) or (fullAxis[2] is None) or (fullAxis[3] is None) : LOG.warn("Unable to display figures for variable" + nameMessage + " because of inability to identify" + " usable bounding longitude and latitude range on the earth. Bounding range that was identified:" + str(fullAxis)) return None, None, None # we really shouldn't get to this point, but if we do, we don't have a usable set of bounds # figure out our projections LOG.info('\t\tcreating projection objects') in_proj, out_proj = maps.create_cartopy_proj_objects(fullAxis,) return fullAxis, in_proj, out_proj # ********************* Section of public classes *********************** class PlottingFunctionFactory : """ This class is intended to be an abstract parent class for our plotting function creation classes. It contains a fake "abstract" method. Please don't ever instantiate this class. """ def __init__(self) : #LOG.error("Someone has instantiated PlottingFunctionFactory. This class is NOT meant to be instantiated.") # note: somewhere in the transition to python 3 the internals started calling this method for child classes too pass def create_plotting_functions ( self, # the most basic data set needed aData, bData, # these should be data objects from glance.data variableDisplayName, epsilon, doPlotSettingsDict, # where the names of the created figures will be stored original_fig_list, compared_fig_list, # parameters that are only needed for geolocated data lonLatDataDict=None, # only used if we are plotting a contour dataRanges=None, dataRangeNames=None, dataColors=None, shouldUseSharedRangeForOriginal=True, # a comparison of the data if the data comparison info is needed # this should be a DiffInfoObject from glance.data differences=None, # only used for plotting quiver data aUData=None, aVData=None, bUData=None, bVData=None, # only used for line plots binIndex=None, tupleIndex=None, binName=None, tupleName=None, # the optional epsilon for comparison of a percent of A epsilonPercent=None, # the optional units for display units_a=None, units_b=None, # an optional range for a histogram histRange=None ) : pass class BasicComparisonPlotsFunctionFactory (PlottingFunctionFactory) : """ This class creates the most basic of comparison plots based on two similarly sized data sets. (Plots created include histogram and scatter plots.) """ #def __init__(self): # LOG.debug("Creating BasicComparisonPlotsFunctionFactory object.") def create_plotting_functions ( self, # the most basic data set needed aData, bData, variableDisplayName, epsilon, doPlotSettingsDict, # where the names of the created figures will be stored original_fig_list, compared_fig_list, # parameters that are only needed for geolocated data lonLatDataDict=None, # only used if we are plotting a contour dataRanges=None, dataRangeNames=None, dataColors=None, shouldUseSharedRangeForOriginal=True, # a comparison of the data if the data comparison info is needed # this should be a DiffInfoObject from glance.data differences=None, # only used for plotting quiver data aUData=None, aVData=None, bUData=None, bVData=None, # only used for line plots binIndex=None, tupleIndex=None, binName=None, tupleName=None, # the optional epsilon for comparison of a percent of A epsilonPercent=None, # the optional units for display units_a=None, units_b=None, # an optional range for a histogram histRange=None ) : #TODO, for the moment, unpack these values into local variables; FUTURE use the objects directly and as needed #goodInAMask = differences.a_data_object.masks.valid_mask #goodInBMask = differences.b_data_object.masks.valid_mask rawDiffData = differences.diff_data_object.data #absDiffData = np.abs(rawDiffData) # we also want to show the distance between our two, not just which one's bigger/smaller goodInBothMask = differences.diff_data_object.masks.valid_mask #mismatchMask = differences.diff_data_object.masks.mismatch_mask outsideEpsilonMask = differences.diff_data_object.masks.outside_epsilon_mask aData = aData.data bData = bData.data functionsToReturn = { } # make the histogram plot if (DO_PLOT_HISTOGRAM_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_HISTOGRAM_KEY]) : assert(goodInBothMask.shape == rawDiffData.shape) # setup the data bins for the histogram numBinsToUse = 50 valuesForHist = rawDiffData[goodInBothMask] functionsToReturn[HIST_FUNCTION_KEY] = ((lambda : figures.create_histogram(valuesForHist, numBinsToUse, "Difference in\n" + variableDisplayName, 'Value of (Data File B - Data File A) at a Data Point', 'Number of Data Points with a Given Difference', True, units=units_a, rangeList=histRange)), "histogram of the amount of difference in " + variableDisplayName, "Hist.png", compared_fig_list) # make the scatter plot if (DO_PLOT_SCATTER_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_SCATTER_KEY]) : assert(aData.shape == bData.shape) assert(bData.shape == goodInBothMask.shape) assert(goodInBothMask.shape == outsideEpsilonMask.shape) # TODO, if there's an epsilon percent, how should the epsilon lines be drawn? good_a_data = aData[goodInBothMask] good_b_data = bData[goodInBothMask] if good_a_data.size <= figures.MAX_SCATTER_PLOT_DATA : # make a basic scatter plot functionsToReturn[SCATTER_FUNCTION_KEY] = ((lambda : figures.create_scatter_plot(good_a_data, good_b_data, "Value in File A vs Value in File B", "File A Value", "File B Value", outsideEpsilonMask[goodInBothMask], epsilon, units_x=units_a, units_y=units_b)), "scatter plot of file a values vs file b values for " + variableDisplayName, "Scatter.png", compared_fig_list) else : LOG.warn("Too much data to allow creation of scatter plot for " + variableDisplayName + ".") # make a density scatter plot as well functionsToReturn[DENSITY_SCATTER_FN_KEY] = ((lambda : figures.create_density_scatter_plot(good_a_data, good_b_data, "Density of Value in File A vs Value in File B", "File A Value", "File B Value", epsilon=epsilon, units_x=units_a, units_y=units_b)), "density scatter plot of file a values vs file b values for " + variableDisplayName, "DensityScatter.png", compared_fig_list) # make a hexplot, which is like a scatter plot with density if (DO_PLOT_HEX_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_HEX_KEY]) : assert(aData.shape == bData.shape) assert(bData.shape == goodInBothMask.shape) if np.sum(goodInBothMask) <= figures.MAX_HEX_PLOT_DATA : functionsToReturn[HEX_PLOT_FUNCTION_KEY] = ((lambda : figures.create_hexbin_plot(aData[goodInBothMask], bData[goodInBothMask], "Value in File A vs Value in File B", "File A Value", "File B Value", epsilon, units_x=units_a, units_y=units_b)), "density of file a values vs file b values for " + variableDisplayName, "Hex.png", compared_fig_list) else : LOG.warn("Too much data to allow creation of hex plot for " + variableDisplayName + ".") return functionsToReturn class MappedContourPlotFunctionFactory (PlottingFunctionFactory) : """ This class creates contour plots mapped onto a region of the earth. """ def __init__(self): LOG.debug("Creating MappedContourPlotFunctionFactory object.") def create_plotting_functions ( self, # the most basic data set needed aData, bData, variableDisplayName, epsilon, doPlotSettingsDict, # where the names of the created figures will be stored original_fig_list, compared_fig_list, # parameters that are only needed for geolocated data lonLatDataDict=None, # only used if we are plotting a contour dataRanges=None, dataRangeNames=None, dataColors=None, shouldUseSharedRangeForOriginal=True, # a comparison of the data if the data comparison info is needed # this should be a DiffInfoObject from glance.data differences=None, # only used for plotting quiver data aUData=None, aVData=None, bUData=None, bVData=None, # only used for line plots binIndex=None, tupleIndex=None, binName=None, tupleName=None, # the optional epsilon for comparison of a percent of A epsilonPercent=None, # the optional units for display units_a=None, units_b=None, # an optional range for a histogram histRange=None ) : #TODO, for the moment, unpack these values into local variables; FUTURE use the objects directly and as needed goodInAMask = differences.a_data_object.masks.valid_mask goodInBMask = differences.b_data_object.masks.valid_mask rawDiffData = differences.diff_data_object.data absDiffData = np.abs(rawDiffData) # we also want to show the distance between our two, not just which one's bigger/smaller goodInBothMask = differences.diff_data_object.masks.valid_mask mismatchMask = differences.diff_data_object.masks.mismatch_mask #outsideEpsilonMask = differences.diff_data_object.masks.outside_epsilon_mask aData = aData.data bData = bData.data # the default for plotting geolocated data mappedPlottingFunction = figures.create_mapped_figure functionsToReturn = { } assert(lonLatDataDict is not None) assert(goodInAMask is not None) assert(goodInBMask is not None) # figure out where we're going to be plotting and using which projections fullAxis, in_proj, out_proj = _get_extents_and_projections(lonLatDataDict, goodInAMask, goodInBMask, variableDisplayName) # get a shared range if we need one sharedRange = _make_shared_range(aData, goodInAMask, bData, goodInBMask, shouldUseSharedRangeForOriginal) # make the plotting functions # make the original data plots if (DO_PLOT_ORIGINALS_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_ORIGINALS_KEY]) : assert(A_FILE_KEY in lonLatDataDict) assert(LAT_KEY in lonLatDataDict[A_FILE_KEY]) assert(LON_KEY in lonLatDataDict[A_FILE_KEY]) assert(lonLatDataDict[A_FILE_KEY][LAT_KEY].shape == lonLatDataDict[A_FILE_KEY][LON_KEY].shape) functionsToReturn[ORIG_A_FUNCTION_KEY] = ((lambda : mappedPlottingFunction(aData, lonLatDataDict[A_FILE_KEY][LAT_KEY], lonLatDataDict[A_FILE_KEY][LON_KEY], in_proj, out_proj, fullAxis, (variableDisplayName + "\nin File A"), invalidMask=(~goodInAMask), dataRanges=dataRanges or sharedRange, dataRangeNames=dataRangeNames, dataRangeColors=dataColors, units=units_a)), variableDisplayName + " in file a", "A.png", original_fig_list) assert(B_FILE_KEY in lonLatDataDict) assert(LAT_KEY in lonLatDataDict[B_FILE_KEY]) assert(LON_KEY in lonLatDataDict[B_FILE_KEY]) assert(lonLatDataDict[B_FILE_KEY][LAT_KEY].shape == lonLatDataDict[B_FILE_KEY][LON_KEY].shape) functionsToReturn[ORIG_B_FUNCTION_KEY] = ((lambda : mappedPlottingFunction(bData, lonLatDataDict[B_FILE_KEY][LAT_KEY], lonLatDataDict[B_FILE_KEY][LON_KEY], in_proj, out_proj, fullAxis, (variableDisplayName + "\nin File B"), invalidMask=(~goodInBMask), dataRanges=dataRanges or sharedRange, dataRangeNames=dataRangeNames, dataRangeColors=dataColors, units=units_b)), variableDisplayName + " in file b", "B.png", original_fig_list) # make the absolute value difference plot if (DO_PLOT_ABS_DIFF_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_ABS_DIFF_KEY]) : assert(absDiffData.shape == goodInBothMask.shape) assert(COMMON_KEY in lonLatDataDict) assert(LAT_KEY in lonLatDataDict[COMMON_KEY]) assert(LON_KEY in lonLatDataDict[COMMON_KEY]) assert(lonLatDataDict[COMMON_KEY][LAT_KEY].shape == lonLatDataDict[COMMON_KEY][LON_KEY].shape) assert(lonLatDataDict[COMMON_KEY][LON_KEY].shape == absDiffData.shape) functionsToReturn[ABS_DIFF_FUNCTION_KEY] = ((lambda : mappedPlottingFunction(absDiffData, lonLatDataDict[COMMON_KEY][LAT_KEY], lonLatDataDict[COMMON_KEY][LON_KEY], in_proj, out_proj, fullAxis, ("Absolute value of difference in\n" + variableDisplayName), invalidMask=(~goodInBothMask), units=units_a)), "absolute value of difference in " + variableDisplayName, "AbsDiff.png", compared_fig_list) # make the subtractive difference plot if (DO_PLOT_SUB_DIFF_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_SUB_DIFF_KEY]) : assert(rawDiffData.shape == goodInBothMask.shape) assert(COMMON_KEY in lonLatDataDict) assert(LAT_KEY in lonLatDataDict[COMMON_KEY]) assert(LON_KEY in lonLatDataDict[COMMON_KEY]) assert(lonLatDataDict[COMMON_KEY][LAT_KEY].shape == lonLatDataDict[COMMON_KEY][LON_KEY].shape) assert(lonLatDataDict[COMMON_KEY][LON_KEY].shape == rawDiffData.shape) functionsToReturn[SUB_DIFF_FUNCTION_KEY] = ((lambda : mappedPlottingFunction(rawDiffData, lonLatDataDict[COMMON_KEY][LAT_KEY], lonLatDataDict[COMMON_KEY][LON_KEY], in_proj, out_proj, fullAxis, ("Value of (Data File B - Data File A) for\n" + variableDisplayName), invalidMask=(~goodInBothMask), units=units_a)), "the difference in " + variableDisplayName, "Diff.png", compared_fig_list) # make the mismatch data plot if (DO_PLOT_MISMATCH_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_MISMATCH_KEY]) : assert(aData.shape == bData.shape) assert(goodInAMask.shape == goodInBMask.shape) assert(COMMON_KEY in lonLatDataDict) assert(LAT_KEY in lonLatDataDict[COMMON_KEY]) assert(LON_KEY in lonLatDataDict[COMMON_KEY]) assert(lonLatDataDict[COMMON_KEY][LAT_KEY].shape == lonLatDataDict[COMMON_KEY][LON_KEY].shape) assert(lonLatDataDict[COMMON_KEY][LON_KEY].shape == aData.shape) # this is not an optimal solution, but we need to have at least somewhat valid data at any mismatched points so # that our plot won't be totally destroyed by missing or non-finite data from B bDataCopy = np.array(bData) tempMask = goodInAMask & (~goodInBMask) bDataCopy[tempMask] = aData[tempMask] functionsToReturn[MISMATCH_FUNCTION_KEY] = ((lambda : mappedPlottingFunction(bDataCopy, lonLatDataDict[COMMON_KEY][LAT_KEY], lonLatDataDict[COMMON_KEY][LON_KEY], in_proj, out_proj, fullAxis, ("Areas of mismatch data in\n" + variableDisplayName), invalidMask=(~(goodInAMask | goodInBMask)), colorMap=figures.MEDIUM_GRAY_COLOR_MAP, tagData=mismatchMask, dataRanges=dataRanges, dataRangeNames=dataRangeNames, units=units_a)), # FUTURE, combined units? "mismatch data in " + variableDisplayName, "Mismatch.png", compared_fig_list) return functionsToReturn # TODO, this class has not been fully tested class MappedQuiverPlotFunctionFactory (PlottingFunctionFactory) : """ This class creates quiver plots mapped onto a region of the earth. Note: the plotting function requires u and v data for both data sets, but the size of the two data sets is not required to be the same. If the size of the two data sets is the same, additional comparison plots will be created. """ def __init__(self): LOG.debug("Creating MappedQuiverPlotFunctionFactory object.") def create_plotting_functions ( self, # the most basic data set needed aData, bData, variableDisplayName, epsilon, doPlotSettingsDict, # where the names of the created figures will be stored original_fig_list, compared_fig_list, # parameters that are only needed for geolocated data lonLatDataDict=None, # only used if we are plotting a contour dataRanges=None, dataRangeNames=None, dataColors=None, shouldUseSharedRangeForOriginal=True, # a comparison of the data if the data comparison info is needed # this should be a DiffInfoObject from glance.data differences=None, # only used for plotting quiver data aUData=None, aVData=None, bUData=None, bVData=None, # only used for line plots binIndex=None, tupleIndex=None, binName=None, tupleName=None, # the optional epsilon for comparison of a percent of A epsilonPercent=None, # the optional units for display units_a=None, units_b=None, # an optional range for a histogram histRange=None ) : #FUTURE, for the moment, unpack these values into local variables; FUTURE use the objects directly and as needed goodInAMask = differences.a_data_object.masks.valid_mask goodInBMask = differences.b_data_object.masks.valid_mask rawDiffData = differences.diff_data_object.data absDiffData = np.abs(rawDiffData) # we also want to show the distance between our two, not just which one's bigger/smaller goodInBothMask = differences.diff_data_object.masks.valid_mask mismatchMask = differences.diff_data_object.masks.mismatch_mask #outsideEpsilonMask = differences.diff_data_object.masks.outside_epsilon_mask aData = aData.data bData = bData.data # the default for plotting geolocated data mappedPlottingFunction = figures.create_quiver_mapped_figure functionsToReturn = { } assert(aUData is not None) assert(aVData is not None) assert(bUData is not None) assert(bVData is not None) assert(lonLatDataDict is not None) assert(goodInAMask is not None) assert(goodInBMask is not None) # figure out where we're going to be plotting and using which projections fullAxis, in_proj, out_proj = _get_extents_and_projections(lonLatDataDict, goodInAMask, goodInBMask, variableDisplayName) # make the plotting functions # make the original data plots if (DO_PLOT_ORIGINALS_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_ORIGINALS_KEY]) : assert(A_FILE_KEY in lonLatDataDict) assert(LAT_KEY in lonLatDataDict[A_FILE_KEY]) assert(LON_KEY in lonLatDataDict[A_FILE_KEY]) assert(lonLatDataDict[A_FILE_KEY][LAT_KEY].shape == lonLatDataDict[A_FILE_KEY][LON_KEY].shape) functionsToReturn[ORIG_A_FUNCTION_KEY] = \ ((lambda : mappedPlottingFunction(aData, lonLatDataDict[A_FILE_KEY][LAT_KEY], lonLatDataDict[A_FILE_KEY][LON_KEY], in_proj, out_proj, fullAxis, (variableDisplayName + "\nin File A"), invalidMask=(~goodInAMask), uData=aUData, vData=aVData, units=units_a)), variableDisplayName + " in file a", "A.png", original_fig_list) assert(B_FILE_KEY in lonLatDataDict) assert(LAT_KEY in lonLatDataDict[B_FILE_KEY]) assert(LON_KEY in lonLatDataDict[B_FILE_KEY]) assert(lonLatDataDict[B_FILE_KEY][LAT_KEY].shape == lonLatDataDict[B_FILE_KEY][LON_KEY].shape) functionsToReturn[ORIG_B_FUNCTION_KEY] = \ ((lambda : mappedPlottingFunction(bData, lonLatDataDict[B_FILE_KEY][LAT_KEY], lonLatDataDict[B_FILE_KEY][LON_KEY], in_proj, out_proj, fullAxis, (variableDisplayName + "\nin File B"), invalidMask=(~ goodInBMask), uData=bUData, vData=bVData, units=units_b)), variableDisplayName + " in file b", "B.png", original_fig_list) # FUTURE, any additional figures of the original data? if aUData.shape == bUData.shape : LOG.info("creating comparison quiver plots for " + variableDisplayName) # TODO, is there any complication in taking the diff of vectors this way? diffUData = aUData - bUData diffVData = aVData - bVData # make the absolute value difference plot if (DO_PLOT_ABS_DIFF_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_ABS_DIFF_KEY]) : assert(absDiffData.shape == goodInBothMask.shape) assert(COMMON_KEY in lonLatDataDict) assert(LAT_KEY in lonLatDataDict[COMMON_KEY]) assert(LON_KEY in lonLatDataDict[COMMON_KEY]) assert(lonLatDataDict[COMMON_KEY][LAT_KEY].shape == lonLatDataDict[COMMON_KEY][LON_KEY].shape) assert(lonLatDataDict[COMMON_KEY][LON_KEY].shape == absDiffData.shape) functionsToReturn[ABS_DIFF_FUNCTION_KEY] = \ ((lambda : mappedPlottingFunction(absDiffData, lonLatDataDict[COMMON_KEY][LAT_KEY], lonLatDataDict[COMMON_KEY][LON_KEY], in_proj, out_proj, fullAxis, ("Absolute value of difference in\n" + variableDisplayName), invalidMask=(~ goodInBothMask), uData=diffUData, vData=diffVData, units=units_a)), "absolute value of difference in " + variableDisplayName, "AbsDiff.png", compared_fig_list) # make the subtractive difference plot if (SUB_DIFF_FUNCTION_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[SUB_DIFF_FUNCTION_KEY]) : assert(rawDiffData.shape == goodInBothMask.shape) assert(COMMON_KEY in lonLatDataDict) assert(LAT_KEY in lonLatDataDict[COMMON_KEY]) assert(LON_KEY in lonLatDataDict[COMMON_KEY]) assert(lonLatDataDict[COMMON_KEY][LAT_KEY].shape == lonLatDataDict[COMMON_KEY][LON_KEY].shape) assert(lonLatDataDict[COMMON_KEY][LON_KEY].shape == rawDiffData.shape) functionsToReturn[SUB_DIFF_FUNCTION_KEY] = \ ((lambda : mappedPlottingFunction(rawDiffData, lonLatDataDict[COMMON_KEY][LAT_KEY], lonLatDataDict[COMMON_KEY][LON_KEY], in_proj, out_proj, fullAxis, ("Value of (Data File B - Data File A) for\n" + variableDisplayName), invalidMask=(~ goodInBothMask), uData=diffUData, vData=diffVData, units=units_a)), "the difference in " + variableDisplayName, "Diff.png", compared_fig_list) # make the mismatch data plot if (DO_PLOT_MISMATCH_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_MISMATCH_KEY]) : assert(aData.shape == bData.shape) assert(goodInAMask.shape == goodInBMask.shape) assert(COMMON_KEY in lonLatDataDict) assert(LAT_KEY in lonLatDataDict[COMMON_KEY]) assert(LON_KEY in lonLatDataDict[COMMON_KEY]) assert(lonLatDataDict[COMMON_KEY][LAT_KEY].shape == lonLatDataDict[COMMON_KEY][LON_KEY].shape) assert(lonLatDataDict[COMMON_KEY][LON_KEY].shape == aData.shape) # this is not an optimal solution, but we need to have at least somewhat valid data at any mismatched points so # that our plot won't be totally destroyed by missing or non-finite data from B bDataCopy = np.array(bData) tempMask = goodInAMask & (~goodInBMask) bDataCopy[tempMask] = aData[tempMask] functionsToReturn[MISMATCH_FUNCTION_KEY] = \ ((lambda : mappedPlottingFunction(bDataCopy, lonLatDataDict[COMMON_KEY][LAT_KEY], lonLatDataDict[COMMON_KEY][LON_KEY], in_proj, out_proj, fullAxis, ("Areas of mismatch data in\n" + variableDisplayName), invalidMask=(~(goodInAMask | goodInBMask)), colorMap=figures.MEDIUM_GRAY_COLOR_MAP, tagData=mismatchMask, dataRanges=dataRanges, dataRangeNames=dataRangeNames, # TODO, does this need modification? uData=bUData, vData=bVData, units=units_a)), "mismatch data in " + variableDisplayName, "Mismatch.png", compared_fig_list) return functionsToReturn class LinePlotsFunctionFactory (PlottingFunctionFactory) : """ This class creates simple line plots based on simple one dimensional data. """ def __init__(self): LOG.debug("Creating LinePlotsFunctionFactory object.") def create_plotting_functions ( self, # the most basic data set needed aData, bData, variableDisplayName, epsilon, doPlotSettingsDict, # where the names of the created figures will be stored original_fig_list, compared_fig_list, # parameters that are only needed for geolocated data lonLatDataDict=None, # only used if we are plotting a contour dataRanges=None, dataRangeNames=None, dataColors=None, shouldUseSharedRangeForOriginal=True, # a comparison of the data if the data comparison info is needed # this should be a DiffInfoObject from glance.data differences=None, # only used for plotting quiver data aUData=None, aVData=None, bUData=None, bVData=None, # only used for line plots binIndex=None, tupleIndex=None, binName=None, tupleName=None, # the optional epsilon for comparison of a percent of A epsilonPercent=None, # the optional units for display units_a=None, units_b=None, # an optional range for a histogram histRange=None ) : """ This method generates line plotting functions for one dimensional data and returns them in a dictionary of tupples, where each tupple is in the form: returnDictionary['descriptive name'] = (function, title, file_name, list_this_figure_should_go_into) The file name is only the name of the file, not the full path. """ #TODO, for the moment, unpack these values into local variables; FUTURE use the objects directly and as needed goodInAMask = differences.a_data_object.masks.valid_mask goodInBMask = differences.b_data_object.masks.valid_mask rawDiffData = differences.diff_data_object.data absDiffData = np.abs(rawDiffData) # we also want to show the distance between our two, not just which one's bigger/smaller goodInBothMask = differences.diff_data_object.masks.valid_mask mismatchMask = differences.diff_data_object.masks.mismatch_mask #outsideEpsilonMask = differences.diff_data_object.masks.outside_epsilon_mask aData = aData.data bData = bData.data assert(aData is not None) assert(bData is not None) assert(len(aData.shape) == 1) assert(aData.shape == bData.shape) # make all our data sets for plotting ahead of time for simplicity aList = [(aData, ~goodInAMask, 'r', 'A data', None, units_a)] bList = [(bData, ~goodInBMask, 'b', 'B data', None, units_b)] absDiffList = [(absDiffData, ~goodInBothMask, '', 'abs. diff. data', None, units_a)] # todo, should this be a units? subDiffList = [(rawDiffData, ~goodInBothMask, '', 'sub. diff. data', None, units_a)] # todo, should this be a units? mismatchList = [(aData, ~goodInAMask, 'r', 'A data', mismatchMask, units_a), (bData, ~goodInBMask, 'b', 'B data', mismatchMask, units_b)] functionsToReturn = { } # make the original data plots if (DO_PLOT_ORIGINALS_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_ORIGINALS_KEY]) : functionsToReturn[ORIG_FUNCTION_KEY] = ((lambda: figures.create_line_plot_figure((aList + bList), variableDisplayName + "\nin Both Files")), variableDisplayName + " in both files", "AB.png", original_fig_list) functionsToReturn[ORIG_A_FUNCTION_KEY] = ((lambda: figures.create_line_plot_figure(aList, variableDisplayName + "\nin File A")), variableDisplayName + " in file a", "A.png", original_fig_list) functionsToReturn[ORIG_B_FUNCTION_KEY] = ((lambda: figures.create_line_plot_figure(bList, variableDisplayName + "\nin File B")), variableDisplayName + " in file b", "B.png", original_fig_list) # make the absolute value difference plot if (DO_PLOT_ABS_DIFF_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_ABS_DIFF_KEY]) : functionsToReturn[ABS_DIFF_FUNCTION_KEY] = ((lambda: figures.create_line_plot_figure(absDiffList, "Absolute value of difference in\n" + variableDisplayName)), "absolute value of difference in " + variableDisplayName, "AbsDiff.png", compared_fig_list) # make the subtractive difference plot if (DO_PLOT_SUB_DIFF_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_SUB_DIFF_KEY]) : functionsToReturn[SUB_DIFF_FUNCTION_KEY] = ((lambda: figures.create_line_plot_figure(subDiffList, "Value of (Data File B - Data File A) for\n" + variableDisplayName)), "the difference in " + variableDisplayName, "Diff.png", compared_fig_list) # make the mismatch data plot if (DO_PLOT_MISMATCH_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_MISMATCH_KEY]) : functionsToReturn[MISMATCH_FUNCTION_KEY] = ((lambda: figures.create_line_plot_figure(mismatchList, "Areas of mismatch data in\n" + variableDisplayName)), "mismatch data in " + variableDisplayName, "Mismatch.png", compared_fig_list) return functionsToReturn class BinTupleAnalysisFunctionFactory (PlottingFunctionFactory) : """ This class handles complex sampling of data when you have a much higher volume of multi-dimensional data to search through. """ def __init__(self): LOG.debug("Creating BinTupleAnalysisFunctionFactory object.") def create_plotting_functions ( self, # the most basic data set needed aData, bData, variableDisplayName, epsilon, doPlotSettingsDict, # where the names of the created figures will be stored original_fig_list, compared_fig_list, # parameters that are only needed for geolocated data lonLatDataDict=None, # only used if we are plotting a contour dataRanges=None, dataRangeNames=None, dataColors=None, shouldUseSharedRangeForOriginal=True, # a comparison of the data if the data comparison info is needed # this should be a DiffInfoObject from glance.data differences=None, # only used for plotting quiver data aUData=None, aVData=None, bUData=None, bVData=None, # only used for line plots binIndex=None, tupleIndex=None, binName=None, tupleName=None, # the optional epsilon for comparison of a percent of A epsilonPercent=None, # the optional units for display units_a=None, units_b=None, # an optional range for a histogram histRange=None ) : """ This method generates histogram and sample line plot functions for complex three dimensional data and returns them in a dictionary of tuples, where each tuple is in the form: returnDictionary['descriptive name'] = (function, title, file_name, list_this_figure_should_go_into) The file name is only the name of the file, not the full path. """ #TODO, for the moment, unpack these values into local variables; FUTURE use the objects directly and as needed goodInAMask = differences.a_data_object.masks.valid_mask goodInBMask = differences.b_data_object.masks.valid_mask rawDiffData = differences.diff_data_object.data #absDiffData = np.abs(rawDiffData) # we also want to show the distance between our two, not just which one's bigger/smaller goodInBothMask = differences.diff_data_object.masks.valid_mask #mismatchMask = differences.diff_data_object.masks.mismatch_mask #outsideEpsilonMask = differences.diff_data_object.masks.outside_epsilon_mask aData = aData.data bData = bData.data # confirm that our a and b data are minimally ok assert(aData is not None) assert(bData is not None) assert(len(aData.shape) >= 2) assert(aData.shape == bData.shape) # confirm that our bin and tuple indexes are valid assert(binIndex is not None) assert(tupleIndex is not None) assert(binIndex != tupleIndex) assert(binIndex < len(aData.shape)) assert(tupleIndex < len(aData.shape)) # reorder and reshape our data into the [bin][case][tuple] form reorderMapObject = delta.BinTupleMapping(aData.shape, binIndexNumber=binIndex, tupleIndexNumber=tupleIndex) aData = reorderMapObject.reorder_for_bin_tuple(aData) bData = reorderMapObject.reorder_for_bin_tuple(bData) goodInAMask = reorderMapObject.reorder_for_bin_tuple(goodInAMask) goodInBMask = reorderMapObject.reorder_for_bin_tuple(goodInBMask) #absDiffData = reorderMapObject.reorder_for_bin_tuple(absDiffData) rawDiffData = reorderMapObject.reorder_for_bin_tuple(rawDiffData) goodInBothMask = reorderMapObject.reorder_for_bin_tuple(goodInBothMask) #mismatchMask = reorderMapObject.reorder_for_bin_tuple(mismatchMask) #outsideEpsilonMask = reorderMapObject.reorder_for_bin_tuple(outsideEpsilonMask) # our list of functions that will later create the plots functionsToReturn = { } if aData.size <= 0 : return functionsToReturn # create the scatter plot with colors for each section scatterPlotList = [ ] tempColorMap = colormapinfo.get_cmap('jet', rawDiffData.shape[0]) for binNum in range(rawDiffData.shape[0]) : tempColor = tempColorMap(binNum) if len(tempColor) > 3 : tempColor = tempColor[:3] scatterPlotList.append(((aData[binNum][goodInBothMask[binNum]]).ravel(), (bData[binNum][goodInBothMask[binNum]]).ravel(), None, colors.rgb2hex(tempColor), None, 'bin ' + str(binNum + 1), None)) functionsToReturn[MULTI_SCATTER_FUNCTION_KEY] = \ ((lambda : figures.create_complex_scatter_plot(scatterPlotList, "Value in File A vs Value in File B, Colored by Bin", "File A Value", "File B Value", epsilon, units_x=units_a, units_y=units_b)), "scatter plot of file a values vs file b values for " + variableDisplayName + " by bin", "MultiScatter.png", compared_fig_list) # for each of the bins, make the rms histogram data numHistogramSections = 7 # FUTURE at some point make this a user controlled setting for binNum in range(rawDiffData.shape[0]) : new_list = [ ] compared_fig_list.append(new_list) # figure out all the rms diff values for the various cases rmsDiffValues = np.zeros(rawDiffData.shape[1]) for caseNumber in range(rawDiffData.shape[1]) : rmsDiffValues[caseNumber] = delta.calculate_root_mean_square(rawDiffData[binNum][caseNumber], goodInBothMask[binNum][caseNumber]) # make the basic histogram for this bin number dataForHistogram = rmsDiffValues[np.isfinite(rmsDiffValues)] # remove any invalid data "nan" values if (DO_PLOT_HISTOGRAM_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_HISTOGRAM_KEY]) : def make_histogram(binNumber=binNum, data=dataForHistogram) : return figures.create_histogram(data, numHistogramSections, "RMS Diff. in " + variableDisplayName + "\nfor " + binName + " # " + str(binNumber + 1), 'RMS Difference across ' + tupleName + ' dimension', 'Number of Cases with a Given RMS Diff.', True, units=units_a, rangeList=histRange) functionsToReturn[str(binNum + 1) + HIST_FUNCTION_KEY] = (make_histogram, "histogram of rms differences in " + variableDisplayName, str(binNum + 1) + "Hist.png", new_list) # we will need to be able to mask out the non-finite data tempFiniteMap = np.isfinite(rmsDiffValues) # figure out the min/max rms diff values minRMSDiff = np.min(rmsDiffValues[tempFiniteMap]) maxRMSDiff = np.max(rmsDiffValues[tempFiniteMap]) # sort the cases by their rms diff values counts = np.zeros(numHistogramSections) histogramSections = { } histogramSectionLimits = np.linspace(minRMSDiff, maxRMSDiff, numHistogramSections + 1) histogramSectionLimits[0] = histogramSectionLimits[0] - 0.00000001 for caseNumber in range(rmsDiffValues.size) : # check each of the sections to see which one it falls in for limitIndex in range(histogramSectionLimits.size - 1) : # if it falls in this section, add it's case number index to the list for this section if ( (rmsDiffValues[caseNumber] > histogramSectionLimits[limitIndex]) and (rmsDiffValues[caseNumber] <= histogramSectionLimits[limitIndex + 1]) ) : if limitIndex not in histogramSections : histogramSections[limitIndex] = [ ] histogramSections[limitIndex].append(caseNumber) # select example cases for the histogram random.seed('test') # TODO, seed with something else? for section in sorted(histogramSections) : listOfCases = histogramSections[section] caseNumber = listOfCases[random.randint(0, len(listOfCases) - 1)] # make lineplot functions for the example cases caseIndexes = reorderMapObject.determine_case_indecies(caseNumber) caseNumText = '' for caseIndex in caseIndexes : caseNumText += str(caseIndex) dataList = [(aData[binNum][caseNumber], ~goodInAMask[binNum][caseNumber], 'r', 'A case', None, units_a), (bData[binNum][caseNumber], ~goodInBMask[binNum][caseNumber], 'b', 'B case', None, units_b)] def make_lineplot(data=dataList, binNumber=binNum, caseNumberText=caseNumText): return figures.create_line_plot_figure(data, variableDisplayName + " in both files" + "\n" + "for " + binName + " # " + str(binNumber + 1) + " and case # " + caseNumberText) dataDiff = aData[binNum][caseNumber] - bData[binNum][caseNumber] maskDiff = ~goodInAMask[binNum][caseNumber] | ~goodInBMask[binNum][caseNumber] def make_diffplot(data=dataDiff, badMask=maskDiff, binNumber=binNum, caseNumberText=caseNumText, units=units_a): return figures.create_line_plot_figure([(data, badMask, 'm', 'A - B', None, units)], "Value of " + variableDisplayName + " in File A - the value in File B\n" + "for " + binName + " # " + str(binNumber + 1) + " and case # " + caseNumberText) functionsToReturn[str(binNum + 1) + 'sample' + str(section + 1) + '.AB.png'] = (make_lineplot, variableDisplayName + " sample in both files", str(binNum + 1) + 'sample' + str(section + 1) + '.AB.png', new_list) functionsToReturn[str(binNum + 1) + 'sample' + str(section + 1) + 'ABdiff.png] '] = (make_diffplot, variableDisplayName + " difference between files", str(binNum + 1) + 'sample' + str(section + 1) + '.ABdiff.png', new_list) return functionsToReturn class IMShowPlotFunctionFactory (PlottingFunctionFactory) : """ This class creates simple imshow plots of 2D data """ def __init__(self): LOG.debug("Creating IMShowPlotFunctionFactory object.") def create_plotting_functions ( self, # the most basic data set needed aData, bData, variableDisplayName, epsilon, doPlotSettingsDict, # where the names of the created figures will be stored original_fig_list, compared_fig_list, # parameters that are only needed for geolocated data lonLatDataDict=None, # only used if we are plotting a contour dataRanges=None, dataRangeNames=None, dataColors=None, shouldUseSharedRangeForOriginal=True, # a comparison of the data if the data comparison info is needed # this should be a DiffInfoObject from glance.data differences=None, # only used for plotting quiver data aUData=None, aVData=None, bUData=None, bVData=None, # only used for line plots binIndex=None, tupleIndex=None, binName=None, tupleName=None, # the optional epsilon for comparison of a percent of A epsilonPercent=None, # the optional units for display units_a=None, units_b=None, # an optional range for a histogram histRange=None ) : """ This method generates imshow plotting functions for two dimensional data and returns them in a dictionary of tupples, where each tupple is in the form: returnDictionary['descriptive name'] = (function, title, file_name, list_this_figure_should_go_into) The file name is only the name of the file, not the full path. """ #TODO, for the moment, unpack these values into local variables; FUTURE use the objects directly and as needed goodInAMask = differences.a_data_object.masks.valid_mask goodInBMask = differences.b_data_object.masks.valid_mask rawDiffData = differences.diff_data_object.data absDiffData = np.abs(rawDiffData) # we also want to show the distance between our two, not just which one's bigger/smaller goodInBothMask = differences.diff_data_object.masks.valid_mask mismatchMask = differences.diff_data_object.masks.mismatch_mask #outsideEpsilonMask = differences.diff_data_object.masks.outside_epsilon_mask aData = aData.data bData = bData.data assert(aData is not None) assert(bData is not None) functionsToReturn = { } sharedRange = _make_shared_range(aData, goodInAMask, bData, goodInBMask, shouldUseSharedRangeForOriginal) # make the original data plots if (DO_PLOT_ORIGINALS_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_ORIGINALS_KEY]) : assert(goodInAMask is not None) assert(aData.shape == goodInAMask.shape) functionsToReturn[ORIG_A_FUNCTION_KEY] = \ ((lambda: figures.create_simple_figure(aData, variableDisplayName + "\nin File A", invalidMask=~goodInAMask, colorbarLimits=sharedRange, units=units_a)), variableDisplayName + " in file a", "A.png", original_fig_list) assert(goodInBMask is not None) assert(bData.shape == goodInBMask.shape) functionsToReturn[ORIG_B_FUNCTION_KEY] = \ ((lambda: figures.create_simple_figure(bData, variableDisplayName + "\nin File B", invalidMask=~goodInBMask, colorbarLimits=sharedRange, units=units_b)), variableDisplayName + " in file b", "B.png", original_fig_list) # make the absolute value difference plot if (DO_PLOT_ABS_DIFF_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_ABS_DIFF_KEY]) : assert(absDiffData is not None) assert(goodInBothMask is not None) assert(goodInBothMask.shape == absDiffData.shape) functionsToReturn[ABS_DIFF_FUNCTION_KEY] = \ ((lambda: figures.create_simple_figure(absDiffData, "Absolute value of difference in\n" + variableDisplayName, invalidMask=~goodInBothMask, units=units_a)), "absolute value of difference in " + variableDisplayName, "AbsDiff.png", compared_fig_list) # make the subtractive difference plot if (DO_PLOT_SUB_DIFF_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_SUB_DIFF_KEY]) : assert(rawDiffData is not None) assert(goodInBothMask is not None) assert(goodInBothMask.shape == rawDiffData.shape) functionsToReturn[SUB_DIFF_FUNCTION_KEY] = \ ((lambda: figures.create_simple_figure(rawDiffData, "Value of (Data File B - Data File A) for\n" + variableDisplayName, invalidMask=~goodInBothMask, units=units_a)), "the difference in " + variableDisplayName, "Diff.png", compared_fig_list) # make the mismatch data plot if (DO_PLOT_MISMATCH_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_MISMATCH_KEY]) : assert(goodInAMask is not None) assert(goodInBMask is not None) assert(goodInAMask.shape == goodInBMask.shape) assert(aData.shape == bData.shape) assert(aData.shape == goodInAMask.shape) assert(mismatchMask is not None) assert(mismatchMask.shape == aData.shape) # this is not an optimal solution, but we need to have at least somewhat valid data at any mismatched points so # that our plot won't be totally destroyed by missing or non-finite data from B bDataCopy = np.array(bData) tempMask = goodInAMask & (~goodInBMask) bDataCopy[tempMask] = aData[tempMask] functionsToReturn[MISMATCH_FUNCTION_KEY] = ((lambda: figures.create_simple_figure(bDataCopy, "Areas of mismatch data in\n" + variableDisplayName, invalidMask=~(goodInAMask | goodInBMask), tagData=mismatchMask, colorMap=figures.MEDIUM_GRAY_COLOR_MAP, units=units_a)), "mismatch data in " + variableDisplayName, "Mismatch.png", compared_fig_list) return functionsToReturn # ********** below here are the inspection plotting functions ********** # note: for all of these the epsilons are meaningless, for some the bData has optional effects # that won't come up when they're used for inspection reports class DataHistogramPlotFunctionFactory (PlottingFunctionFactory) : """ This class creates the most basic of histogram plots based on one data set. """ def __init__(self): LOG.debug("Creating DataHistogramPlotFunctionFactory object.") def create_plotting_functions ( self, # the most basic data set needed aData, bData, # Note, bData is not used variableDisplayName, epsilon, doPlotSettingsDict, # where the names of the created figures will be stored original_fig_list, compared_fig_list, # parameters that are only needed for geolocated data lonLatDataDict=None, # only used if we are plotting a contour dataRanges=None, dataRangeNames=None, dataColors=None, shouldUseSharedRangeForOriginal=True, # no comparison is needed for this call, should be None differences=None, # only used for plotting quiver data aUData=None, aVData=None, bUData=None, bVData=None, # only used for line plots binIndex=None, tupleIndex=None, binName=None, tupleName=None, # the optional epsilon for comparison of a percent of A epsilonPercent=None, # the optional units for display units_a=None, units_b=None, # an optional range for a histogram histRange=None ) : functionsToReturn = { } # right now this function is not intended to handle both types of data; in the FUTURE this may change assert (aData is not None) assert (bData is None) # make the histogram plot; TODO, for now reuse this setting; in FUTURE, should a new one be made to control this? if (DO_PLOT_HISTOGRAM_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_HISTOGRAM_KEY]) : goodInAMask = aData.masks.valid_mask assert(goodInAMask.shape == aData.data.shape) # setup the data bins for the histogram numBinsToUse = 50 valuesForHist = aData.data[goodInAMask] functionsToReturn[HIST_FUNCTION_KEY] = \ ((lambda : figures.create_histogram(valuesForHist, numBinsToUse, "Values of\n" + variableDisplayName, 'Value of Data Point', 'Number of Data Points with a Given Value', True, units=units_a, rangeList=histRange)), "histogram of the values in " + variableDisplayName, "HistA.png", original_fig_list) return functionsToReturn class InspectIMShowPlotFunctionFactory (PlottingFunctionFactory) : """ This class creates a simple imshow plot of 2D data """ def __init__(self): LOG.debug("Creating InspectIMShowPlotFunctionFactory object.") def create_plotting_functions ( self, # the most basic data set needed aData, bData, # bData is not expected and will not be used variableDisplayName, epsilon, doPlotSettingsDict, # where the names of the created figures will be stored original_fig_list, compared_fig_list, # parameters that are only needed for geolocated data lonLatDataDict=None, # only used if we are plotting a contour dataRanges=None, dataRangeNames=None, dataColors=None, shouldUseSharedRangeForOriginal=True, # no comparison is needed for this call, should be None differences=None, # only used for plotting quiver data aUData=None, aVData=None, bUData=None, bVData=None, # only used for line plots binIndex=None, tupleIndex=None, binName=None, tupleName=None, # the optional epsilon for comparison of a percent of A epsilonPercent=None, # the optional units for display units_a=None, units_b=None, # an optional range for a histogram histRange=None ) : """ This method generates imshow plotting functions for two dimensional data and returns them in a dictionary of tupples, where each tupple is in the form: returnDictionary['descriptive name'] = (function, title, file_name, list_this_figure_should_go_into) The file name is only the name of the file, not the full path. """ assert(aData is not None) assert(aData.data is not None) assert(bData is None) functionsToReturn = { } # make the original data plots if (DO_PLOT_ORIGINALS_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_ORIGINALS_KEY]) : goodInAMask = aData.masks.valid_mask assert(goodInAMask is not None) assert(aData.data.shape == goodInAMask.shape) functionsToReturn[ORIG_A_FUNCTION_KEY] = ((lambda: figures.create_simple_figure(aData.data, variableDisplayName + "\nin File", invalidMask=~goodInAMask, units=units_a)), variableDisplayName + " in file", "origA.png", original_fig_list) return functionsToReturn class InspectLinePlotsFunctionFactory (PlottingFunctionFactory) : """ This class creates simple line plots based on simple one dimentional data. """ def __init__(self): LOG.debug("Creating InspectLinePlotsFunctionFactory object.") def create_plotting_functions ( self, # the most basic data set needed aData, bData, # bData will not be used variableDisplayName, epsilon, doPlotSettingsDict, # where the names of the created figures will be stored original_fig_list, compared_fig_list, # parameters that are only needed for geolocated data lonLatDataDict=None, # only used if we are plotting a contour dataRanges=None, dataRangeNames=None, dataColors=None, shouldUseSharedRangeForOriginal=True, # no comparison is needed for this call, should be None differences=None, # only used for plotting quiver data aUData=None, aVData=None, bUData=None, bVData=None, # only used for line plots binIndex=None, tupleIndex=None, binName=None, tupleName=None, # the optional epsilon for comparison of a percent of A epsilonPercent=None, # the optional units for display units_a=None, units_b=None, # an optional range for a histogram histRange=None ) : """ This method generates line plotting functions for one dimensional data and returns them in a dictionary of tupples, where each tupple is in the form: returnDictionary['descriptive name'] = (function, title, file_name, list_this_figure_should_go_into) The file name is only the name of the file, not the full path. """ assert(aData is not None) assert(aData.data is not None) assert(bData is None) #assert(len(aData.data.shape) is 1) functionsToReturn = { } # make the original data plots if (DO_PLOT_ORIGINALS_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_ORIGINALS_KEY]) : aDataTemp = aData.data aMaskTemp = ~aData.masks.valid_mask if len(aDataTemp.shape) > 1 : aDataTemp = aDataTemp.ravel() aMaskTemp = aMaskTemp.ravel() aList = [(aDataTemp, aMaskTemp, 'b', 'A data', None, units_a)] functionsToReturn[ORIG_A_FUNCTION_KEY] = ((lambda: figures.create_line_plot_figure(aList, variableDisplayName + "\nin File")), variableDisplayName + " in file", "lineA.png", original_fig_list) return functionsToReturn class InspectMappedContourPlotFunctionFactory (PlottingFunctionFactory) : """ This class creates contour plots mapped onto a region of the earth. """ def __init__(self): LOG.debug("Creating InspectMappedContourPlotFunctionFactory object.") def create_plotting_functions ( self, # the most basic data set needed aData, bData, # bData will not be used variableDisplayName, epsilon, doPlotSettingsDict, # where the names of the created figures will be stored original_fig_list, compared_fig_list, # parameters that are only needed for geolocated data lonLatDataDict=None, # only used if we are plotting a contour dataRanges=None, dataRangeNames=None, dataColors=None, shouldUseSharedRangeForOriginal=True, # this isn't needed for this method, should be None differences=None, # only used for plotting quiver data aUData=None, aVData=None, bUData=None, bVData=None, # only used for line plots binIndex=None, tupleIndex=None, binName=None, tupleName=None, # the optional epsilon for comparison of a percent of A epsilonPercent=None, # the optional units for display units_a=None, units_b=None, # an optional range for a histogram histRange=None ) : # for simplicity, get the valid mask for a goodInAMask = aData.masks.valid_mask # the default for plotting geolocated data mappedPlottingFunction = figures.create_mapped_figure functionsToReturn = { } assert(lonLatDataDict is not None) assert(goodInAMask is not None) #print ("lon lat dictionary form: " + str(lonLatDataDict)) # figure out where we're going to be plotting and using which projections fullAxis, in_proj, out_proj = _get_extents_and_projections({A_FILE_KEY:lonLatDataDict}, goodInAMask, None, # there is no b mask variableDisplayName) # make the original data plot if (DO_PLOT_ORIGINALS_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_ORIGINALS_KEY]) : assert(LAT_KEY in lonLatDataDict) assert(LON_KEY in lonLatDataDict) assert(lonLatDataDict[LAT_KEY].shape == lonLatDataDict[LON_KEY].shape) functionsToReturn[ORIG_A_FUNCTION_KEY] = ((lambda : mappedPlottingFunction(aData.data, lonLatDataDict[LAT_KEY], lonLatDataDict[LON_KEY], in_proj, out_proj, fullAxis, (variableDisplayName + "\nin File"), invalidMask=(~goodInAMask), dataRanges=dataRanges, dataRangeNames=dataRangeNames, dataRangeColors=dataColors, units=units_a)), variableDisplayName + " in file", "mapA.png", original_fig_list) return functionsToReturn class InspectMappedQuiverPlotFunctionFactory(PlottingFunctionFactory): """ This class creates quiver plots for a single file mapped onto a region of the earth. Note: the plotting function requires u and v data. """ def __init__(self): LOG.debug("Creating InspectMappedQuiverPlotFunctionFactory object.") def create_plotting_functions( self, # the most basic data set needed aData, bData, # bData will not be used variableDisplayName, epsilon, doPlotSettingsDict, # where the names of the created figures will be stored original_fig_list, compared_fig_list, # parameters that are only needed for geolocated data lonLatDataDict=None, # not used in this method (for contour plots) dataRanges=None, dataRangeNames=None, dataColors=None, shouldUseSharedRangeForOriginal=True, # not used in this method (for comparison plots) differences=None, # only used for plotting quiver data aUData=None, aVData=None, bUData=None, bVData=None, # b versions will not be used # not used in this method (only used for line plots) binIndex=None, tupleIndex=None, binName=None, tupleName=None, # not used in this method (the optional epsilon for comparison of a percent of A) epsilonPercent=None, # the optional units for display units_a=None, units_b=None, # units_b will not be used # not used in this method (an optional range for a histogram) histRange=None ): # FUTURE, for the moment, unpack these values into local variables; FUTURE use the objects directly and as needed goodInAMask = aData.masks.valid_mask aData = aData.data # the default for plotting geolocated data mappedPlottingFunction = figures.create_quiver_mapped_figure functionsToReturn = {} assert (aUData is not None) assert (aVData is not None) assert (lonLatDataDict is not None) assert (goodInAMask is not None) # figure out where we're going to be plotting and using which projections temp_lonlat = { A_FILE_KEY: lonLatDataDict, } fullAxis, in_proj, out_proj = _get_extents_and_projections(temp_lonlat, goodInAMask, None, variableDisplayName) # make the plotting functions # make the original data plots if (DO_PLOT_ORIGINALS_KEY not in doPlotSettingsDict) or (doPlotSettingsDict[DO_PLOT_ORIGINALS_KEY]): assert (LAT_KEY in lonLatDataDict) assert (LON_KEY in lonLatDataDict) assert (lonLatDataDict[LAT_KEY].shape == lonLatDataDict[LON_KEY].shape) functionsToReturn[ORIG_A_FUNCTION_KEY] = \ ((lambda: mappedPlottingFunction(aData, lonLatDataDict[LAT_KEY], lonLatDataDict[LON_KEY], in_proj, out_proj, fullAxis, (variableDisplayName + "\nin File A"), invalidMask=(~goodInAMask), uData=aUData, vData=aVData, units=units_a)), variableDisplayName + " in file a", "A.png", original_fig_list) # FUTURE, any additional figures of the original data? return functionsToReturn if __name__=='__main__': import doctest doctest.testmod()