From 04e60d69c4418e484647ff35dd8d71c0c48d6f43 Mon Sep 17 00:00:00 2001
From: "(no author)" <(no author)@8a9318a1-56ba-4d59-b755-99d26321be01>
Date: Mon, 21 Nov 2011 17:25:32 +0000
Subject: [PATCH] adding mako version dependancy to setup.py; added new stats
 provider and figure manager to remove those tasks from the model; used
 partial to simplify the a/b controls/methods in the view

git-svn-id: https://svn.ssec.wisc.edu/repos/glance/trunk@154 8a9318a1-56ba-4d59-b755-99d26321be01
---
 pyglance/glance/data.py              |  22 +++
 pyglance/glance/gui_controller.py    |  17 ++-
 pyglance/glance/gui_figuremanager.py | 158 +++++++++++++++++++
 pyglance/glance/gui_model.py         | 217 ++++++---------------------
 pyglance/glance/gui_statsprovider.py | 100 ++++++++++++
 pyglance/glance/gui_view.py          |  62 ++------
 pyglance/setup.py                    |   4 +-
 7 files changed, 351 insertions(+), 229 deletions(-)
 create mode 100644 pyglance/glance/gui_figuremanager.py
 create mode 100644 pyglance/glance/gui_statsprovider.py

diff --git a/pyglance/glance/data.py b/pyglance/glance/data.py
index b7c1793..b1c6e2e 100644
--- a/pyglance/glance/data.py
+++ b/pyglance/glance/data.py
@@ -293,6 +293,28 @@ class DiffInfoObject (object) :
                                                    mismatch_pt_mask, outside_epsilon_mask)
         
         return diff_data_object
+    
+    @staticmethod
+    def verifyDataCompatability (aDataObject, bDataObject, aName, bName) :
+        """
+        Confirm that the two data objects can minimally be compared.
+        
+        return None if they can be compared or a text message explaining why they cannot.
+        """
+        # check the minimum comparison requirments
+        message = None
+        
+        # if either object does not exist, they can not be compared
+        if (aDataObject is None) or (bDataObject is None) :
+            message = ("Requested data was not available or did not exist.")
+        # check to see if the two variables have the same shape of data
+        elif aDataObject.data.shape != bDataObject.data.shape :
+            message = (aName + ' / ' + bName + ' ' + 
+                       'could not be compared because the data for these variables does not match in shape ' +
+                       'between the two files (file A data shape: ' + str(aDataObject.data.shape) + '; file B data shape: '
+                       + str(bDataObject.data.shape) + ').')
+        
+        return message
 
 class FileInfo (object) :
     """
diff --git a/pyglance/glance/gui_controller.py b/pyglance/glance/gui_controller.py
index d123eef..1e9dda3 100644
--- a/pyglance/glance/gui_controller.py
+++ b/pyglance/glance/gui_controller.py
@@ -15,8 +15,10 @@ if os.path.isdir(PYQT4_HAX):
 
 from PyQt4 import QtGui
 
-import glance.gui_view  as gui_view
-import glance.gui_model as gui_model
+import glance.gui_view          as gui_view
+import glance.gui_model         as gui_model
+import glance.gui_statsprovider as gui_stats
+import glance.gui_figuremanager as gui_figs
 
 LOG = logging.getLogger(__name__)
 
@@ -33,6 +35,8 @@ class GlanceGUIController (object) :
     
     self.view  - the view object (see glance.gui_view)
     self.model - the model object (see glance.gui_model)
+    self.stats - the stats provider object (see glance.gui_statsprovider)
+    self.figs  - the figure manager object (see glance.gui_figuremanager)
     self.qtApp - an application object, used to start QT
     """
     
@@ -45,10 +49,15 @@ class GlanceGUIController (object) :
         self.qtApp = QtGui.QApplication(sys.argv)
         self.view  = gui_view.GlanceGUIView(version_string)
         self.model = gui_model.GlanceGUIModel()
+        self.stats = gui_stats.GlanceGUIStats(self.model)
+        self.figs  = gui_figs.GlanceGUIFigures(self.model)
         
         # set things up to talk to each other
         self.model.registerErrorHandler(self)
         self.model.registerDataListener(self.view)
