-
Eva Schiffer authoredEva Schiffer authored
gui_view.py 50.92 KiB
#!/usr/bin/env python
# encoding: utf-8
"""
The view portion of the glance GUI code.
Created by evas Oct 2011.
Copyright (c) 2011 University of Wisconsin SSEC. All rights reserved.
"""
import sys, os.path, logging
from PyQt4 import QtGui, QtCore
from functools import partial
from glance.gui_constants import A_CONST, B_CONST
LOG = logging.getLogger(__name__)
"""
The GUI view is pretty simple.
All it needs to do is send messages when data is changed and
accept UI updates (of both values and lock state) from the model and controller.
The GUI's most complex responsibility is keeping the user from entering
garbage data into the UI.
"""
class _DoubleOrNoneValidator (QtGui.QDoubleValidator) :
"""
This validator accepts doubles like it's parent class or None.
"""
def __init__(self, parent=None) :
super(self.__class__, self).__init__(parent)
def validate (self, value, pos ) :
"""
override our parent's validate method
"""
if (value == "") or (value is None) or (value == "None") :
return (QtGui.QValidator.Acceptable, pos)
if (value == "N") or (value == "No") or (value == "Non") :
return (QtGui.QValidator.Intermediate, pos)
return super(self.__class__, self).validate(value, pos)
def fixup (self, value) :
"""
correct the input if needed
"""
ok = False
if (value == "N") or (value == "No") or (value == "Non") :
value = QtCore.QString("None")
ok = True
if (value == "") or (value == None) :
value = QtCore.QString("")
ok = True
value = QtCore.QString(value)
if not ok :
super(self.__class__, self).fixup(value)
class GlanceGUIView (QtGui.QWidget) :
"""
The main view object that will create the gui that's visible to the user.
It includes:
self.userUpdateListeners - objects that want to be notified when the user
changes data in the gui
self.lastFilePath - the last path that was loaded on this program run,
starts as './'
self.epsilonWidget - the widget that handles the input of epsilon
self.imageSelectionDropDown - the drop down that lets the user select the type
of image to be created
self.displayButton - the button that lets the user create a plot
self.widgetInfo - a dictionary of the widgets that need to be
classifed by file; these are indexed in the form
self.widgetInfo[file_prefix][widget_name] and
include the following widget names:
'path' - the file path text display for that file
'load' - the load button for that file
'variable' - the variable drop down selector for that file
'dims' - the label to display dimensions for that file
'attrs' - the table to display attributes for that file
'override' - the override check box for that file
'fillValue' - the fill value for that file
TODO - at some point all of these should move to these constants should
move to the constants module
"""
def __init__ (self, versionString, parent=None) :
"""
build the various Qt controls that make up this application
"""
QtGui.QWidget.__init__(self,parent)
# set our title with the version string
self.setWindowTitle(versionString)
# to make Dave happy, give it an icon
from pkg_resources import resource_filename
self.setWindowIcon(QtGui.QIcon(resource_filename(__name__, "pass.gif")))
# a place to hang onto our file specific widgets
self.widgetInfo = { }
# setup the rest of our window
# TODO, this section is temporary, set up the tabs in a sub function?
self.tabWidget = QtGui.QTabWidget()
# add the main tab that allows for basic control
tempWidget = QtGui.QWidget()
tempWidget.setLayout(self._build_data_tab())
self.tabWidget.addTab(tempWidget, "basic")
# add a tab that allows more detailed, optional settings
tempWidget = QtGui.QWidget()
tempWidget.setLayout(self._build_settings_tab())
self.tabWidget.addTab(tempWidget, "settings")
# add a tab that allows more complex filtering options
tempWidget = QtGui.QWidget()
tempWidget.setLayout(self._build_filters_tab())
self.tabWidget.addTab(tempWidget, "filters")
tempLayout = QtGui.QGridLayout()
tempLayout.addWidget(self.tabWidget)
self.setLayout(tempLayout)
self.setGeometry(600, 600, 625, 700)
# this will represent those who want to be notified
# when the user changes things
self.userUpdateListeners = [ ]
# we will use this to remember were the user wanted to load files from last
# TODO, can we remember this between program runs? something like preferences?
self.lastFilePath = './'
# hang on to stats windows so they don't vanish
self.statsWindows = { }
self.statsCounter = 1
# hang on to data display windows
self.dataShowWindows = { }
self.dataShowCounter = 1
def _build_data_tab (self) :
"""
built the qt controls for the basic data tab and lay them out in a grid layout
"""
# create the layout and set up some of the overall record keeping
layoutToUse = QtGui.QGridLayout()
currentRow = 0
# set up the file info for the A file
currentRow = self._add_file_related_controls(A_CONST, layoutToUse, currentRow)
# set up the file info for the B file
currentRow = self._add_file_related_controls(B_CONST, layoutToUse, currentRow)
# set up the epsilon input box
layoutToUse.addWidget(QtGui.QLabel("epsilon:"), currentRow, 0)
self.epsilonWidget = QtGui.QLineEdit()
self.epsilonWidget.setToolTip("Maximum acceptible difference between the variable data in the two files. Leave blank or enter None for no comparison.")
tempValidator = _DoubleOrNoneValidator(self.epsilonWidget)
tempValidator.setBottom(0.0) # only accept positive epsilons
tempValidator.setNotation(QtGui.QDoubleValidator.StandardNotation)
self.epsilonWidget.setValidator(tempValidator)
self.epsilonWidget.editingFinished.connect(self.reportEpsilonChanged)
layoutToUse.addWidget(self.epsilonWidget, currentRow, 1, 1, 2)
currentRow = currentRow + 1
# set up the epsilon percent input box
layoutToUse.addWidget(QtGui.QLabel("epsilon percent:"), currentRow, 0)
self.epsilonPerWidget = QtGui.QLineEdit()
self.epsilonPerWidget.setToolTip("Maximum acceptible difference between the variable data in terms of % of each data point in the A file. Leave blank or enter None for no comparison.")
tempValidator = _DoubleOrNoneValidator(self.epsilonPerWidget)
tempValidator.setBottom(0.0) # only accept positive epsilon percents
tempValidator.setNotation(QtGui.QDoubleValidator.StandardNotation)
self.epsilonPerWidget.setValidator(tempValidator)
self.epsilonPerWidget.editingFinished.connect(self.reportEpsilonPercentChanged)
layoutToUse.addWidget(self.epsilonPerWidget, currentRow, 1, 1, 2)
currentRow = currentRow + 1
# set up the drop down to allow image type selection
layoutToUse.addWidget(QtGui.QLabel("Image Type:"), currentRow, 0)
self.imageSelectionDropDown = QtGui.QComboBox()
self.imageSelectionDropDown.activated.connect(self.reportImageTypeSelected)
layoutToUse.addWidget(self.imageSelectionDropDown, currentRow, 1, 1, 2)
currentRow = currentRow + 1
# set up a button that shows the numerical data
self.rawDataButton = QtGui.QPushButton("Display Data")
self.rawDataButton.clicked.connect(self.reportDisplayRawDataClicked)
layoutToUse.addWidget(self.rawDataButton, currentRow, 1, 1, 1)
# set up a button that shows stats
self.statsButton = QtGui.QPushButton("Display Statistics")
self.statsButton.clicked.connect(self.reportDisplayStatsClicked)
layoutToUse.addWidget(self.statsButton, currentRow, 2, 1, 1)
# set up the button at the bottom that creates plots
self.displayButton = QtGui.QPushButton("Display Plot")
self.displayButton.clicked.connect(self.reportDisplayPlotClicked)
layoutToUse.addWidget(self.displayButton, currentRow, 3, 1, 2)
return layoutToUse
def _add_file_related_controls (self, file_prefix, grid_layout, currentRow) :
"""
add a set of file related controls to the widget, using the
given name prefix and grid layout and starting on "currentRow"
return the index of the next empty "currentRow" after we're finished adding controls
"""
# where we'll hang onto the controls we need to access later
self.widgetInfo[file_prefix] = { }
# set up the file loading control
grid_layout.addWidget(QtGui.QLabel("File " + file_prefix + ":"), currentRow, 0)
filePath = QtGui.QLineEdit()
filePath.setToolTip("The currently loaded file.")
filePath.setReadOnly(True) # this is mostly for displaying the file path, the load button selects it
self.widgetInfo[file_prefix]['path'] = filePath
grid_layout.addWidget(filePath, currentRow, 1, 1, 3)
loadButton = QtGui.QPushButton("Load")
# set some tooltip text
loadButton.setToolTip("Load a file: glance can handle NetCDF, HDF4, HDF5, and AERI files")
# connect the button to an action
loadButton.clicked.connect(partial(self.selectFileToLoad, file_prefix=file_prefix))
self.widgetInfo[file_prefix]['load'] = loadButton
grid_layout.addWidget(loadButton, currentRow, 4)
currentRow = currentRow + 1
# set up the drop down for the variable select
grid_layout.addWidget(QtGui.QLabel("variable name:"), currentRow, 1)
variableSelection = QtGui.QComboBox()
variableSelection.setDisabled(True)
variableSelection.activated.connect(partial(self.reportVariableSelected, file_prefix=file_prefix))
self.widgetInfo[file_prefix]['variable'] = variableSelection
grid_layout.addWidget(variableSelection, currentRow, 2, 1, 3)
currentRow = currentRow + 1
# set up a label to display the variable dimension information
tempShapeLabel = QtGui.QLabel("data shape:")
tempShapeLabel.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
grid_layout.addWidget(tempShapeLabel, currentRow, 1)
dimensionsLabel = QtGui.QLabel(" ")
self.widgetInfo[file_prefix]['dims'] = dimensionsLabel
grid_layout.addWidget(dimensionsLabel, currentRow, 2, 1, 3)
currentRow = currentRow + 1
# set up a table to display variable attribute information
tempAttributesTable = QtGui.QTableWidget()
tempAttributesTable.setColumnCount(2)
# set up the table headers
tempAttributesTable.setHorizontalHeaderLabels(["variable attribute", "value"])
tempAttributesTable.horizontalHeader().setResizeMode(0, QtGui.QHeaderView.Stretch)
tempAttributesTable.horizontalHeader().setResizeMode(1, QtGui.QHeaderView.Stretch)
tempAttributesTable.verticalHeader().hide()
# save the widget and put it in our layout
self.widgetInfo[file_prefix]['attrs'] = tempAttributesTable
grid_layout.addWidget(tempAttributesTable, currentRow, 1, 1, 4)
currentRow = currentRow + 1
# set up a check box to override the fill value loaded from the file
overrideFillButton = QtGui.QCheckBox("override fill value")
overrideFillButton.setToolTip("Check to override the default fill value.")
overrideFillButton.setDisabled(True)
overrideFillButton.stateChanged.connect(partial(self.reportOverrideChange, file_prefix=file_prefix))
self.widgetInfo[file_prefix]['override'] = overrideFillButton
grid_layout.addWidget(overrideFillButton, currentRow, 1)
# now set up the input of the fill value that will be used
grid_layout.addWidget(QtGui.QLabel("fill value:"), currentRow+1, 1)
fillValue = QtGui.QLineEdit()
fillValue.setToolTip("The fill value that will be used.")
tempValidator = _DoubleOrNoneValidator(fillValue)
tempValidator.setNotation(QtGui.QDoubleValidator.StandardNotation)
fillValue.setValidator(tempValidator)
fillValue.setDisabled(True)
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)
currentRow = currentRow + 2
return currentRow
def _build_settings_tab (self) :
"""
built the basic qt controls for the settings tab and lay them out in a grid layout
"""
# create the layout and set up some of the overall record keeping
layoutToUse = QtGui.QGridLayout()
currentRow = 0
# add the drop down for selecting a custom color map
layoutToUse.addWidget(QtGui.QLabel("Color map:"), currentRow, 0)
self.colormapDropDown = QtGui.QComboBox()
self.colormapDropDown.activated.connect(self.colormapSelected)
layoutToUse.addWidget(self.colormapDropDown, currentRow, 1, 1, 2)
currentRow += 1
# add a check box so the user can plot geotiffs as rgb images
doPlotRGB = QtGui.QCheckBox("plot multi-channel GeoTIFFs as RGB images")
doPlotRGB.setToolTip("When plotting original images for multi-channel GeoTIFFs, plot them as RGB images regardless of the selected variable.\n" +
"This setting won't change how comparison images and simpler plots like histograms appear.")
doPlotRGB.setDisabled(False)
doPlotRGB.stateChanged.connect(self.reportPlotGeoTiffsAsRGBToggled)
self.plotGeoTiffsAsRGB = doPlotRGB
layoutToUse.addWidget(doPlotRGB, currentRow, 1, 1, 2)
currentRow += 1
# add the drop down for selecting the data display form
layoutToUse.addWidget(QtGui.QLabel("Data Form:"), currentRow, 0)
self.dataDisplayFormDropDown = QtGui.QComboBox()
self.dataDisplayFormDropDown.activated.connect(self.reportDataFormSelected)
layoutToUse.addWidget(self.dataDisplayFormDropDown, currentRow, 1, 1, 2)
currentRow += 1
# add a check box to constrain originals to the same range
showOriginalsInSameRange = QtGui.QCheckBox("show original plots in same range")
showOriginalsInSameRange.setToolTip("Check to constrain the colorbar range of the Original A and " +
"Original B data plots to be the same.\nThe range will include all data in both A and B.")
showOriginalsInSameRange.setDisabled(False)
showOriginalsInSameRange.stateChanged.connect(self.reportOriginalsRangeToggled)
self.useSameRangeWidget = showOriginalsInSameRange
layoutToUse.addWidget(showOriginalsInSameRange, currentRow, 1, 1, 2)
currentRow += 1
# add lon/lat controls
# add box to enter lon/lat epsilon
layoutToUse.addWidget(QtGui.QLabel("lon/lat epsilon:"), currentRow, 0)
llepsilonWidget = QtGui.QLineEdit()
self.llepsilonWidget = llepsilonWidget
llepsilonWidget.setToolTip("Maximum acceptible difference between the longitudes or latitudes in the two files. Leave blank or enter None for no comparison.")
tempValidator = _DoubleOrNoneValidator(llepsilonWidget)
tempValidator.setBottom(0.0) # only accept positive epsilons
tempValidator.setNotation(QtGui.QDoubleValidator.StandardNotation)
llepsilonWidget.setValidator(tempValidator)
llepsilonWidget.editingFinished.connect(self.reportLLEpsilonChanged)
layoutToUse.addWidget(llepsilonWidget, currentRow, 1, 1, 2)
currentRow += 1
""" TODO, why did I create this control in the first place? remove this...
# add a checkbox to let the user hide data that's spatially invalid based on epsilon
hideDataAssociatedWithInvalidNavigation = QtGui.QCheckBox("hide data associated with mismatched navigation")
hideDataAssociatedWithInvalidNavigation.setToolTip("Check to treat all data matching navigation that differ by more than the " +
"defined lon/lat epsilon as fill data.\n" +
"Whether or not this is checked, if you plot mapped images data matching invalid " +
"(fill or outside of valid range) navigation will be treated as fill data.")
hideDataAssociatedWithInvalidNavigation.setDisabled(False)
hideDataAssociatedWithInvalidNavigation.stateChanged.connect(self.reportHideInvalidNavToggled)
self.hideInvalidNavWidget = hideDataAssociatedWithInvalidNavigation
layoutToUse.addWidget(hideDataAssociatedWithInvalidNavigation, currentRow, 1, 1, 2)
currentRow += 1
"""
# add the lon/lat controls that are separated by file
currentRow = self._add_lon_lat_controls(A_CONST, layoutToUse, currentRow)
currentRow = self._add_lon_lat_controls(B_CONST, layoutToUse, currentRow)
return layoutToUse
def _build_filters_tab (self) :
"""
built the basic qt controls for the filters tab and lay them out in a grid layout
"""
# create the layout and set up some of the overall record keeping
layoutToUse = QtGui.QGridLayout()
currentRow = 0
# add the filtering related controls
currentRow = self._add_simple_filter_controls(A_CONST, layoutToUse, currentRow)
currentRow = self._add_simple_filter_controls(B_CONST, layoutToUse, currentRow)
return layoutToUse
def _add_simple_filter_controls (self, file_prefix, grid_layout, current_row) :
"""
Add controls for the user to enter the simple filters; simple filters
include restricting data to a range and correcting for AWIPS types
return the next free current_row number when finished adding widgets
"""
# label the specific section
tempLabel = QtGui.QLabel(str(file_prefix) + " simple filtering:")
#tempLabel.setToolTip("Simple filters are applied after any complex filters are resolved.") # TODO need to specify this once there are complex filters
tempLabel.setToolTip("Simple filters that will be applied to the data before display or analysis.")
grid_layout.addWidget(tempLabel, current_row, 0)
current_row = current_row + 1
# add something to give range restrictions
restrictToRangeCheckbox = QtGui.QCheckBox("restrict data to range:")
restrictToRangeCheckbox.setToolTip("When checked data outside the entered range will be treated as the fill value.")
restrictToRangeCheckbox.setDisabled(False)
restrictToRangeCheckbox.stateChanged.connect(partial(self.reportRestrictRangeToggled, file_prefix=file_prefix))
self.widgetInfo[file_prefix]["doRestrictRangeCheckbox"] = restrictToRangeCheckbox
grid_layout.addWidget(restrictToRangeCheckbox, current_row, 1, 1, 2)
current_row = current_row + 1
# add the areas to enter the range boundaries
# first add the min
minRangeValue = QtGui.QLineEdit()
minRangeValue.setToolTip("Minimum acceptable data value for " + str(file_prefix) + " data. (Leave blank or enter None for no limit.)")
tempValidator = _DoubleOrNoneValidator(minRangeValue)
# at some point the bottom and top of the ranges will need to be set on this validator
# do I need to save it or can I recover it from the widget?
tempValidator.setNotation(QtGui.QDoubleValidator.StandardNotation)
minRangeValue.setValidator(tempValidator)
minRangeValue.editingFinished.connect(partial(self.reportMinRangeValChanged, file_prefix=file_prefix))
self.widgetInfo[file_prefix]["minRangeRestriction"] = minRangeValue
grid_layout.addWidget(minRangeValue, current_row, 1) #, 1, 2)
# now a label to clarify the relationship between the entry areas
grid_layout.addWidget(QtGui.QLabel("to"), current_row, 2)
# first add the min
maxRangeValue = QtGui.QLineEdit()
maxRangeValue.setToolTip("Maximum acceptable data value for " + str(file_prefix) + " data. (Leave blank or enter None for no limit.)")
tempValidator = _DoubleOrNoneValidator(maxRangeValue)
# at some point the bottom and top of the ranges will need to be set on this validator
# do I need to save it or can I recover it from the widget?
tempValidator.setNotation(QtGui.QDoubleValidator.StandardNotation)
maxRangeValue.setValidator(tempValidator)
maxRangeValue.editingFinished.connect(partial(self.reportMaxRangeValChanged, file_prefix=file_prefix))
self.widgetInfo[file_prefix]["maxRangeRestriction"] = maxRangeValue
grid_layout.addWidget(maxRangeValue, current_row, 3) #1, 1, 2)
current_row = current_row + 1
# add a check box to filter AWIPS data
isAWIPSdata = QtGui.QCheckBox("correct for AWIPS data types")
isAWIPSdata.setToolTip("AWIPS files use signed numbers to hold unsigned data. "
+ "Check this box if you are plotting an AWIPS file and would like your data plotted in the original, unsigned form.")
isAWIPSdata.setDisabled(False)
isAWIPSdata.stateChanged.connect(partial(self.reportIsAWIPSToggled, file_prefix=file_prefix))
self.widgetInfo[file_prefix]["isAWIPScheckbox"] = isAWIPSdata
grid_layout.addWidget(isAWIPSdata, current_row, 1, 1, 2)
current_row = current_row + 1
return current_row
def _add_lon_lat_controls (self, file_prefix, grid_layout, current_row) :
"""
Add the longitude and latitude controls for the given file_prefix to
the given grid_layout starting on current_row
return the next free current_row number when finished adding widgets
"""
# add the label so we know which file this is for
tempLabel = QtGui.QLabel(file_prefix + " Navigation:")
tempLabel.setToolTip("Navigation variables will only be used when drawing mapped plots.")
grid_layout.addWidget(tempLabel, current_row, 0)
current_row = current_row + 1
# add drop down to select latitude
grid_layout.addWidget(QtGui.QLabel("Latitude:"), current_row, 1)
latNameDropDown = QtGui.QComboBox()
latNameDropDown.activated.connect(partial(self.reportLatitudeSelected, file_prefix=file_prefix))
self.widgetInfo[file_prefix]["latName"] = latNameDropDown
grid_layout.addWidget(latNameDropDown, current_row, 2, 1, 2)
current_row = current_row + 1
# add drop down to select longitude
grid_layout.addWidget(QtGui.QLabel("Longitude:"), current_row, 1)
lonNameDropDown = QtGui.QComboBox()
lonNameDropDown.activated.connect(partial(self.reportLongitudeSelected, file_prefix=file_prefix))
self.widgetInfo[file_prefix]["lonName"] = lonNameDropDown
grid_layout.addWidget(lonNameDropDown, current_row, 2, 1, 2)
current_row = current_row + 1
return current_row
################# start methods related to user input #################
def selectFileToLoad (self, file_prefix=None) :
"""
when the load button is pressed, let the user pick a file to load
"""
# get the file from the user
tempFilePath = QtGui.QFileDialog.getOpenFileName(self, 'Open ' + str(file_prefix) + ' file', self.lastFilePath)
LOG.debug ("New " + str(file_prefix) + " file selected in GUI: " + str(tempFilePath))
if (tempFilePath is not None) and (len(tempFilePath) > 0) :
self.lastFilePath = os.path.dirname(str(tempFilePath))
# let our listeners know that the user picked a file
for listener in self.userUpdateListeners :
listener.newFileSelected(file_prefix, tempFilePath)
def reportVariableSelected (self, file_prefix=None) :
"""
when a variable is selected for one of the files, report it to any user update listeners
"""
selectionText = self.widgetInfo[file_prefix]['variable'].currentText()
# let our listeners know the user selected a variable
for listener in self.userUpdateListeners :
listener.userSelectedVariable(file_prefix, selectionText)
def reportOverrideChange (self, file_prefix=None) :
"""
when the user checks or unchecks one of the override checkboxes, report it to user update listeners
"""
# this must be recorded before we tamper with the focus, because that will
# trigger other events that may erase this information temporarily
shouldDoOverride = self.widgetInfo[file_prefix]['override'].isChecked()
# first we need to clean up focus in case it's in one of the line-edit boxes
self.setFocus()
# let our listeners know the user changed an overload setting
for listener in self.userUpdateListeners :
listener.userChangedOverload(file_prefix, shouldDoOverride)
def fillValueChanged (self, file_prefix=None) :
"""
when the user edits a fill value, report it to user update listeners
"""
newFillValue = self.widgetInfo[file_prefix]['fillValue'].text()
# it's still possible for this to not be a number, so fix that
newFillValue = self._extra_number_validation(newFillValue)
self.widgetInfo[file_prefix]['fillValue'].setText(str(newFillValue))
# let our user update listeners know the fill value changed
for listener in self.userUpdateListeners :
listener.userChangedFillValue(file_prefix, newFillValue)
def reportEpsilonChanged (self) :
"""
when the epsilon changes, report it to user update listeners
"""
newEpsilon = self.epsilonWidget.text()
# it's still possible for epsilon to not be a number, so fix that
newEpsilon = self._extra_number_validation(newEpsilon)
self.epsilonWidget.setText(str(newEpsilon))
# let our user update listeners know the epsilon changed
for listener in self.userUpdateListeners :
listener.userChangedEpsilon(newEpsilon)
def reportLLEpsilonChanged (self) :
"""
when the lon/lat epsilon changes, report it to user update listeners
"""
newLLEpsilon = self.llepsilonWidget.text()
# it's still possible for epsilon to not be a number, so fix that
newLLEpsilon = self._extra_number_validation(newLLEpsilon)
self.llepsilonWidget.setText(str(newLLEpsilon))
# let our user update listeners know the llepsilon changed
for listener in self.userUpdateListeners :
listener.userChangedLLEpsilon(newLLEpsilon)
def reportEpsilonPercentChanged (self) :
"""
when the epsilon percent changes, report it to user update listeners
"""
newEpsilonPer = self.epsilonPerWidget.text()
# it's still possible for epsilon percent to not be a number, so fix that
newEpsilonPer = self._extra_number_validation(newEpsilonPer)
self.epsilonPerWidget.setText(str(newEpsilonPer))
# let our user update listeners know the epsilon percent changed
for listener in self.userUpdateListeners :
listener.userChangedEpsilonPercent(newEpsilonPer)
def reportLongitudeSelected (self, file_prefix=None) :
"""
when a longitude variable is selected for one of the files, report it to any user update listeners
"""
selectionText = self.widgetInfo[file_prefix]['lonName'].currentText()
# let our listeners know the user selected a longitude
for listener in self.userUpdateListeners :
listener.userSelectedLongitude(file_prefix, selectionText)
def reportLatitudeSelected (self, file_prefix=None) :
"""
when a latitude variable is selected for one of the files, report it to any user update listeners
"""
selectionText = self.widgetInfo[file_prefix]['latName'].currentText()
# let our listeners know the user selected a latitude
for listener in self.userUpdateListeners :
listener.userSelectedLatitude(file_prefix, selectionText)
def reportImageTypeSelected (self) :
"""
the user selected a new image type, so let our user update listeners know that
"""
newImageType = self.imageSelectionDropDown.currentText()
# report the new image type to our user update listeners
for listener in self.userUpdateListeners :
listener.userSelectedImageType(newImageType)
def colormapSelected (self) :
"""
the user selected a colormap, let our user update listeners know
"""
newColormap = self.colormapDropDown.currentText()
# let the listeners know
for listener in self.userUpdateListeners :
listener.userSelectedColormap(newColormap)
def reportDataFormSelected (self) :
"""
the user selected a new data form, so let our user update listeners know that
"""
newDataForm = self.dataDisplayFormDropDown.currentText()
# report the new data form to our user update listeners
for listener in self.userUpdateListeners :
listener.userSelectedDataForm(newDataForm)
def reportOriginalsRangeToggled (self) :
"""
the user toggled whether or not they want their original plots to be
displayed in a shared range
"""
# this must be recorded before we tamper with the focus, because that will
# trigger other events that may erase this information temporarily
shouldUseSharedRange = self.useSameRangeWidget.isChecked()
# first we need to clean up focus in case it's in one of the line-edit boxes
self.setFocus()
# let our listeners know the user changed an overload setting
for listener in self.userUpdateListeners :
listener.userToggledSharedRange(shouldUseSharedRange)
def reportPlotGeoTiffsAsRGBToggled (self) :
"""
the user toggled whether or not they want their original plots to be
displayed in a shared range
"""
# this must be recorded before we tamper with the focus, because that will
# trigger other events that may erase this information temporarily
doPlotRGB = self.plotGeoTiffsAsRGB.isChecked()
# first we need to clean up focus in case it's in one of the line-edit boxes
self.setFocus()
# let our listeners know the user changed an overload setting
for listener in self.userUpdateListeners :
listener.userToggledGeoTiffAsRGB(doPlotRGB)
def reportHideInvalidNavToggled (self) :
"""
the user toggled whether or not they want the data corresponding
with navigation that's "too different" to be hidden
"""
# this must be recorded before we tamper with the focus, because that will
# trigger other events that may erase this information temporarily
shouldHideMismatchedNav = self.hideInvalidNavWidget.isChecked()
# first we need to clean up focus in case it's in one of the line-edit boxes
self.setFocus()
# let our listeners know the user changed an overload setting
for listener in self.userUpdateListeners :
listener.userToggledHideMismatchedNav(shouldHideMismatchedNav)
def reportRestrictRangeToggled (self, file_prefix=None) :
"""
the user toggled the "restrict data to range" check box
"""
# this must be recorded before we tamper with the focus, because that will
# trigger other events that may erase this information temporarily
shouldRestrictRange = self.widgetInfo[file_prefix]["doRestrictRangeCheckbox"].isChecked()
# first we need to clean up focus in case it's in one of the line-edit boxes
self.setFocus()
# let our listeners know the user changed an overload setting
for listener in self.userUpdateListeners :
listener.userToggledRestrictRange(file_prefix, shouldRestrictRange)
def reportMinRangeValChanged (self, file_prefix=None) :
"""
the user changed the minimum value for the acceptable range
"""
newRangeMin = self.widgetInfo[file_prefix]["minRangeRestriction"].text()
# it's still possible for this to not be a number, so fix that
newRangeMin = self._extra_number_validation(newRangeMin)
self.widgetInfo[file_prefix]["minRangeRestriction"].setText(str(newRangeMin))
# let our user update listeners know the value changed
for listener in self.userUpdateListeners :
listener.userChangedRangeMin(file_prefix, newRangeMin)
def reportMaxRangeValChanged (self, file_prefix=None) :
"""
the user changed the maximum value for the acceptable range
"""
newRangeMax = self.widgetInfo[file_prefix]["maxRangeRestriction"].text()
# it's still possible for this to not be a number, so fix that
newRangeMax = self._extra_number_validation(newRangeMax)
self.widgetInfo[file_prefix]["maxRangeRestriction"].setText(str(newRangeMax))
# let our user update listeners know the value changed
for listener in self.userUpdateListeners :
listener.userChangedRangeMax(file_prefix, newRangeMax)
def reportIsAWIPSToggled (self, file_prefix=None) :
"""
the user toggled the "correct for AWIPS data types" check box
"""
# this must be recorded before we tamper with the focus, because that will
# trigger other events that may erase this information temporarily
dataIsAWIPS = self.widgetInfo[file_prefix]["isAWIPScheckbox"].isChecked()
# first we need to clean up focus in case it's in one of the line-edit boxes
self.setFocus()
# let our listeners know the user changed an overload setting
for listener in self.userUpdateListeners :
listener.userToggledIsAWIPS(file_prefix, dataIsAWIPS)
def reportDisplayStatsClicked (self) :
"""
the user clicked the display stats button
"""
# make sure the focus isn't in a line-edit box
self.statsButton.setFocus()
# now report to our listeners that the user wants stats
for listener in self.userUpdateListeners :
listener.userRequestsStats()
def reportDisplayRawDataClicked (self) :
"""
the user clicked the display raw data button
"""
# make sure the focus isn't in a line-edit box
self.rawDataButton.setFocus()
for listener in self.userUpdateListeners :
listener.userRequestsRawData()
def reportDisplayPlotClicked (self) :
"""
the user clicked the display plot button
"""
# first we need to clean up focus in case it's in one of the line-edit boxes
self.displayButton.setFocus()
# now report to our listeners that the user wants a plot
for listener in self.userUpdateListeners :
listener.userRequestsPlot()
def _extra_number_validation (self, string_that_should_be_a_number) :
"""
try to validate the string that should be a number
"""
toReturn = None
if toReturn != "" :
try :
toReturn = int(string_that_should_be_a_number)
except ValueError :
try :
toReturn = float(string_that_should_be_a_number)
except ValueError :
pass # in this case we can't convert it, so just toss it
return toReturn
################# end methods related to user input #################
################# start data model update related methods #################
def displayStatsData (self, aVariableName, bVariableName, statsAnalysis) :
"""
given the names of the two variables and the statistical analysis,
display this to the user
"""
tempID = self.statsCounter
self.statsCounter += self.statsCounter + 1
# I don't like this solution, but it would allow me to show multiple sets of stats at a time
self.statsWindows[tempID] = StatisticsDisplayWindow(tempID,
aVariableName, variable_name_b=bVariableName,
statsTextToDisplay=statsAnalysis, stored_in=self.statsWindows)
def displayVarData (self, variableName, fileDescriptor, variableDataObject) :
"""
given variable data, pop a window to show it to the user
"""
tempID = self.dataShowCounter
self.dataShowCounter += 1
# not the best solution ever, but it works for now
self.dataShowWindows[tempID] = RawDataDisplayWindow(tempID,
variableDataObject, variableName,
file_descriptor=fileDescriptor,
stored_in=self.dataShowWindows)
def fileDataUpdate (self, file_prefix, file_path, selected_variable, use_fill_override, new_fill_value, variable_dimensions,
variable_list=None, attribute_list=None) :
"""
The file data for one of the two files has changed. Update the GUI to reflect the change in data.
"""
# set the path
self.widgetInfo[file_prefix]['path'].setText(file_path)
# if we got a new variable list, set up the appropriate drop down list
if variable_list is not None :
# set up the list of selectable variables for analysis
self.widgetInfo[file_prefix]['variable'].clear()
self.widgetInfo[file_prefix]['variable'].addItems(variable_list)
# set the selected variable
tempPosition = self.widgetInfo[file_prefix]['variable'].findText(selected_variable)
self.widgetInfo[file_prefix]['variable'].setCurrentIndex(tempPosition)
# set the override
self.widgetInfo[file_prefix]['override'].setChecked(use_fill_override)
# set the fill value that's going to be used
self.widgetInfo[file_prefix]['fillValue'].setText(str(new_fill_value))
# show the variable dimensions
self.widgetInfo[file_prefix]['dims'].setText(str(variable_dimensions))
# if needed, show the attribute list
if attribute_list is not None :
temp_table = self.widgetInfo[file_prefix]['attrs']
temp_table.clearContents()
temp_table.setRowCount(len(attribute_list.keys()))
rowCounter = 0
for attributeKey in sorted(attribute_list.keys()) :
temp_table.setCellWidget(rowCounter, 0, QtGui.QLabel(str(attributeKey)))
temp_table.setCellWidget(rowCounter, 1, QtGui.QLabel(str(attribute_list[attributeKey])))
rowCounter = rowCounter + 1
# if there is a file selected, enable some of the other controls
if file_path != "" :
self.widgetInfo[file_prefix]['variable'].setDisabled(False)
self.widgetInfo[file_prefix]['override'].setDisabled(False)
self.widgetInfo[file_prefix]['fillValue'].setDisabled(not use_fill_override)
def updateSelectedLatLon(self, filePrefix, newLatitude, newLongitude, lonlatList=None) :
"""
Update the latitude and longitude names that are selected in the drop down,
if a list is given, then replace the list of options that are being displayed for that file.
"""
# if we got a new list, set up the appropriate drop down lists
if lonlatList is not None :
# set up the longitude and latitude selectors
self.widgetInfo[filePrefix]['latName'].clear()
self.widgetInfo[filePrefix]['latName'].addItems(lonlatList)
self.widgetInfo[filePrefix]['lonName'].clear()
self.widgetInfo[filePrefix]['lonName'].addItems(lonlatList)
# set the selected latitude
tempPosition = self.widgetInfo[filePrefix]['latName'].findText(newLatitude)
self.widgetInfo[filePrefix]['latName'].setCurrentIndex(tempPosition)
# set the selected longitude
tempPosition = self.widgetInfo[filePrefix]['lonName'].findText(newLongitude)
self.widgetInfo[filePrefix]['lonName'].setCurrentIndex(tempPosition)
def updateEpsilon (self, epsilon) :
"""
update the comparison epsilon displayed to the user
"""
stringToUse = str(epsilon) if epsilon is not None else ""
self.epsilonWidget.setText(str(epsilon))
def updateEpsilonPercent (self, epsilonPercent) :
"""
update the epsilon percent displayed to the user
"""
self.epsilonPerWidget.setText(str(epsilonPercent))
def updateLLEpsilon (self, newLonLatEpsilon) :
"""
update the epsilon for longitude and latitude displayed to the user
"""
self.llepsilonWidget.setText(str(newLonLatEpsilon))
def updateImageTypes (self, imageType, list=None) :
"""
update the image type that's selected,
if the list is given, clear and reset the list of possible image types
"""
# replace the list if needed
if list is not None :
self.imageSelectionDropDown.clear()
self.imageSelectionDropDown.addItems(list)
# change the currently selected image type
tempPosition = self.imageSelectionDropDown.findText(imageType)
self.imageSelectionDropDown.setCurrentIndex(tempPosition)
def updateColormaps(self, colormap, list=None) :
"""
update the colormap that's selected,
if the list is given, clear and reset the list of possible colormaps
"""
# replace the list if needed
if list is not None :
self.colormapDropDown.clear()
self.colormapDropDown.addItems(list)
#change the currently selected colormap
tempPosition = self.colormapDropDown.findText(colormap)
self.colormapDropDown.setCurrentIndex(tempPosition)
def updateDataForms(self, dataForm, list=None) :
"""
update the data form that's selected,
if the list is given, clear and reset the list of possible data forms
"""
# replace the list if needed
if list is not None :
self.dataDisplayFormDropDown.clear()
self.dataDisplayFormDropDown.addItems(list)
# change the currently selected data form
tempPosition = self.dataDisplayFormDropDown.findText(dataForm)
self.dataDisplayFormDropDown.setCurrentIndex(tempPosition)
def updateUseSharedRange(self, doUseSharedRange) :
"""
update whether or not a shared range should be used
"""
self.useSameRangeWidget.setChecked(doUseSharedRange)
def updatePlotGeoTiffAsRGB (self, doPlotGeoTiffAsRGB) :
"""
update whether or not to plot geotiffs as rgb images
"""
self.plotGeoTiffsAsRGB.setChecked(doPlotGeoTiffAsRGB)
def updateHideMismatchNav(self, shouldHideBasedOnNavMismatch) :
"""
update whether or not the data corresponding to mismatched navigation
should be hidden when plotting
"""
#self.hideInvalidNavWidget.setChecked(shouldHideBasedOnNavMismatch)
def updateDoRestrictRange (self, filePrefix, doRestrictRange) :
"""
update our control to reflect whether or not the range is going to be restricted
"""
self.widgetInfo[filePrefix]["doRestrictRangeCheckbox"].setChecked(doRestrictRange)
def updateRestrictRangeMin (self, filePrefix, newRangeMin) :
"""
update the minimum for the range restriction
"""
self.widgetInfo[filePrefix]["minRangeRestriction"].setText(str(newRangeMin))
def updateRestrictRangeMax (self, filePrefix, newRangeMax) :
"""
update the maximum for the range restriction
"""
self.widgetInfo[filePrefix]["maxRangeRestriction"].setText(str(newRangeMax))
def updateIsAWIPS (self, filePrefix, isAWIPS) :
"""
update whether or not this should be treated as an AWIPS file
"""
self.widgetInfo[filePrefix]["isAWIPScheckbox"].setChecked(isAWIPS)
################# end data model update related methods #################
def showWarning (self, warningMessage):
"""
show the user a warning dialog to the user
"""
tempMessageBox = QtGui.QMessageBox()
tempMessageBox.setText(warningMessage)
tempMessageBox.exec_()
def registerUserUpdateListener (self, objectToRegister) :
"""
add the given object to our list of listeners
"""
if objectToRegister not in self.userUpdateListeners :
self.userUpdateListeners.append(objectToRegister)
class StatisticsDisplayWindow (QtGui.QWidget) :
"""
This class represents a window that displays a statistics comparison between two variables.
This window is intended to be displayed in a non-modal manner.
"""
def __init__ (self, id_number, variable_name_a, variable_name_b=None,
statsTextToDisplay="", stored_in=None, parent=None) :
"""
set up a window to display stats
"""
QtGui.QWidget.__init__(self, parent)
self.id = id_number
self.stored = stored_in
# build and set the window title
tempTitle = "Statistics Comparing " + str(variable_name_a)
if (variable_name_b is not None) and (variable_name_b != variable_name_a) :
tempTitle = tempTitle + " / " + str(variable_name_b)
tempTitle = tempTitle + " data"
self.setWindowTitle(tempTitle)
# create the layout and set up some of the overall record keeping
layoutToUse = QtGui.QGridLayout()
# set up the box that shows the stats
self.statsText = QtGui.QTextEdit()
self.statsText.setHtml(statsTextToDisplay)
self.statsText.setReadOnly(True)
layoutToUse.addWidget(self.statsText, 1, 1)
# set up the overall window geometry
self.setLayout(layoutToUse)
self.setGeometry(400, 400, 500, 500)
self.show()
def closeEvent (self, event) :
"""
we need to clean some stuff up when the window wants to close
"""
if self.stored is not None :
del self.stored[self.id]
event.accept()
class NumpyArrayTableModel (QtCore.QAbstractTableModel) :
"""
this is a model designed to show numpy arrays in
QTableView widgets
"""
def __init__(self, array_to_show, parent=None):
"""
given the data to show, build our model
"""
QtCore.QAbstractTableModel.__init__(self, parent)
self.np_array = array_to_show
def rowCount(self, parent=None):
return self.np_array.shape[0]
def columnCount(self, parent=None):
to_return = 1
if len(self.np_array.shape) > 1 :
to_return = self.np_array.shape[1]
return to_return
def data(self, index, role=QtCore.Qt.DisplayRole):
if index.isValid():
if role == QtCore.Qt.DisplayRole:
row = index.row()
col = index.column()
if len(self.np_array.shape) > 1 :
return QtCore.QVariant("%.5f"%self.np_array[row, col])
else :
return QtCore.QVariant("%.5f"%self.np_array[row])
return QtCore.QVariant()
class RawDataDisplayWindow (QtGui.QWidget) :
def __init__ (self, id_number, data_object_to_display, variable_name,
file_descriptor=None, stored_in=None, parent=None) :
"""
set up a window to display raw data
"""
QtGui.QWidget.__init__(self, parent)
self.id = id_number
self.stored = stored_in
# set the window title
temp_title = "Data display for " + str(variable_name)
temp_title = temp_title + " in file " + file_descriptor if file_descriptor is not None else temp_title
self.setWindowTitle(temp_title)
# create the layout and set up some of the overall record keeping
layoutToUse = QtGui.QGridLayout()
# create a table view to display our data
self.dataView = QtGui.QTableView()
self.dataView.setModel(NumpyArrayTableModel(data_object_to_display.data))
layoutToUse.addWidget(self.dataView, 1, 1)
# set up the overall window geometry
self.setLayout(layoutToUse)
self.setGeometry(400, 400, 500, 500)
self.show()
def closeEvent (self, event) :
"""
we need to clean some stuff up when the window wants to close
"""
if self.stored is not None :
del self.stored[self.id]
if __name__=='__main__':
import doctest
doctest.testmod()