+        self.stats.registerErrorHandler(self)
+        self.stats.registerStatsListener(self.view)
+        self.figs.registerErrorHandler(self)
         self.view.registerUserUpdateListener(self)
         
         # force the initial info load from the model
@@ -112,14 +121,14 @@ class GlanceGUIController (object) :
         the user has asked for stats information
         """
         
-        self.model.sendStatsInfo() # TODO, should a different object handle this?
+        self.stats.sendStatsInfo()
     
     def userRequestsPlot (self) :
         """
         the user has asked for a plot
         """
         
-        self.model.spawnPlotWithCurrentInfo()
+        self.figs.spawnPlot()
     
     ################# end of methods to handle user input reporting #################
     
diff --git a/pyglance/glance/gui_figuremanager.py b/pyglance/glance/gui_figuremanager.py
new file mode 100644
index 0000000..6c5501c
--- /dev/null
+++ b/pyglance/glance/gui_figuremanager.py
@@ -0,0 +1,158 @@
+#!/usr/bin/env python
+# encoding: utf-8
+"""
+This module manages creating figures for the Glance GUI.
+
+Created by evas Nov 2011.
+Copyright (c) 2011 University of Wisconsin SSEC. All rights reserved.
+"""
+
+# these first two lines must stay before the pylab import
+import matplotlib
+matplotlib.use('Qt4Agg') # use the Qt Anti-Grain Geometry rendering engine
+
+from pylab import *
+
+import matplotlib.cm     as cm
+import matplotlib.pyplot as plt
+import matplotlib.colors as colors
+
+import logging
+import numpy as np
+
+import glance.data      as dataobjects
+import glance.figures   as figures
+import glance.gui_model as model
+
+LOG = logging.getLogger(__name__)
+
+# the number of bins to use for histograms
+DEFAULT_NUM_BINS = 50
+
+class GlanceGUIFigures (object) :
+    """
+    This class handles creating figures for the glance gui.
+    
+    (in future it may manage them more actively)
+    
+    it includes:
+    
+    self.dataModel      - the GlanceGUIModel object that contains the main data
+                          model for the GUI
+    self.errorHandlers  - objects that want to be notified when there's a serious error
+    """
+    
+    def __init__ (self, dataModelToSave) :
+        """
+        create a figure manager, hanging on to the data model, for use in creating figures
+        """
+        
+        self.dataModel     = dataModelToSave
+        self.errorHandlers = [ ]
+    
+    def registerErrorHandler (self, objectToRegister) :
+        """
+        add the given object to our list of error handlers
+        """
+        
+        if objectToRegister not in self.errorHandlers :
+            self.errorHandlers.append(objectToRegister)
+    
+    def spawnPlot (self) :
+        """
+        create a matplotlib plot using the current model information
+        """
+        
+        imageType = self.dataModel.getImageType()
+        
+        LOG.info ("Preparing variable data for plotting...")
+        
+        # get Variable names
+        aVarName    = self.dataModel.getVariableName("A")
+        bVarName    = self.dataModel.getVariableName("B")
+        
+        # get Data objects
+        aDataObject = self.dataModel.getVariableData("A", aVarName)
+        bDataObject = self.dataModel.getVariableData("B", bVarName)
+        
+        # TODO, this ignores the fact that the "original" plots don't need two sets of data
+        message = dataobjects.DiffInfoObject.verifyDataCompatability (aDataObject, bDataObject, aVarName, bVarName)
+        
+        # if the data isn't valid, stop now
+        if message is not None :
+            for errorHandler in self.errorHandlers :
+                errorHandler.handleWarning(message)
+            # we can't make any images from this data, so just return
+            return
+        
+        # compare our data
+        diffData = dataobjects.DiffInfoObject(aDataObject, bDataObject, epsilonValue=self.dataModel.getEpsilon())
+        
+        # get units text for display
+        aUnitsText  = self.dataModel.getUnitsText("A", aVarName)
+        bUnitsText  = self.dataModel.getUnitsText("B", bVarName)
+        
+        LOG.info("Spawning plot window: " + imageType)
+        
+        plt.ion() # make sure interactive plotting is on
+        
+        # create the plot
+        
+        if   imageType == model.ORIGINAL_A :
+            
+            tempFigure = figures.create_simple_figure(aDataObject.data, aVarName + "\nin File A",
+                                                      invalidMask=aDataObject.masks.missing_mask, colorMap=cm.jet, units=aUnitsText)
+            
+        elif imageType == model.ORIGINAL_B :
+            
+            tempFigure = figures.create_simple_figure(bDataObject.data, bVarName + "\nin File B",
+                                                      invalidMask=bDataObject.masks.missing_mask, colorMap=cm.jet, units=bUnitsText)
+            
+        elif imageType == model.ABS_DIFF :
+            
+            tempFigure = figures.create_simple_figure(np.abs(diffData.diff_data_object.data), "Absolute value of difference\nin " + aVarName,
+                                                      invalidMask=~diffData.diff_data_object.masks.valid_mask, colorMap=cm.jet, units=aUnitsText)
+            
+        elif imageType == model.RAW_DIFF :
+            
+            tempFigure = figures.create_simple_figure(diffData.diff_data_object.data, "Value of (Data File B - Data File A)\nfor " + aVarName,
+                                                      invalidMask=~diffData.diff_data_object.masks.valid_mask, colorMap=cm.jet, units=aUnitsText)
+            
+        elif imageType == model.HISTOGRAM :
+            
+            rawDiffDataClean = diffData.diff_data_object.data[diffData.diff_data_object.masks.valid_mask]
+            tempFigure = figures.create_histogram(rawDiffDataClean, DEFAULT_NUM_BINS, "Difference in\n" + aVarName,
+                                                  "Value of (B - A) at each data point", "Number of points with a given difference", units=aUnitsText)
+            
+        elif imageType == model.MISMATCH :
+            
+            mismatchMask = diffData.diff_data_object.masks.mismatch_mask
+            tempFigure = figures.create_simple_figure(aDataObject.data, "Areas of mismatch data\nin " + aVarName,
+                                                      invalidMask=aDataObject.masks.missing_mask, tagData=mismatchMask,
+                                                      colorMap=figures.MEDIUM_GRAY_COLOR_MAP, units=aUnitsText)
+            
+        elif imageType == model.SCATTER :
+            
+            tempCleanMask     = aDataObject.masks.missing_mask | bDataObject.masks.missing_mask
+            aDataClean        = aDataObject.data[~tempCleanMask]
+            bDataClean        = bDataObject.data[~tempCleanMask]
+            cleanMismatchMask = diffData.diff_data_object.masks.mismatch_mask[~tempCleanMask]
+            figures.create_scatter_plot(aDataClean, bDataClean, "Value in File A vs Value in File B", 
+                                        "File A Value in " + aVarName,
+                                        "File B Value in " + bVarName,
+                                        badMask=cleanMismatchMask, epsilon=self.dataModel.getEpsilon(),
+                                        units_x=aUnitsText, units_y=bUnitsText)
+            
+        elif imageType == model.HEX_PLOT :
+            
+            tempCleanMask     = aDataObject.masks.missing_mask | bDataObject.masks.missing_mask
+            aDataClean        = aDataObject.data[~tempCleanMask]
+            bDataClean        = bDataObject.data[~tempCleanMask]
+            tempFigure = figures.create_hexbin_plot(aDataClean, bDataClean,
+                                                    "Value in File A vs Value in File B",
+                                                    "File A Value in " + aVarName,
+                                                    "File B Value in " + bVarName,
+                                                    epsilon=self.dataModel.getEpsilon(),
+                                                    units_x=aUnitsText, units_y=bUnitsText)
+        
+        plt.draw()
diff --git a/pyglance/glance/gui_model.py b/pyglance/glance/gui_model.py
index 5ed31b8..fb5b52b 100644
--- a/pyglance/glance/gui_model.py
+++ b/pyglance/glance/gui_model.py
@@ -7,27 +7,11 @@ Created by evas Oct 2011.
 Copyright (c) 2011 University of Wisconsin SSEC. All rights reserved.
 """
 
-# these first two lines must stay before the pylab import
-import matplotlib
-matplotlib.use('Qt4Agg') # use the Qt Anti-Grain Geometry rendering engine
-
-from pylab import *
-
-import matplotlib.cm     as cm
-import matplotlib.pyplot as plt
-import matplotlib.colors as colors
-
-from pkg_resources import resource_string, resource_filename
-from mako.template import Template
-from mako.lookup   import TemplateLookup
-
-import sys, os.path, logging
+import logging
 import numpy as np
 
-import glance.data    as dataobjects
-import glance.figures as figures
-import glance.io      as io
-import glance.stats   as stats
+import glance.data as dataobjects
+import glance.io   as io
 
 LOG = logging.getLogger(__name__)
 
@@ -62,9 +46,6 @@ IMAGE_TYPES = [ORIGINAL_A,
                HEX_PLOT
               ]
 
-# the number of bins to use for histograms
-DEFAULT_NUM_BINS = 50
-
 class _FileModelData (object) :
     """
     This object is meant to be used internally by the GUI model. The model is going to mess with the
@@ -331,168 +312,62 @@ class GlanceGUIModel (object) :
                 listener.updateEpsilon(self.epsilon)
                 listener.updateImageTypes(self.imageType)
     
-    def _verifyDataCompatability (self, aDataObject, bDataObject, aName, bName) :
+    def getVariableName (self, filePrefix) :
         """
-        Confirm that the two data objects can minimally be compared.
-        
-        return None if they can be compared or a text message explaining why they cannot.
+        get the name of the variable loaded for the given file prefix
+        or None if no variable is loaded
         """
+        toReturn = None
         
-        # check the minimum comparison requirments
-        message = None
-        # if either object does not exist, they can not be compared
-        if (aDataObject is None) or (bDataObject is None) :
-            message = ("Data for requested files was not available. " +
-                       "Please load or reload files and try again.")
-        # check to see if the two variables have the same shape of data
-        elif aDataObject.data.shape != bDataObject.data.shape :
-            message = (aName + ' / ' + bName + ' ' + 
-                       'could not be compared because the data for these variables does not match in shape ' +
-                       'between the two files (file A data shape: ' + str(aDataObject.data.shape) + '; file B data shape: '
-                       + str(bDataObject.data.shape) + ').')
+        if filePrefix in self.fileData.keys() :
+            toReturn = self.fileData[filePrefix].variable
         
-        return message
+        return toReturn
     
-    def sendStatsInfo (self) :
+    def getVariableData (self, filePrefix, variableName) :
         """
-        our data listeners should be sent statistics information for a comparison
-        of the currently selected variables (if possible)
+        get the data object for the variable of variableName associated with the file prefix
+        or None if that variable is not loaded
         
-        TODO, move this to some sort of report manager model object?
+        Note: this is not a copy, but the original object, so any manipulations
+        done to it will be reflected in the model
         """
+        toReturn = None
         
-        tempVarA = self.fileData["A"].variable
-        tempVarB = self.fileData["B"].variable
-        aData    = self.fileData["A"].var_data_cache[tempVarA] if tempVarA in self.fileData["A"].var_data_cache else None
-        bData    = self.fileData["B"].var_data_cache[tempVarB] if tempVarB in self.fileData["B"].var_data_cache else None
+        if (filePrefix in self.fileData) and (variableName in self.fileData[filePrefix].var_data_cache) :
+            toReturn = self.fileData[filePrefix].var_data_cache[variableName]
         
-        # check the minimum validity of our data
-        message = self._verifyDataCompatability (aData, bData, tempVarA, tempVarB)
-        
-        # if the data isn't valid, stop now
-        if message is not None :
-            for errorHandler in self.errorHandlers :
-                errorHandler.handleWarning(message)
-            # we can't make any stats from this data, so just return
-            return
-        
-        LOG.info ("Constructing statistics")
-        
-        tempAnalysis = stats.StatisticalAnalysis.withDataObjects(aData, bData, epsilon=self.epsilon)
-        tempInfo = {'variable_name':       tempVarA,
-                    'alternate_name_in_B': tempVarB}
-        kwargs = { 'runInfo':    tempInfo,
-                   'statGroups': tempAnalysis.dictionary_form(),
-                   }
-        
-        templateLookup = TemplateLookup(directories=[resource_filename(__name__, ".")])
-        guiTemplate    = Template(resource_string(__name__, "guistatsreport.txt"), lookup=templateLookup)
-        
-        renderedText = guiTemplate.render(**kwargs)
-        
-        # tell my listeners to show the stats data we've collected
-        for listener in self.dataListeners :
-                listener.displayStatsData(tempVarA, tempVarB, renderedText) # TODO, do we need a different set of data here?
+        return toReturn
     
-    def spawnPlotWithCurrentInfo (self) :
+    def getUnitsText (self, filePrefix, variableName) :
         """
-        create a matplotlib plot using the current model information
-        
-        TODO, move this into some sort of figure manager model object/module?
+        get the text describing the units of the variable if the variable exists and that
+        attribute exists, otherwise return None
         """
-        
-        LOG.debug ("Variable A cache entries: " + str(self.fileData["A"].var_data_cache.keys()))
-        LOG.debug ("Variable B cache entries: " + str(self.fileData["B"].var_data_cache.keys()))
-        
-        LOG.info ("Preparing variable data for plotting...")
-        
-        tempVarA = self.fileData["A"].variable
-        tempVarB = self.fileData["B"].variable
-        
-        # TODO, move to taking advantage of the whole data objects
-        aData = self.fileData["A"].var_data_cache[tempVarA] if tempVarA in self.fileData["A"].var_data_cache else None
-        bData = self.fileData["B"].var_data_cache[tempVarB] if tempVarB in self.fileData["B"].var_data_cache else None
-        
-        # TODO, this ignores the fact that the "original" plots don't need two sets of data
-        message = self._verifyDataCompatability (aData, bData, tempVarA, tempVarB)
-        
-        # if the data isn't valid, stop now
-        if message is not None :
-            for errorHandler in self.errorHandlers :
-                errorHandler.handleWarning(message)
-            # we can't make any images from this data, so just return
-            return
-        
-        # compare our data
-        diffData = dataobjects.DiffInfoObject(aData, bData, epsilonValue=self.epsilon)
-        
-        # pull the units information
-        aUnits = self.fileData["A"].file.file_object.get_attribute(self.fileData["A"].variable, io.UNITS_CONSTANT)
-        bUnits = self.fileData["B"].file.file_object.get_attribute(self.fileData["B"].variable, io.UNITS_CONSTANT)
-        
-        LOG.info("Spawning plot window: " + self.imageType)
-        
-        plt.ion() # make sure interactive plotting is on
-        
-        # create the plot
-        
-        if   self.imageType == ORIGINAL_A :
-            
-            tempFigure = figures.create_simple_figure(aData.data, self.fileData["A"].variable + "\nin File A",
-                                                      invalidMask=aData.masks.missing_mask, colorMap=cm.jet, units=aUnits)
-            
-        elif self.imageType == ORIGINAL_B :
-            
-            tempFigure = figures.create_simple_figure(bData.data, self.fileData["B"].variable + "\nin File B",
-                                                      invalidMask=bData.masks.missing_mask, colorMap=cm.jet, units=bUnits)
-            
-        elif self.imageType == ABS_DIFF :
-            
-            tempFigure = figures.create_simple_figure(np.abs(diffData.diff_data_object.data), "Absolute value of difference\nin " + self.fileData["A"].variable,
-                                                      invalidMask=~diffData.diff_data_object.masks.valid_mask, colorMap=cm.jet, units=aUnits)
-            
-        elif self.imageType == RAW_DIFF :
-            
-            tempFigure = figures.create_simple_figure(diffData.diff_data_object.data, "Value of (Data File B - Data File A)\nfor " + self.fileData["A"].variable,
-                                                      invalidMask=~diffData.diff_data_object.masks.valid_mask, colorMap=cm.jet, units=aUnits)
-            
-        elif self.imageType == HISTOGRAM :
-            
-            rawDiffDataClean = diffData.diff_data_object.data[diffData.diff_data_object.masks.valid_mask]
-            tempFigure = figures.create_histogram(rawDiffDataClean, DEFAULT_NUM_BINS, "Difference in\n" + self.fileData["A"].variable,
-                                                  "Value of (B - A) at each data point", "Number of points with a given difference", units=aUnits)
-            
-        elif self.imageType == MISMATCH :
-            
-            mismatchMask = diffData.diff_data_object.masks.mismatch_mask
-            tempFigure = figures.create_simple_figure(aData.data, "Areas of mismatch data\nin " + self.fileData["A"].variable,
-                                                      invalidMask=aData.masks.missing_mask, tagData=mismatchMask, colorMap=figures.MEDIUM_GRAY_COLOR_MAP, units=aUnits)
-            
-        elif self.imageType == SCATTER :
-            
-            tempCleanMask     = aData.masks.missing_mask | bData.masks.missing_mask
-            aDataClean        = aData.data[~tempCleanMask]
-            bDataClean        = bData.data[~tempCleanMask]
-            cleanMismatchMask = diffData.diff_data_object.masks.mismatch_mask[~tempCleanMask]
-            figures.create_scatter_plot(aDataClean, bDataClean, "Value in File A vs Value in File B", 
-                                        "File A Value in " + self.fileData["A"].variable,
-                                        "File B Value in " + self.fileData["B"].variable,
-                                        badMask=cleanMismatchMask, epsilon=self.epsilon,
-                                        units_x=aUnits, units_y=bUnits)
-            
-        elif self.imageType == HEX_PLOT :
-            
-            tempCleanMask     = aData.masks.missing_mask | bData.masks.missing_mask
-            aDataClean        = aData.data[~tempCleanMask]
-            bDataClean        = bData.data[~tempCleanMask]
-            tempFigure = figures.create_hexbin_plot(aDataClean, bDataClean,
-                                                    "Value in File A vs Value in File B",
-                                                    "File A Value in " + self.fileData["A"].variable,
-                                                    "File B Value in " + self.fileData["B"].variable,
-                                                    epsilon=self.epsilon,
-                                                    units_x=aUnits, units_y=bUnits)
-        
-        plt.draw()
+        return self.fileData[filePrefix].file.file_object.get_attribute(variableName, io.UNITS_CONSTANT)
+    
+    def getEpsilon (self) :
+        """
+        get the current value of epsilon
+        """
+        return self.epsilon
+    
+    def getImageType (self) :
+        """
+        get the text string describing the image type currently selected
+        
+        the return will correspond to one of the constants from this module:
+        
+            ORIGINAL_A,
+            ORIGINAL_B,
+            ABS_DIFF,
+            RAW_DIFF,
+            HISTOGRAM,
+            MISMATCH,
+            SCATTER,
+            HEX_PLOT
+        """
+        return self.imageType
     
     def registerDataListener (self, objectToRegister) :
         """
diff --git a/pyglance/glance/gui_statsprovider.py b/pyglance/glance/gui_statsprovider.py
new file mode 100644
index 0000000..f82b88b
--- /dev/null
+++ b/pyglance/glance/gui_statsprovider.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+# encoding: utf-8
+"""
+This module handles providing stats data to the rest of the Glance GUI.
+
+Created by evas Nov 2011.
+Copyright (c) 2011 University of Wisconsin SSEC. All rights reserved.
+"""
+
+from pkg_resources import resource_string, resource_filename
+from mako.template import Template
+from mako.lookup   import TemplateLookup
+
+import glance.stats as stats
+import glance.data  as dataobjects
+
+import logging
+
+LOG = logging.getLogger(__name__)
+
+class GlanceGUIStats (object) :
+    """
+    this class represents a model object that manages providing
+    statistics information to the Glance GUI
+    
+    it includes:
+    
+    self.dataModel      - the GlanceGUIModel object that contains the main data
+                          model for the GUI
+    self.statsListeners - objects that want to be notified when stats are calculated
+    self.errorHandlers  - objects that want to be notified when there's a serious error
+    """
+    
+    def __init__ (self, dataModelToSave) :
+        """
+        create the gui stats object, hanging onto the dataModel given
+        """
+        
+        self.dataModel      = dataModelToSave
+        self.statsListeners = [ ]
+        self.errorHandlers  = [ ]
+    
+    def registerStatsListener (self, objectToRegister) :
+        """
+        add the given object to our list of stats listeners
+        """
+        
+        if objectToRegister not in self.statsListeners :
+            self.statsListeners.append(objectToRegister)
+    
+    def registerErrorHandler (self, objectToRegister) :
+        """
+        add the given object to our list of error handlers
+        """
+        
+        if objectToRegister not in self.errorHandlers :
+            self.errorHandlers.append(objectToRegister)
+    
+    def sendStatsInfo (self) :
+        """
+        our data listeners should be sent statistics information for a comparison
+        of the currently selected variables (if possible)
+        """
+        
+        # get Variable names
+        aVarName    = self.dataModel.getVariableName("A")
+        bVarName    = self.dataModel.getVariableName("B")
+        
+        # get Data objects
+        aDataObject = self.dataModel.getVariableData("A", aVarName)
+        bDataObject = self.dataModel.getVariableData("B", bVarName)
+        
+        # check the minimum validity of our data
+        message = dataobjects.DiffInfoObject.verifyDataCompatability (aDataObject, bDataObject, aVarName, bVarName)
+        
+        # if the data isn't valid, stop now
+        if message is not None :
+            for errorHandler in self.errorHandlers :
+                errorHandler.handleWarning(message)
+            # we can't make any stats from this data, so just return
+            return
+        
+        LOG.info ("Constructing statistics")
+        
+        # do the statistical analysis and collect the data that will be needed to render it nicely
+        tempAnalysis = stats.StatisticalAnalysis.withDataObjects(aDataObject, bDataObject, epsilon=self.dataModel.getEpsilon())
+        tempInfo = { 'variable_name':       aVarName,
+                     'alternate_name_in_B': bVarName }
+        kwargs   = { 'runInfo':    tempInfo,
+                     'statGroups': tempAnalysis.dictionary_form() }
+        
+        # use a mako template to render an html verion of the stats for display
+        templateLookup = TemplateLookup(directories=[resource_filename(__name__, ".")])
+        guiTemplate    = Template(resource_string(__name__, "guistatsreport.txt"), lookup=templateLookup)
+        renderedText = guiTemplate.render(**kwargs)
+        
+        # tell my listeners to show the stats data we've collected
+        for listener in self.statsListeners :
+                listener.displayStatsData(aVarName, bVarName, renderedText)
+
diff --git a/pyglance/glance/gui_view.py b/pyglance/glance/gui_view.py
index 7db22f7..9d0dd2e 100644
--- a/pyglance/glance/gui_view.py
+++ b/pyglance/glance/gui_view.py
@@ -15,6 +15,8 @@ if os.path.isdir(PYQT4_HAX):
 
 from PyQt4 import QtGui, QtCore
 
+from functools import partial
+
 LOG = logging.getLogger(__name__)
 
 """
@@ -100,10 +102,7 @@ class GlanceGUIView (QtGui.QWidget) :
         # set some tooltip text
         loadButton.setToolTip("Load a file: glance can handle NetCDF, HDF4, HDF5, and AERI files")
         # connect the button to an action
-        if   file_prefix is "A" :
-            loadButton.clicked.connect(self.clickedALoad)
-        elif file_prefix is "B" :
-            loadButton.clicked.connect(self.clickedBLoad)
+        loadButton.clicked.connect(partial(self.selectFileToLoad, file_prefix=file_prefix))
         self.widgetInfo[file_prefix]['load'] = loadButton
         grid_layout.addWidget(loadButton, currentRow, 4)
         
@@ -113,10 +112,7 @@ class GlanceGUIView (QtGui.QWidget) :
         grid_layout.addWidget(QtGui.QLabel("variable name:"), currentRow, 1)
         variableSelection = QtGui.QComboBox()
         variableSelection.setDisabled(True)
-        if   file_prefix is "A" :
-            variableSelection.activated.connect(self.selectedAVariable)
-        elif file_prefix is "B" :
-            variableSelection.activated.connect(self.selectedBVariable)
+        variableSelection.activated.connect(partial(self.reportVariableSelected, file_prefix=file_prefix))
         self.widgetInfo[file_prefix]['variable'] = variableSelection
         grid_layout.addWidget(variableSelection, currentRow, 2, 1, 3)
         
@@ -149,10 +145,7 @@ class GlanceGUIView (QtGui.QWidget) :
         # set up a check box to override the fill value loaded from the file
         overrideFillButton = QtGui.QCheckBox("override fill value")
         overrideFillButton.setDisabled(True)
-        if   file_prefix is "A" :
-            overrideFillButton.stateChanged.connect(self.toggledAOverride)
-        elif file_prefix is "B" :
-            overrideFillButton.stateChanged.connect(self.toggledBOverride)
+        overrideFillButton.stateChanged.connect(partial(self.reportOverrideChange, file_prefix=file_prefix))
         self.widgetInfo[file_prefix]['override'] = overrideFillButton
         grid_layout.addWidget(overrideFillButton, currentRow, 1)
         
@@ -163,10 +156,7 @@ class GlanceGUIView (QtGui.QWidget) :
         tempValidator.setNotation(QtGui.QDoubleValidator.StandardNotation)
         fillValue.setValidator(tempValidator)
         fillValue.setDisabled(True)
-        if   file_prefix is "A" :
-            fillValue.editingFinished.connect(self.fillValueEditedA)
-        elif file_prefix is "B" :
-            fillValue.editingFinished.connect(self.fillValueEditedB)
+        fillValue.editingFinished.connect(partial(self.fillValueChanged, file_prefix=file_prefix))
         self.widgetInfo[file_prefix]['fillValue'] = fillValue
         grid_layout.addWidget(fillValue, currentRow+1, 2, 1, 3)
         
@@ -227,15 +217,7 @@ class GlanceGUIView (QtGui.QWidget) :
     
     ################# start methods related to user input #################
     
-    def clickedALoad (self) :
-        
-        self.selectFileToLoad ("A")
-    
-    def clickedBLoad (self) :
-        
-        self.selectFileToLoad ("B")
-    
-    def selectFileToLoad (self, file_prefix) :
+    def selectFileToLoad (self, file_prefix=None) :
         """
         when the load button is pressed, let the user pick a file to load
         """
@@ -252,15 +234,7 @@ class GlanceGUIView (QtGui.QWidget) :
         for listener in self.userUpdateListeners :
             listener.newFileSelected(file_prefix, tempFilePath)
     
-    def selectedAVariable (self) :
-        
-        self.reportVariableSelected("A")
-    
-    def selectedBVariable (self) :
-        
-        self.reportVariableSelected("B")
-    
-    def reportVariableSelected (self, file_prefix) :
+    def reportVariableSelected (self, file_prefix=None) :
         """
         when a variable is selected for one of the files, report it to any user update listeners
         """
@@ -271,15 +245,7 @@ class GlanceGUIView (QtGui.QWidget) :
         for listener in self.userUpdateListeners :
             listener.userSelectedVariable(file_prefix, selectionText)
     
-    def toggledAOverride (self) :
-        
-        self.reportOverrideChange("A")
-    
-    def toggledBOverride (self) :
-        
-        self.reportOverrideChange("B")
-    
-    def reportOverrideChange (self, file_prefix) :
+    def reportOverrideChange (self, file_prefix=None) :
         """
         when the user checks or unchecks one of the override checkboxes, report it to user update listeners
         """
@@ -295,15 +261,7 @@ class GlanceGUIView (QtGui.QWidget) :
         for listener in self.userUpdateListeners :
             listener.userChangedOverload(file_prefix, shouldDoOverride)
     
-    def fillValueEditedA (self) :
-        
-        self.fillValueChanged("A")
-    
-    def fillValueEditedB (self) :
-        
-        self.fillValueChanged("B")
-    
-    def fillValueChanged (self, file_prefix) :
+    def fillValueChanged (self, file_prefix=None) :
         """
         when the user edits a fill value, report it to user update listeners
         """
diff --git a/pyglance/setup.py b/pyglance/setup.py
index b372998..fa1c232 100644
--- a/pyglance/setup.py
+++ b/pyglance/setup.py
@@ -22,11 +22,11 @@ easy_install -d $HOME/Library/Python -vi http://larch.ssec.wisc.edu/eggs/repos g
 from setuptools import setup, find_packages
 
 setup( name="glance", 
-       version="0.2.7.01", 
+       version="0.2.7.02", 
        zip_safe = False,
        entry_points = { 'console_scripts': [ 'glance = glance.compare:main' ] },
        packages = ['glance'], #find_packages('.'),
-       install_requires=[ 'numpy', 'matplotlib' ],
+       install_requires=[ 'numpy', 'matplotlib', 'mako>=0.4.1' ],
        package_data = {'': ['*.txt', '*.gif']}
        )
 
-- 
GitLab