Commit 3934705a authored by James Harle's avatar James Harle
Browse files

Adding pynemo code.

parent b2981577
pynemo/gui/all_mask.png

220 Bytes

<?xml version="1.0" encoding="UTF-8"?>
<netcdf xmlns="http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2" title="NEMO aggregation">
<aggregation type="union" >
</aggregation>
</netcdf>
\ No newline at end of file
pynemo/gui/border.png

186 Bytes

pynemo/gui/freehand.png

1.14 KB

pynemo/gui/max_depth.png

436 Bytes

pynemo/gui/minus.png

1.63 KB

# The Normalize class is largely based on code provided by Sarah Graves.
import numpy as np
import numpy.ma as ma
import matplotlib.cbook as cbook
from matplotlib.colors import Normalize
class MyNormalize(Normalize):
'''
A Normalize class for imshow that allows different stretching functions
for astronomical images.
'''
def __init__(self, stretch='linear', exponent=5, vmid=None, vmin=None,
vmax=None, clip=False):
'''
Initalize an APLpyNormalize instance.
Optional Keyword Arguments:
*vmin*: [ None | float ]
Minimum pixel value to use for the scaling.
*vmax*: [ None | float ]
Maximum pixel value to use for the scaling.
*stretch*: [ 'linear' | 'log' | 'sqrt' | 'arcsinh' | 'power' ]
The stretch function to use (default is 'linear').
*vmid*: [ None | float ]
Mid-pixel value used for the log and arcsinh stretches. If
set to None, a default value is picked.
*exponent*: [ float ]
if self.stretch is set to 'power', this is the exponent to use.
*clip*: [ True | False ]
If clip is True and the given value falls outside the range,
the returned value will be 0 or 1, whichever is closer.
'''
if vmax < vmin:
raise Exception("vmax should be larger than vmin")
# Call original initalization routine
Normalize.__init__(self, vmin=vmin, vmax=vmax, clip=clip)
# Save parameters
self.stretch = stretch
self.exponent = exponent
if stretch == 'power' and np.equal(self.exponent, None):
raise Exception("For stretch=='power', an exponent should be specified")
if np.equal(vmid, None):
if stretch == 'log':
if vmin > 0:
self.midpoint = vmax / vmin
else:
raise Exception("When using a log stretch, if vmin < 0, then vmid has to be specified")
elif stretch == 'arcsinh':
self.midpoint = -1. / 30.
else:
self.midpoint = None
else:
if stretch == 'log':
if vmin < vmid:
raise Exception("When using a log stretch, vmin should be larger than vmid")
self.midpoint = (vmax - vmid) / (vmin - vmid)
elif stretch == 'arcsinh':
self.midpoint = (vmid - vmin) / (vmax - vmin)
else:
self.midpoint = None
def __call__(self, value, clip=None):
#read in parameters
method = self.stretch
exponent = self.exponent
midpoint = self.midpoint
# ORIGINAL MATPLOTLIB CODE
if clip is None:
clip = self.clip
if cbook.iterable(value):
vtype = 'array'
val = ma.asarray(value).astype(np.float)
else:
vtype = 'scalar'
val = ma.array([value]).astype(np.float)
self.autoscale_None(val)
vmin, vmax = self.vmin, self.vmax
if vmin > vmax:
raise ValueError("minvalue must be less than or equal to maxvalue")
elif vmin == vmax:
return 0.0 * val
else:
if clip:
mask = ma.getmask(val)
val = ma.array(np.clip(val.filled(vmax), vmin, vmax),
mask=mask)
result = (val - vmin) * (1.0 / (vmax - vmin))
# CUSTOM APLPY CODE
# Keep track of negative values
negative = result < 0.
if self.stretch == 'linear':
pass
elif self.stretch == 'log':
result = ma.log10(result * (self.midpoint - 1.) + 1.) \
/ ma.log10(self.midpoint)
elif self.stretch == 'sqrt':
result = ma.sqrt(result)
elif self.stretch == 'arcsinh':
result = ma.arcsinh(result / self.midpoint) \
/ ma.arcsinh(1. / self.midpoint)
elif self.stretch == 'power':
result = ma.power(result, exponent)
else:
raise Exception("Unknown stretch in APLpyNormalize: %s" %
self.stretch)
# Now set previously negative values to 0, as these are
# different from true NaN values in the FITS image
result[negative] = -np.inf
if vtype == 'scalar':
result = result[0]
return result
def inverse(self, value):
# ORIGINAL MATPLOTLIB CODE
if not self.scaled():
raise ValueError("Not invertible until scaled")
vmin, vmax = self.vmin, self.vmax
# CUSTOM APLPY CODE
if cbook.iterable(value):
val = ma.asarray(value)
else:
val = value
if self.stretch == 'linear':
pass
elif self.stretch == 'log':
val = (ma.power(10., val * ma.log10(self.midpoint)) - 1.) / (self.midpoint - 1.)
elif self.stretch == 'sqrt':
val = val * val
elif self.stretch == 'arcsinh':
val = self.midpoint * \
ma.sinh(val * ma.arcsinh(1. / self.midpoint))
elif self.stretch == 'power':
val = ma.power(val, (1. / self.exponent))
else:
raise Exception("Unknown stretch in APLpyNormalize: %s" %
self.stretch)
return vmin + val * (vmax - vmin)
pynemo/gui/nemo-icon.png

657 Bytes

'''
Created on 21 Jan 2015
@author: Mr. Srikanth Nagella
'''
# pylint: disable=E1103
# pylint: disable=no-name-in-module
# pylint: disable=E1002
from PyQt4 import QtGui
from .nemo_bdy_namelist_edit import NameListEditor
from .nemo_bdy_mask_gui import MatplotlibWidget
from PyQt4.QtGui import QSizePolicy
from PyQt4.Qt import Qt
class InputWindow(QtGui.QDialog):
'''
Input Window for editing pyNEMO settings
'''
def __init__(self, setup):
'''
Initialises the UI components
'''
super(InputWindow, self).__init__()
#initialise NameListEditor
self.nl_editor = NameListEditor(setup)
#initialise MatplotlibWidget
self.mpl_widget = MatplotlibWidget()
#connect namelistedit to matplotlibwidget
self.nl_editor.bathymetry_update.connect(self.mpl_widget.set_bathymetry_file)
self.nl_editor.mask_update.connect(self.mpl_widget.save_mask_file)
self.nl_editor.mask_settings_update.connect(self.mpl_widget.set_mask_settings)
if setup.bool_settings['mask_file']:
try: #Try to load with bathy and mask file
self.mpl_widget.set_bathymetry_file(setup.settings['bathy'], setup.settings['mask_file'])
except: # if mask file is not readable then open with bathy
self.mpl_widget.set_bathymetry_file(setup.settings['bathy'],None)
else:
self.mpl_widget.set_bathymetry_file(setup.settings['bathy'],None)
self.mpl_widget.set_mask_settings(float(setup.settings['mask_max_depth']), float(setup.settings['mask_shelfbreak_dist']))
splitter = QtGui.QSplitter(Qt.Horizontal)
splitter.addWidget(self.nl_editor)
splitter.addWidget(self.mpl_widget)
hbox = QtGui.QHBoxLayout()
hbox.addWidget(splitter)
self.setLayout(hbox)
#set the Dialog title
self.setWindowTitle("PyNEMO Settings Editor")
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))
#show the window
self.show()
'''
Mask Class to hold the mask information and operation on mask
@author: Mr. Srikanth Nagella
'''
# pylint: disable=E1103
# pylint: disable=no-name-in-module
import numpy as np
from netCDF4 import Dataset
import logging
from scipy import ndimage
import matplotlib.pyplot as plt
from pynemo.utils import gcoms_break_depth
from PyQt4.QtGui import QMessageBox
from PyQt4 import QtCore
class Mask(object):
"""This is a Mask holder. which reads from a netCDF bathymetry file and
stores it in 'data' member variable"""
min_depth = 200.0
shelfbreak_dist = 200.0
mask_type = 0
def __init__(self, bathymetry_file=None, mask_file=None, min_depth = 200.0, shelfbreak_dist = 200.0):
"""Initialises the Mask data"""
self.data = None
self.bathy_data = None
self.mask_file = None
self.logger = logging.getLogger(__name__)
self.set_mask_file(mask_file)
self.set_bathymetry_file(bathymetry_file)
self.min_depth = min_depth
self.shelfbreak_dist = shelfbreak_dist
def set_mask_file(self, mask_file):
"""
Reads the mask data from the mask file
Assumes the mask file is 2D
"""
self.mask_file = mask_file
#if mask file is not set then reset the data
if self.mask_file == None:
self.data = None
return
try:
mask_nc = Dataset(str(self.mask_file), mode="r")
data = mask_nc.variables['mask']
self.data = data[:,:]
except KeyError:
self.logger.error('Mask file missing have mask variable')
raise
except (IOError, RuntimeError):
self.logger.error('Cannot open mask file '+self.mask_file)
self.data = None
raise
def set_bathymetry_file(self, bathy_file):
""" This reads the bathymetry file and sets the land to 0 and ocean to 1 """
if bathy_file == None:
return
try:
self.bathymetry_file = str(bathy_file)
#open the bathymetry file
self.bathy_nc = Dataset(self.bathymetry_file)
self.lon = np.asarray(self.bathy_nc.variables['nav_lon'])
self.lat = np.asarray(self.bathy_nc.variables['nav_lat'])
self.bathy_data = self.bathy_nc.variables['Bathymetry'][:,:]
try: #check if units exists otherwise unknown. TODO
self.data_units = self.bathy_nc.variables['Bathymetry'].units
except AttributeError:
self.data_units = "unknown"
if self.data is None:
self.data = self.bathy_nc.variables['Bathymetry']
self.data = np.asarray(self.data[:, :])
self.data = np.around((self.data + .5).clip(0, 1))
#apply default 1px border
self.apply_border_mask(1)
except KeyError:
self.logger.error('Bathymetry file does not have Bathymetry variable')
raise
except (IOError, RuntimeError):
self.logger.error('Cannot open bathymetry file '+self.bathymetry_file)
raise
def save_mask(self, mask_file):
"""Reads the mask data from the mask file"""
if mask_file == None:
mask_file = self.mask_file
try:
self.logger.info('Writing mask data to %s' % mask_file)
msgbox = QMessageBox()
msgbox.setWindowTitle("Saving....")
msgbox.setText("Writing mask data to file, please wait...")
msgbox.setWindowModality(QtCore.Qt.NonModal)
msgbox.show()
mask_nc = Dataset(str(mask_file), mode="w")
mask_nc.createDimension('y', size=len(self.bathy_nc.dimensions['y']))
mask_nc.createDimension('x', size=len(self.bathy_nc.dimensions['x']))
nav_lat = mask_nc.createVariable('nav_lat', 'f4', ('y','x',))
nav_lon = mask_nc.createVariable('nav_lon', 'f4', ('y','x',))
mask_var = mask_nc.createVariable('mask', 'f4', ('y','x',))
mask_var[...] = self.data
nav_lat[...] = self.lat
nav_lon[...] = self.lon
msgbox.close()
except (IOError, RuntimeError):
QMessageBox.information(None,"pyNEMO","Failed to write the mask file, please check the permissions")
self.logger.info('Cannot open mask file for writing '+mask_file)
raise
def apply_border_mask(self, pixels):
""" pixels is number of pixels in the border that need applying mask"""
if self.data is not None and pixels < self.data.shape[0] and pixels < self.data.shape[1]:
if pixels != 0:
tmp = np.ones(self.data.shape, dtype=bool)
tmp[pixels:-pixels, pixels:-pixels] = False
else:
tmp = np.zeros(self.data.shape, dtype=bool)
self.reset_mask()
self.data = self.data * -1
self.data[tmp] = -1
self.select_the_largest_region()
def add_mask(self, index, roi):
""" Adds the masks for the given index values depending on the type of mask selected"""
out_index = None
if self.mask_type == None or self.mask_type == 0:
out_index = index
elif self.mask_type == 1: #maximum depth
out_index = self._get_bathy_depth_index(index,self.min_depth)
out_index = self.remove_small_regions(out_index)
out_index = self.fill_small_regions(out_index)
elif self.mask_type == 2: # shelf break
#dummy, shelf_break = gcoms_break_depth.gcoms_break_depth(self.bathy_data[index])
#out_index = self._get_bathy_depth_index(index, shelf_break)
out_index = gcoms_break_depth.polcoms_select_domain(self.bathy_data, self.lat,
self.lon, roi, self.shelfbreak_dist)
out_index = np.logical_and(index, out_index)
out_index = self.remove_small_regions(out_index)
#out_index = self.fill_small_regions(out_index)
#if index is not empty
if out_index is not None:
tmp = self.data[out_index]
tmp[tmp == -1] = 1
self.data[out_index] = tmp
self.select_the_largest_region()
def _get_bathy_depth_index(self, index, depth):
""" returns the indices from the input field `index` which have bathymetry depth greater
than the input field `depth`
"""
output_index = self.bathy_data < depth
output_index = np.logical_and(index,output_index)
return output_index
def remove_mask(self,index,roi):
""" Removes the mask for the given index values depending on the type of mask selected """
out_index = None
if self.mask_type == None or self.mask_type == 0:
out_index = index
elif self.mask_type == 1: #minimum depth
out_index = self._get_bathy_depth_index(index,self.min_depth)
out_index = self.remove_small_regions(out_index)
out_index = self.fill_small_regions(out_index)
elif self.mask_type == 2: # shelf break
# dummy, shelf_break = gcoms_break_depth.gcoms_break_depth(self.bathy_data[index])
# out_index = self._get_bathy_depth_index(index, shelf_break)
out_index = gcoms_break_depth.polcoms_select_domain(self.bathy_data, self.lat,
self.lon, roi, self.shelfbreak_dist)
out_index = np.logical_and(index, out_index)
out_index = self.remove_small_regions(out_index)
#out_index = self.fill_small_regions(out_index)
tmp = self.data[out_index]
tmp[tmp == 1] = -1
self.data[out_index] = tmp
self.select_the_largest_region()
def set_minimum_depth_mask(self, depth):
self.min_depth = depth
def set_mask_type(self, mask_type):
""" Sets the mask type """
self.mask_type = mask_type
def remove_small_regions(self, index):
""" Removes the small regions in the selection area and takes only the largest area
for mask"""
#prepare the regions
mask_data = np.zeros(self.data.shape)
mask_data[index] = self.data[index]
#connected components
label_mask, num_labels = ndimage.label(mask_data)
mean_values = ndimage.sum(np.ones(self.data.shape),label_mask,range(1, num_labels+1))
max_area_mask = None
if mean_values.size != 0:
max_area_index = np.argmax(mean_values)+1
max_area_mask = label_mask == max_area_index
return max_area_mask
def fill_small_regions(self, index):
""" This method fills the small regions of the selection area and fills them up """
#prepare the region with mask and land as 0, ocean as 1
mask_data = np.zeros(self.data.shape)
mask_data[index] = 1
#remove the small unmask holes
mask_withoutholes = ndimage.binary_fill_holes(mask_data)
return np.where(mask_withoutholes==1)
def reset_mask(self):
""" This method resets the data back to no mask with land fill """
self.data = np.around((self.bathy_data + .5).clip(0, 1))*-1
def select_the_largest_region(self):
""" This method tidies up the mask by selecting the largest masked region. this is to avoid two disconnected masked regions """
mask_data = np.zeros(self.data.shape)
mask_data[:,:] = self.data[:,:]
mask_data[mask_data == -1] = 0
#connected components
label_mask, num_labels = ndimage.label(mask_data)
if num_labels == 0: #if mask is empty/clear
return
mean_values = ndimage.sum(np.ones(self.data.shape),label_mask,range(1, num_labels+1))
max_area_mask = None
if mean_values.size != 0:
max_area_index = np.argmax(mean_values)+1
max_area_mask = label_mask == max_area_index
self.data = np.around((self.bathy_data + .5).clip(0, 1))
self.data[self.data == 1] = -1
self.data[max_area_mask] = self.data[max_area_mask] * -1
def apply_mediterrian_mask(self):
""" This is mediterrian mask specific for the test bathymetry file """
tmp = self.data[0:59, 280:350]
tmp[tmp == 1] = -1
self.data[0:59, 280:350] = tmp
'''
Created on 12 Jan 2015
@author: Mr. Srikanth Nagella
'''
# pylint: disable=E1103
# pylint: disable=no-name-in-module
from netCDF4 import Dataset
from mpl_toolkits.basemap import Basemap, cm
import numpy as np
from .selection_editor import PolygonEditor, BoxEditor
import os.path
from PyQt4.QtCore import pyqtSignal, pyqtSlot, Qt
from nemo_bdy_mask import Mask
import logging
from PyQt4.QtGui import QSizePolicy
from matplotlib.colors import Normalize
mask_alpha = 0.3
from PyQt4 import QtGui
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.path import Path
from matplotlib.transforms import Bbox
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
# pylint: disable=E1002
class MatplotlibWidget(QtGui.QWidget):
"""This class is a QWidget for pyNEMO mask plot"""
min_depth = 200.0
shelfbreak_dist = 200.0
mask_type = 0
def __init__(self, parent=None, mask=None, min_depth = 200.0, shelfbreak_dist = 200.0,*args, **kwargs):
""" Initialises the mask, matplot and the navigation toolbar """
super(MatplotlibWidget, self).__init__(parent)
#QtGui.QWidget.__init__(self, parent)
self.figure = Figure(*args, **kwargs)
self.canvas = FigureCanvas(self.figure)
self.mask = mask
self.min_depth = min_depth
self.shelfbreak_dist = shelfbreak_dist
if self.mask is not None:
self.mask.min_depth = min_depth
self.mask.shelfbreak_dist = shelfbreak_dist
self.toolbar = NemoNavigationToolbar(self.canvas, self)
self.toolbar.locLabel.setMinimumWidth(100)
self.toolbar.locLabel.setMaximumWidth(170)
self.toolbar.locLabel.setSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed)
self.toolbar.locLabel.setAlignment(Qt.AlignLeft|Qt.AlignTop)
self.toolbar.drawing_tool.connect(self.drawing_tool_callback)
self.axes = self.figure.add_subplot(111)
self.cbar = None
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
self.setLayout(layout)
self._drawing_tool = None
self._drawing_tool_name = None
self.create_basemap()
@pyqtSlot(str)
def drawing_tool_callback(self, toolname):
""" callback for the drawing tool when the signal of change of drawing tool is
received"""
if self._drawing_tool_name != None and toolname == "": #if tool is disabled
self._drawing_tool.disable()
self._drawing_tool_name = None
self._drawing_tool = None
self.canvas.draw()
else:
self._drawing_tool_name = toolname
if self._drawing_tool_name == "freehand": #if freehand tool is enabled
self._drawing_tool = PolygonEditor(self.axes, self.canvas)
self.canvas.draw()
elif self._drawing_tool_name == "rectangle": #if rectange tool is enabled
self._drawing_tool = BoxEditor(self.axes, self.canvas)
self._drawing_tool.enable()
self.canvas.draw()
def create_basemap(self):
""" Draws the basemap and contour with mask information"""
if self.mask == None:
return
x = np.arange(0, self.mask.lon.shape[0])
y = np.arange(0, self.mask.lon.shape[1])
x_vals, y_vals = np.meshgrid(y, x)
Z = self.mask.bathy_data[...].astype(np.float64)
#Z[Z==0] = np.nan
Z = np.ma.masked_where(Z==0, Z)
cmap = plt.get_cmap('GnBu')
cmap.set_bad('0.0')
cmap.set_under('black',1.0)
cmap.set_over('black',1.0)
transcmap = plt.get_cmap('autumn')
transcmap.set_bad(alpha=0.5)
masklayer = np.ma.masked_where(self.mask.data==-1,self.mask.data)
extent = (0, self.mask.lon.shape[1],0, self.mask.lon.shape[0])
#cax = self.axes.pcolormesh(x_vals, y_vals, Z, cmap=cmap)#, extend='min')#cmap=plt.get_cmap('GnBu'))#cmap=cm.s3pcpn)
cax = self.axes.imshow(Z, cmap = cmap, origin="lower",extent=extent,aspect='auto')
#self.axes.contourf(x_vals, y_vals, masklayer, [-2, -1, 0, 1, 2], cmap=transcmap,\
# alpha=mask_alpha)
self.axes.imshow(masklayer, cmap=transcmap,alpha=0.3,origin="lower",extent=extent,aspect='auto')
zmin = np.amin(Z)
zmax = np.amax(Z)
if self.cbar is not None:
self.cbar.remove()
self.cbar = self.figure.colorbar(cax,ticks=np.linspace(zmin,zmax,10),orientation='horizontal')
self.cbar.set_label("Bathymetry (units=%s)"%self.mask.data_units)
self.canvas.draw()
def reset_mask(self):
if self.mask == None:
return
self.mask.reset_mask()
self.axes.clear()
self.create_basemap()
def add_mask(self):
""" adds the selected region in the drawing tool to the mask """
if self._drawing_tool_name != "" and self.mask != None:
if self._drawing_tool.polygon != None:
x = np.arange(0, self.mask.lon.shape[0])
y = np.arange(0, self.mask.lon.shape[1])
x_vals, y_vals = np.meshgrid(y, x)
grid = zip(x_vals.ravel(), y_vals.ravel())
self._drawing_tool.polygon.set_linewidth(1.0)
p_path = Path(self._drawing_tool.polygon.xy)
index = p_path.contains_points(grid)
index = index.reshape(self.mask.lon.shape)
xmin, ymin = np.min(self._drawing_tool.polygon.xy, axis=0)
xmax, ymax = np.max(self._drawing_tool.polygon.xy, axis=0)
self.mask.add_mask(index,[xmin,xmax,ymin,ymax])
self._drawing_tool.reset()
self.axes.clear()
self.create_basemap()
def remove_mask(self):
""" removes the selected region in the drawing tool from the mask """
if self._drawing_tool_name != "" and self.mask != None:
if self._drawing_tool.polygon != None:
x = np.arange(0, self.mask.lon.shape[0])
y = np.arange(0, self.mask.lon.shape[1])
x_vals, y_vals = np.meshgrid(y, x)
grid = zip(x_vals.ravel(), y_vals.ravel()) #check for the index
self._drawing_tool.polygon.set_linewidth(1.0)
p_path = Path(self._drawing_tool.polygon.xy)
index = p_path.contains_points(grid)
index = index.reshape(self.mask.lon.shape)
xmin, ymin = np.min(self._drawing_tool.polygon.xy, axis=0)
xmax, ymax = np.max(self._drawing_tool.polygon.xy, axis=0)
self.mask.remove_mask(index,[xmin,xmax,ymin,ymax])
self._drawing_tool.reset()
self.axes.clear()
self.create_basemap()
def apply_border_mask(self):
""" This applies an mask of given number of pixels at the border of the mask"""
pixels, ok_btn_pressed = QtGui.QInputDialog.getText(self, 'Mask: Border Input',
'Enter number of pixel of border \
to be added to mask:')
if ok_btn_pressed:
self.mask.apply_border_mask(int(pixels))
self.axes.clear()
self.create_basemap()
def set_mask_type(self,type):
""" Sets the mask type """
self.mask_type = type
self.mask.mask_type = type
@pyqtSlot(str, str)
def set_bathymetry_file(self, bathymetry_filename, mask_file):
""" Set the bathymetry file """
try:
self.mask = Mask(bathymetry_filename, mask_file, self.min_depth, self.shelfbreak_dist)
self.mask.mask_type = self.mask_type
self.create_basemap()
except RuntimeError:
pass # couldn't set the new file name
@pyqtSlot(str)
def save_mask_file(self, mask_file):
""" Save the mask data to mask_file """
if self.mask is not None:
self.mask.save_mask(mask_file)
@pyqtSlot(float, float)
def set_mask_settings(self, min_depth, shelfbreak_dist):
""" Mask settings update """
self.min_depth = min_depth
self.shelfbreak_dist = shelfbreak_dist
self.mask.min_depth = min_depth
self.mask.shelfbreak_dist = shelfbreak_dist
class NemoNavigationToolbar(NavigationToolbar):
""" This is custom toolbar for the nemo which includes additional buttons
for drawing tool and (add,remove) for mask in addtion to default NavigationToolbar
provided by matplotlib """
drawing_tool = pyqtSignal(str) #signal for the drawing tool changed
def __init__(self, canvas, parent):
""" Initialises the toolbar """
self.toolitems = (('Home', 'Reset original view', 'home', 'home'),\
('Back', 'Back to previous view', 'back', 'back'),\
('Forward', 'Forward to next view', 'forward', 'forward'),\
(None, None, None, None),\
('Pan', 'Pan axes with left mouse, zoom with right', 'move', 'pan'),\
('Zoom', 'Zoom to rectangle', 'zoom_to_rect', 'zoom'),\
('Reset', 'Reset the mask', 'reset','reset'),\
(None, None, None, None),\
('Freehand', 'Freehand drawing', 'freehand', 'freehand'),\
('Rectangle', 'Rectangle drawing', 'rectangle', 'rectangle'),\
('Border', 'Border selection', 'border', 'border'),\
('plus', 'Add mask', 'add_mask', 'add_mask'),\
('minus', 'Remove mask', 'remove_mask', 'remove_mask'),\
(None, None, None, None),\
('Normal','Normal Mask','normal_mask','normal_mask'),\
('MaxDepth', 'Max Depth Mask', 'max_depth_mask', 'max_depth_mask'),\
('ShelfBreak','Shelf Break Mask','shelf_break_mask','shelf_break_mask'),\
(None, None, None, None)\
)
NavigationToolbar.__init__(self, canvas, parent)
self._actions['reset'].setIcon(set_icon('reset.png'))
self._actions['freehand'].setCheckable(True)
self._actions['freehand'].setIcon(set_icon('freehand.png'))
self._actions['rectangle'].setCheckable(True)
self._actions['rectangle'].setIcon(set_icon('rectangle.png'))
self._actions['border'].setIcon(set_icon('border.png'))
self._actions['add_mask'].setIcon(set_icon('plus.png'))
self._actions['remove_mask'].setIcon(set_icon('minus.png'))
self._actions['normal_mask'].setIcon((set_icon('all_mask.png')))
self._actions['normal_mask'].setCheckable(True)
self._actions['max_depth_mask'].setIcon((set_icon('max_depth.png')))
self._actions['max_depth_mask'].setCheckable(True)
self._actions['shelf_break_mask'].setIcon((set_icon('shelf_break.png')))
self._actions['shelf_break_mask'].setCheckable(True)
self.update_height_mask(0)
def reset(self, *dummy):
""" Callback for reset button clicked"""
self.parent.reset_mask()
def freehand(self, *dummy):
""" callback for freehand button clicked """
if self._actions['freehand'].isChecked() == True:
if self._active == "PAN":
self.pan()
elif self._active == "ZOOM":
self.zoom()
elif self._actions['rectangle'].isChecked() == True:
self._actions['rectangle'].setChecked(False)
self.drawing_tool.emit("") # clear the rectangle selector
self._active = None
self.drawing_tool.emit('freehand')
self._update_buttons_checked()
else:
self.drawing_tool.emit("")
def rectangle(self, *dummy):
""" callback for rectangel button clicked """
if self._actions['rectangle'].isChecked() == True:
if self._active == "PAN":
self.pan()
elif self._active == "ZOOM":
self.zoom()
elif self._actions['freehand'].isChecked() == True:
self._actions['freehand'].setChecked(False)
self.drawing_tool.emit("") # clear the freehand selector
self._active = None
self.drawing_tool.emit('rectangle')
self._update_buttons_checked()
else:
self.drawing_tool.emit("")
def border(self, *dummy):
""" callback for border button clicked """
self.parent.apply_border_mask()
def add_mask(self, *dummy):
""" callback for add mask button clicked """
self.parent.add_mask()
def remove_mask(self, *dummy):
""" callback for remove mask button clicked """
self.parent.remove_mask()
def get_active_button(self):
""" returns the current active button between freehand and rectangle"""
if self._actions['rectangle'].isChecked() == True:
return 'rectangle'
elif self._actions['freehand'].isChecked() == True:
return 'freehand'
return None
def normal_mask(self, *dummy):
""" enable the normal mask button """
self.update_height_mask(0)
def max_depth_mask(self, *dummy):
""" enables the minimum height mask """
self.update_height_mask(1)
def shelf_break_mask(self, *dummy):
""" enables the shelf break mask button """
self.update_height_mask(2)
def update_height_mask(self, btn_id):
""" update the height mask buttons in the interface """
self._actions['normal_mask'].setChecked(False)
self._actions['max_depth_mask'].setChecked(False)
self._actions['shelf_break_mask'].setChecked(False)
try:
self.parent.set_mask_type(btn_id)
except AttributeError:
pass
if btn_id == 0:
self._actions['normal_mask'].setChecked(True)
elif btn_id == 1:
self._actions['max_depth_mask'].setChecked(True)
elif btn_id == 2:
self._actions['shelf_break_mask'].setChecked(True)
def set_icon(name):
""" Creates an icon based on the file found in the module directory with input name"""
return QtGui.QIcon(os.path.join(os.path.dirname(__file__), name))
'''
Created on 12 Jan 2015
@author: Mr. Srikanth Nagella
'''
# pylint: disable=E1103
# pylint: disable=no-name-in-module
from netCDF4 import Dataset
from mpl_toolkits.basemap import Basemap, cm
import numpy as np
from .selection_editor import PolygonEditor, BoxEditor
import os.path
from PyQt4.QtCore import pyqtSignal, pyqtSlot, Qt
from nemo_bdy_mask import Mask
import logging
from PyQt4.QtGui import QSizePolicy
mask_alpha = 0.3
from PyQt4 import QtGui
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.path import Path
from matplotlib.transforms import Bbox
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import mynormalize
# pylint: disable=E1002
class DraggableColorbar(object):
def __init__(self, cbar, mappable):
self.cbar = cbar
self.mappable = mappable
self.press = None
self.cycle = sorted([i for i in dir(plt.cm) if hasattr(getattr(plt.cm,i),'N')])
self.index = self.cycle.index(cbar.get_cmap().name)
def connect(self):
"""connect to all the events we need"""
self.cidpress = self.cbar.patch.figure.canvas.mpl_connect(
'button_press_event', self.on_press)
self.cidrelease = self.cbar.patch.figure.canvas.mpl_connect(
'button_release_event', self.on_release)
self.cidmotion = self.cbar.patch.figure.canvas.mpl_connect(
'motion_notify_event', self.on_motion)
self.keypress = self.cbar.patch.figure.canvas.mpl_connect(
'key_press_event', self.key_press)
def on_press(self, event):
"""on button press we will see if the mouse is over us and store some data"""
if event.inaxes != self.cbar.ax: return
self.press = event.x, event.y
def key_press(self, event):
if event.key=='c':
self.index += 1
elif event.key=='C':
self.index -= 1
if self.index<0:
self.index = len(self.cycle)
elif self.index>=len(self.cycle):
self.index = 0
cmap = self.cycle[self.index]
self.cbar.set_cmap(cmap)
self.cbar.draw_all()
self.mappable.set_cmap(cmap)
self.mappable.get_axes().set_title(cmap)
self.cbar.patch.figure.canvas.draw()
def on_motion(self, event):
'on motion we will move the rect if the mouse is over us'
if self.press is None: return
if event.inaxes != self.cbar.ax: return
xprev, yprev = self.press
dx = event.x - xprev
dy = event.y - yprev
self.press = event.x,event.y
#print 'x0=%f, xpress=%f, event.xdata=%f, dx=%f, x0+dx=%f'%(x0, xpress, event.xdata, dx, x0+dx)
scale = self.cbar.norm.vmax - self.cbar.norm.vmin
perc = 0.03
if event.button==1:
self.cbar.norm.vmin -= (perc*scale)*np.sign(dy)
self.cbar.norm.vmax -= (perc*scale)*np.sign(dy)
elif event.button==3:
self.cbar.norm.vmin -= (perc*scale)*np.sign(dy)
self.cbar.norm.vmax += (perc*scale)*np.sign(dy)
self.cbar.draw_all()
self.mappable.set_norm(self.cbar.norm)
self.cbar.patch.figure.canvas.draw()
def on_release(self, event):
"""on release we reset the press data"""
self.press = None
self.mappable.set_norm(self.cbar.norm)
self.cbar.patch.figure.canvas.draw()
def disconnect(self):
"""disconnect all the stored connection ids"""
self.cbar.patch.figure.canvas.mpl_disconnect(self.cidpress)
self.cbar.patch.figure.canvas.mpl_disconnect(self.cidrelease)
self.cbar.patch.figure.canvas.mpl_disconnect(self.cidmotion)
class MatplotlibWidget(QtGui.QWidget):
"""This class is a QWidget for pyNEMO mask plot"""
min_depth = 200.0
shelfbreak_dist = 200.0
mask_type = 0
def __init__(self, parent=None, mask=None, min_depth = 200.0, shelfbreak_dist = 200.0,*args, **kwargs):
""" Initialises the mask, matplot and the navigation toolbar """
super(MatplotlibWidget, self).__init__(parent)
#QtGui.QWidget.__init__(self, parent)
self.figure = Figure(*args, **kwargs)
self.canvas = FigureCanvas(self.figure)
self.mask = mask
self.min_depth = min_depth
self.shelfbreak_dist = shelfbreak_dist
if self.mask is not None:
self.mask.min_depth = min_depth
self.mask.shelfbreak_dist = shelfbreak_dist
self.toolbar = NemoNavigationToolbar(self.canvas, self)
self.toolbar.locLabel.setMinimumWidth(100)
self.toolbar.locLabel.setMaximumWidth(170)
self.toolbar.locLabel.setSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed)
self.toolbar.locLabel.setAlignment(Qt.AlignLeft|Qt.AlignTop)
self.toolbar.drawing_tool.connect(self.drawing_tool_callback)
self.axes = self.figure.add_subplot(111)
self.cbar = None
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
self.setLayout(layout)
self._drawing_tool = None
self._drawing_tool_name = None
self.create_basemap()
@pyqtSlot(str)
def drawing_tool_callback(self, toolname):
""" callback for the drawing tool when the signal of change of drawing tool is
received"""
if self._drawing_tool_name != None and toolname == "": #if tool is disabled
self._drawing_tool.disable()
self._drawing_tool_name = None
self._drawing_tool = None
self.canvas.draw()
else:
self._drawing_tool_name = toolname
if self._drawing_tool_name == "freehand": #if freehand tool is enabled
self._drawing_tool = PolygonEditor(self.axes, self.canvas)
self.canvas.draw()
elif self._drawing_tool_name == "rectangle": #if rectange tool is enabled
self._drawing_tool = BoxEditor(self.axes, self.canvas)
self._drawing_tool.enable()
self.canvas.draw()
def create_basemap(self):
""" Draws the basemap and contour with mask information"""
if self.mask == None:
return
x = np.arange(0, self.mask.lon.shape[0])
y = np.arange(0, self.mask.lon.shape[1])
x_vals, y_vals = np.meshgrid(y, x)
Z = self.mask.bathy_data[...].astype(np.float64)
#Z[Z==0] = np.nan
Z = np.ma.masked_where(Z==0, Z)
cmap = plt.get_cmap('GnBu')
cmap.set_bad('0.0')
cmap.set_under('black',1.0)
cmap.set_over('black',1.0)
transcmap = plt.get_cmap('autumn')
transcmap.set_bad(alpha=0.5)
masklayer = np.ma.masked_where(self.mask.data==-1,self.mask.data)
cax = self.axes.pcolormesh(x_vals, y_vals, Z, cmap=cmap)#, extend='min')#cmap=plt.get_cmap('GnBu'))#cmap=cm.s3pcpn)
self.axes.contourf(x_vals, y_vals, masklayer, [-2, -1, 0, 1, 2], cmap=transcmap,\
alpha=mask_alpha)
zmin = np.amin(Z)
zmax = np.amax(Z)
if self.cbar is None:
# self.cbar.remove()
# self.cbar = self.figure.colorbar(cax,ticks=np.linspace(zmin,zmax,10),orientation='horizontal')
self.cbar = self.figure.colorbar(cax,ticks=np.linspace(zmin,zmax,10),orientation='horizontal')
self.cbar.set_norm(mynormalize.MyNormalize(vmin=zmin,vmax=zmax,stretch='linear'))
self.cbar.set_label("Bathymetry (units=%s)"%self.mask.data_units)
self.cbar = DraggableColorbar(self.cbar,cax)
self.cbar.connect()
self.canvas.draw()
def reset_mask(self):
if self.mask == None:
return
self.mask.reset_mask()
self.axes.clear()
self.create_basemap()
def add_mask(self):
""" adds the selected region in the drawing tool to the mask """
if self._drawing_tool_name != "" and self.mask != None:
if self._drawing_tool.polygon != None:
x = np.arange(0, self.mask.lon.shape[0])
y = np.arange(0, self.mask.lon.shape[1])
x_vals, y_vals = np.meshgrid(y, x)
grid = zip(x_vals.ravel(), y_vals.ravel())
self._drawing_tool.polygon.set_linewidth(1.0)
p_path = Path(self._drawing_tool.polygon.xy)
index = p_path.contains_points(grid)
index = index.reshape(self.mask.lon.shape)
xmin, ymin = np.min(self._drawing_tool.polygon.xy, axis=0)
xmax, ymax = np.max(self._drawing_tool.polygon.xy, axis=0)
self.mask.add_mask(index,[xmin,xmax,ymin,ymax])
self._drawing_tool.reset()
self.axes.clear()
self.create_basemap()
def remove_mask(self):
""" removes the selected region in the drawing tool from the mask """
if self._drawing_tool_name != "" and self.mask != None:
if self._drawing_tool.polygon != None:
x = np.arange(0, self.mask.lon.shape[0])
y = np.arange(0, self.mask.lon.shape[1])
x_vals, y_vals = np.meshgrid(y, x)
grid = zip(x_vals.ravel(), y_vals.ravel()) #check for the index
self._drawing_tool.polygon.set_linewidth(1.0)
p_path = Path(self._drawing_tool.polygon.xy)
index = p_path.contains_points(grid)
index = index.reshape(self.mask.lon.shape)
xmin, ymin = np.min(self._drawing_tool.polygon.xy, axis=0)
xmax, ymax = np.max(self._drawing_tool.polygon.xy, axis=0)
self.mask.remove_mask(index,[xmin,xmax,ymin,ymax])
self._drawing_tool.reset()
self.axes.clear()
self.create_basemap()
def apply_border_mask(self):
""" This applies an mask of given number of pixels at the border of the mask"""
pixels, ok_btn_pressed = QtGui.QInputDialog.getText(self, 'Mask: Border Input',
'Enter number of pixel of border \
to be added to mask:')
if ok_btn_pressed:
self.mask.apply_border_mask(int(pixels))
self.axes.clear()
self.create_basemap()
def set_mask_type(self,type):
""" Sets the mask type """
self.mask_type = type
self.mask.mask_type = type
@pyqtSlot(str, str)
def set_bathymetry_file(self, bathymetry_filename, mask_file):
""" Set the bathymetry file """
try:
self.mask = Mask(bathymetry_filename, mask_file, self.min_depth, self.shelfbreak_dist)
self.mask.mask_type = self.mask_type
self.create_basemap()
except RuntimeError:
pass # couldn't set the new file name
@pyqtSlot(str)
def save_mask_file(self, mask_file):
""" Save the mask data to mask_file """
if self.mask is not None:
self.mask.save_mask(mask_file)
@pyqtSlot(float, float)
def set_mask_settings(self, min_depth, shelfbreak_dist):
""" Mask settings update """
self.min_depth = min_depth
self.shelfbreak_dist = shelfbreak_dist
self.mask.min_depth = min_depth
self.mask.shelfbreak_dist = shelfbreak_dist
class NemoNavigationToolbar(NavigationToolbar):
""" This is custom toolbar for the nemo which includes additional buttons
for drawing tool and (add,remove) for mask in addtion to default NavigationToolbar
provided by matplotlib """
drawing_tool = pyqtSignal(str) #signal for the drawing tool changed
def __init__(self, canvas, parent):
""" Initialises the toolbar """
self.toolitems = (('Home', 'Reset original view', 'home', 'home'),\
('Back', 'Back to previous view', 'back', 'back'),\
('Forward', 'Forward to next view', 'forward', 'forward'),\
(None, None, None, None),\
('Pan', 'Pan axes with left mouse, zoom with right', 'move', 'pan'),\
('Zoom', 'Zoom to rectangle', 'zoom_to_rect', 'zoom'),\
('Reset', 'Reset the mask', 'reset','reset'),\
(None, None, None, None),\
('Freehand', 'Freehand drawing', 'freehand', 'freehand'),\
('Rectangle', 'Rectangle drawing', 'rectangle', 'rectangle'),\
('Border', 'Border selection', 'border', 'border'),\
('plus', 'Add mask', 'add_mask', 'add_mask'),\
('minus', 'Remove mask', 'remove_mask', 'remove_mask'),\
(None, None, None, None),\
('Normal','Normal Mask','normal_mask','normal_mask'),\
('MaxDepth', 'Max Depth Mask', 'max_depth_mask', 'max_depth_mask'),\
('ShelfBreak','Shelf Break Mask','shelf_break_mask','shelf_break_mask'),\
(None, None, None, None)\
)
NavigationToolbar.__init__(self, canvas, parent)
self._actions['reset'].setIcon(set_icon('reset.png'))
self._actions['freehand'].setCheckable(True)
self._actions['freehand'].setIcon(set_icon('freehand.png'))
self._actions['rectangle'].setCheckable(True)
self._actions['rectangle'].setIcon(set_icon('rectangle.png'))
self._actions['border'].setIcon(set_icon('border.png'))
self._actions['add_mask'].setIcon(set_icon('plus.png'))
self._actions['remove_mask'].setIcon(set_icon('minus.png'))
self._actions['normal_mask'].setIcon((set_icon('all_mask.png')))
self._actions['normal_mask'].setCheckable(True)
self._actions['max_depth_mask'].setIcon((set_icon('max_depth.png')))
self._actions['max_depth_mask'].setCheckable(True)
self._actions['shelf_break_mask'].setIcon((set_icon('shelf_break.png')))
self._actions['shelf_break_mask'].setCheckable(True)
self.update_height_mask(0)
def reset(self, *dummy):
""" Callback for reset button clicked"""
self.parent.reset_mask()
def freehand(self, *dummy):
""" callback for freehand button clicked """
if self._actions['freehand'].isChecked() == True:
if self._active == "PAN":
self.pan()
elif self._active == "ZOOM":
self.zoom()
elif self._actions['rectangle'].isChecked() == True:
self._actions['rectangle'].setChecked(False)
self.drawing_tool.emit("") # clear the rectangle selector
self._active = None
self.drawing_tool.emit('freehand')
self._update_buttons_checked()
else:
self.drawing_tool.emit("")
def rectangle(self, *dummy):
""" callback for rectangel button clicked """
if self._actions['rectangle'].isChecked() == True:
if self._active == "PAN":
self.pan()
elif self._active == "ZOOM":
self.zoom()
elif self._actions['freehand'].isChecked() == True:
self._actions['freehand'].setChecked(False)
self.drawing_tool.emit("") # clear the freehand selector
self._active = None
self.drawing_tool.emit('rectangle')
self._update_buttons_checked()
else:
self.drawing_tool.emit("")
def border(self, *dummy):
""" callback for border button clicked """
self.parent.apply_border_mask()
def add_mask(self, *dummy):
""" callback for add mask button clicked """
self.parent.add_mask()
def remove_mask(self, *dummy):
""" callback for remove mask button clicked """
self.parent.remove_mask()
def get_active_button(self):
""" returns the current active button between freehand and rectangle"""
if self._actions['rectangle'].isChecked() == True:
return 'rectangle'
elif self._actions['freehand'].isChecked() == True:
return 'freehand'
return None
def normal_mask(self, *dummy):
""" enable the normal mask button """
self.update_height_mask(0)
def max_depth_mask(self, *dummy):
""" enables the minimum height mask """
self.update_height_mask(1)
def shelf_break_mask(self, *dummy):
""" enables the shelf break mask button """
self.update_height_mask(2)
def update_height_mask(self, btn_id):
""" update the height mask buttons in the interface """
self._actions['normal_mask'].setChecked(False)
self._actions['max_depth_mask'].setChecked(False)
self._actions['shelf_break_mask'].setChecked(False)
try:
self.parent.set_mask_type(btn_id)
except AttributeError:
pass
if btn_id == 0:
self._actions['normal_mask'].setChecked(True)
elif btn_id == 1:
self._actions['max_depth_mask'].setChecked(True)
elif btn_id == 2:
self._actions['shelf_break_mask'].setChecked(True)
def set_icon(name):
""" Creates an icon based on the file found in the module directory with input name"""
return QtGui.QIcon(os.path.join(os.path.dirname(__file__), name))
'''
Editor for namelist.bdy file
@author: Mr. Srikanth Nagella
'''
# pylint: disable=E1103
# pylint: disable=no-name-in-module
# pylint: disable=E1002
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import pyqtSignal, Qt, QRect, QPoint
import ast
from PyQt4.QtGui import QMessageBox, QRegion, QIcon, QToolTip, QCursor
class NameListEditor(QtGui.QWidget):
'''
This class creates a gui for the Namelist file options
'''
new_settings = {} #temporary variable to store the settings as they are changed in the GUI
bathymetry_update = pyqtSignal(str,str) #fires when there are changes to the settings
mask_update = pyqtSignal(str) #fires when there mask data to be saved is fired
mask_settings_update = pyqtSignal(float, float) #fires when there is mask settings update
def __init__(self, setup):
'''
Constructor for setting up the gui using the settings
'''
super(NameListEditor, self).__init__()
self.settings = setup.settings
self.bool_settings = setup.bool_settings
self.setup = setup
self.init_ui()
def init_ui(self):
'''
Initialises the UI components of the GUI
'''
client = QtGui.QWidget(self)
# Create the Layout to Grid
grid = QtGui.QGridLayout()
# Loop through the settings and create widgets for each setting
index = 0
for setting in self.settings:
# initialises setting Widget
label = QtGui.QLabel(setting)
qlabel = QtGui.QPushButton("")
qlabel.setIcon(self.style().standardIcon(QtGui.QStyle.SP_MessageBoxQuestion))
if type(self.settings[setting]).__name__ in ['str', 'float', 'double',
'int', 'time', 'dict']:
text = QtGui.QLineEdit(self)
text.setText(str(self.settings[setting]))
text.textChanged.connect(lambda value=setting,\
var_name=setting: self.label_changed(value, var_name))
if self.bool_settings.has_key(setting):
chkbox = QtGui.QCheckBox(self)
chkbox.setChecked(self.bool_settings[setting])
chkbox.stateChanged.connect(lambda value=setting,\
var_name=setting:\
self.state_changed(value, var_name))
grid.addWidget(chkbox, index, 0)
elif type(self.settings[setting]).__name__ == 'bool':
text = QtGui.QComboBox(self)
text.insertItem(0, 'True')
text.insertItem(1, 'False')
if self.settings[setting]:
text.setCurrentIndex(0)
else:
text.setCurrentIndex(1)
text.currentIndexChanged.connect(lambda value=setting,\
var_name=setting:\
self.combo_index_changed(value, var_name))
grid.addWidget(label, index, 1)
grid.addWidget(text, index, 2)
qlabel.clicked.connect(lambda widget=qlabel,\
str_val=self.setup.variable_info[setting]:\
QToolTip.showText(QCursor.pos(),str_val))
grid.addWidget(qlabel,index, 3)
if setting in self.setup.variable_info:
qlabel.setToolTip(self.setup.variable_info[setting])
index = index+1
client.setLayout(grid)
#scrollbars
scroll_area = QtGui.QScrollArea(self)
#scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll_area.setWidget(client)
#save cancel buttons
btn_widget = QtGui.QWidget(self)
hbox_layout = QtGui.QHBoxLayout(self)
btn_save = QtGui.QPushButton('Save')
btn_save.clicked.connect(self._btn_save_callback)
self.btn_cancel = QtGui.QPushButton('Close')
self.btn_cancel.clicked.connect(self._btn_cancel_callback)
hbox_layout.addWidget(btn_save)
hbox_layout.addWidget(self.btn_cancel)
btn_widget.setLayout(hbox_layout)
box_layout = QtGui.QVBoxLayout(self)
box_layout.addWidget(scroll_area)
box_layout.addWidget(btn_widget)
btn_widget.setMaximumWidth(400)
scroll_area.setMaximumWidth(400)
self.setLayout(box_layout)
#show the window
self.show()
def label_changed(self, value, name):
""" callback when the text is changed in the text box"""
self.new_settings[name] = unicode(value).encode('utf_8')
def combo_index_changed(self, value, name):
""" callback when the True/False drop down for the settings which has boolean value
is changed"""
if value == 0:
self.new_settings[name] = True
else:
self.new_settings[name] = False
def state_changed(self, state, name):
""" callback when the check box state is changed. This updates the bool_setting """
if state == QtCore.Qt.Checked:
self.bool_settings[name] = True
else:
self.bool_settings[name] = False
def _btn_save_callback(self):
""" callback when save button is clicked. this method writes takes the settings values in
GUI and write them back to file."""
#copy the the modified values to settings and call the setup save
for setting in self.new_settings:
if (type(self.settings[setting]).__name__ == 'dict') & \
(type(self.new_settings[setting]).__name__ != 'dict'):
self.new_settings[setting] = ast.literal_eval(self.new_settings[setting])
self.settings[setting] = self.new_settings[setting]
self.setup.settings = self.settings
try:
self.setup.write() #write settings back to file
QMessageBox.information(self,"pyNEMO","Setting saved to file")
except:
QMessageBox.information(self,"pyNEMO", "Error while saving the settings file, please check the permissions")
try:
#only emit the saving of mask file if the mask file name is set and boolean value is set
if self.settings['mask_file'] is not None and self.bool_settings['mask_file']:
self.mask_update.emit(self.settings['mask_file'])
except KeyError:
QMessageBox.information(self,"pyNEMO","Set mask_file key in the setting .bdy file")
try:
self.mask_settings_update.emit(float(self.settings['mask_max_depth']), float(self.settings['mask_shelfbreak_dist']))
except KeyError:
print 'Set the mask setting mask_max_depth and mask_shelfbreak_dist'
if self.bool_settings['mask_file']:
self.bathymetry_update.emit(self.settings['bathy'],self.settings['mask_file'])
def _btn_cancel_callback(self):
""" callback when cancel button is clicked """
self.close()
'''
Created on 6 Aug 2015
@author: Shirley Crompton, UK Science and Technology Facilities Council
'''
import logging
import os
import xml.etree.ElementTree as ET
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import pyqtSlot
import nemo_ncml_tab_widget
from thredds_crawler.crawl import Crawl
class Ncml_generator(QtGui.QDialog):
'''
Gui editor to capture user input for the purpose of generating NCML representation of pynemo source datasets.
'''
def __init__(self, basefile):
'''
Initialises the UI components
'''
super(Ncml_generator, self).__init__() # no params yet, may be allow user to predefine an input ncml for edit????
#Logging for class
self.logger = logging.getLogger(__name__) #logger config'ed in pynemo_exe.py
if not basefile:
testpath, file_name = os.path.split(__file__)
self.baseFile = os.path.join(testpath,'base.ncml')
else:
self.baseFile = basefile
print 'ncml baseFile : ', str(self.baseFile)
self.filename = None # store the output file pointer
self.initUI()
def initUI(self):
QtGui.QToolTip.setFont(QtGui.QFont('SansSerif', 11))
'''
vbox is the top container
'''
#the
vbox = QtGui.QVBoxLayout(self)
vbox.setSpacing(10)
vbox.setContentsMargins(10, 10, 5, 5)
'''
top panel for output file
'''
top_outfile_label = QtGui.QLabel(unicode('Output filename').encode('utf-8'))
self.top_outfile_name = QtGui.QLineEdit() #location is pre-defined
self.top_outfile_name.setToolTip(unicode('Define output file').encode('utf-8'))
self.top_outfile_name.returnPressed.connect(self.get_fname_input)
top_outfile_button = QtGui.QPushButton(unicode('Select file').encode('utf-8'))
top_outfile_button.clicked.connect(self.get_fname)
top_grpBox = QtGui.QGroupBox(unicode('Define output file').encode('utf-8'), None)
top_grid = QtGui.QGridLayout(top_grpBox)
top_grid.setVerticalSpacing(5)
top_grid.setHorizontalSpacing(10)
top_grid.addWidget(top_outfile_label, 1, 0)
top_grid.addWidget(self.top_outfile_name, 1, 1)
top_grid.addWidget(top_outfile_button, 1,2, QtCore.Qt.AlignRight)
'''
middle panel for tab folder
'''
self.tabWidget = QtGui.QTabWidget()
self.tracer_tab = nemo_ncml_tab_widget.Ncml_tab(unicode("Tracer").encode('utf-8'))
self.tracer_tab.setEnabled(False)
self.dynamic_tab = nemo_ncml_tab_widget.Ncml_tab(unicode("Dynamics").encode('utf-8'))
self.dynamic_tab.setEnabled(False)
self.ice_tab = nemo_ncml_tab_widget.Ncml_tab(unicode("Ice").encode('utf-8'))
self.ice_tab.setEnabled(False)
self.ecosys_tab = nemo_ncml_tab_widget.Ncml_tab(unicode("Ecosystem").encode('utf-8'))
self.ecosys_tab.setEnabled(False)
self.grid_tab = nemo_ncml_tab_widget.Ncml_tab(unicode("Grid").encode('utf-8'))
self.grid_tab.setEnabled(False)
self.tabWidget.addTab(self.tracer_tab, unicode("Tracer").encode('utf-8'))
self.tabWidget.addTab(self.dynamic_tab, unicode("Dynamics").encode('utf-8'))
self.tabWidget.addTab(self.ice_tab, unicode("Ice").encode('utf-8'))
self.tabWidget.addTab(self.ecosys_tab, unicode("Ecosystem").encode('utf-8')) # should be disabled
self.tabWidget.addTab(self.grid_tab, unicode("Grid").encode('utf-8')) # should be disabled
self.tabWidget.setMovable(False)
# if self.tabWidget.widget(self.tabWidget.currentIndex()).isEnabled() is True:
# self.connect(self.tabWidget, SIGNAL('currentChanged(int)'),self.enable_btn_update)
self.tabWidget.currentChanged.connect(lambda: self.enable_btn_update(enable_btn))
'''
button bar
'''
go_btn = QtGui.QPushButton(unicode('Generate').encode('utf-8'))
go_btn.setToolTip(unicode('Add all variable definitions before generating NcML file.').encode('utf-8'))
cancel_btn = QtGui.QPushButton(unicode('Cancel').encode('utf-8'))
enable_btn = QtGui.QPushButton(unicode('Enable Tab').encode('utf-8'))
#layout button bar
btn_hBox = QtGui.QHBoxLayout(None)
btn_hBox.setMargin(5)
btn_hBox.setSpacing(10)
btn_hBox.setAlignment(QtCore.Qt.AlignRight)
btn_hBox.addWidget(enable_btn)
btn_hBox.addWidget(cancel_btn)
btn_hBox.addWidget(go_btn)
go_btn.clicked.connect(self.generate)
cancel_btn.clicked.connect(self.close)
enable_btn.clicked.connect(lambda: self.enable_tab(enable_btn))
# enable_btn.clicked.connect(self.enable_tab)
'''
Assemble the top layout container
'''
vbox.addWidget(top_grpBox)
vbox.addWidget(self.tabWidget)
vbox.addLayout(btn_hBox)
#self.setLayout(grp_box)
self.setWindowIcon(QtGui.QIcon('/Users/jdha/anaconda/lib/python2.7/site-packages/pynemo-0.2-py2.7.egg/pynemo/gui/nemo_icon.png')) #doesn't work
self.setWindowTitle(unicode("PyNEMO NcML Generator").encode('utf-8'))
self.resize(650,300)
#has to change the default focus to stop the output file QTextedit to trigger the widget in focus when enter is pressed. Not sure why this happens???
self.tabWidget.setFocus()
#show the window
self.show()
'''
file picker call back for output file input field
'''
@pyqtSlot()
def get_fname(self):
# When you call getOpenFileName, a file picker dialog is created
# and if the user selects a file, it's path is returned, and if not
# (ie, the user cancels the operation) None is returned
fname = QtGui.QFileDialog.getSaveFileName(self, 'Select output file', '', selectedFilter='*.ncml')
if fname:
self.filename = fname #returns a QString
self.top_outfile_name.setText(str(fname))
#print 'the output file is set to : ' + self.filename
'''
output file text box call back handler
'''
@pyqtSlot()
def get_fname_input(self):
self.filename = self.top_outfile_name.text()
#print 'the output file is manually set to : ' + self.filename
'''
call back to handle the generate button pressed
'''
@pyqtSlot()
def enable_btn_update(self, enable_btn):
if self.tabWidget.widget(self.tabWidget.currentIndex()).isEnabled() is True:
enable_btn.setText(unicode('Disable Tab').encode('utf-8'))
else:
enable_btn.setText(unicode('Enable Tab').encode('utf-8'))
'''
call back to handle the generate button pressed
'''
@pyqtSlot()
def enable_tab(self,enable_btn):
# def enable_tab(self):
#validate output file
if self.tabWidget.widget(self.tabWidget.currentIndex()).isEnabled() is True:
self.tabWidget.widget(self.tabWidget.currentIndex()).setEnabled(False)
enable_btn.setText(unicode('Enable Tab').encode('utf-8'))
else:
self.tabWidget.widget(self.tabWidget.currentIndex()).setEnabled(True)
enable_btn.setText(unicode('Disable Tab').encode('utf-8'))
'''
call back to handle the generate button pressed
'''
@pyqtSlot()
def generate(self):
#validate output file
if self.filename is None or self.filename == "":
if self.top_outfile_name.text() is None or self.top_outfile_name.text() == "":
QtGui.QMessageBox.critical(self, unicode('Something is wrong').encode('utf-8'), unicode('No output file specified!').encode('utf-8'), QtGui.QMessageBox.Ok, QtGui.QMessageBox.Ok)
return
else:
self.filename = self.top_outfile_name.text()
if(os.path.exists(os.path.dirname(str(self.filename)))) == False:
#if os.path.dirname(os.path.dirname(os.path.exists(os.path.normpath(str(self.filename))))) == False:
QtGui.QMessageBox.critical(self, unicode('Something is wrong').encode('utf-8'), unicode('Invalid output directory! Cannot generate file!').encode('utf-8'), QtGui.QMessageBox.Ok, QtGui.QMessageBox.Ok)
#print 'invalid target directory! Cannot generate.'
return
#validate if all the variables are defined, use the mandatory src field as a proxy
# also need to check that the tab is active
tabsList = []
if self.tracer_tab.isEnabled() is True:
if self.tracer_tab.votemper.src != "" and \
self.tracer_tab.vosaline.src != "" :
tabsList.extend([self.tracer_tab.votemper, self.tracer_tab.vosaline])
else:
QtGui.QMessageBox.information(self, unicode('Something is wrong').encode('utf-8'), unicode('Not all the variables under the tracer tab have been defined!').encode('utf-8'), QtGui.QMessageBox.Ok, QtGui.QMessageBox.Ok)
if self.ice_tab.isEnabled() is True:
if self.ice_tab.ileadfra.src != "" and \
self.ice_tab.iicethic.src != "" and \
self.ice_tab.isnowthi.src != "" :
tabsList.extend([self.ice_tab.iicethic, self.ice_tab.ileadfra, self.ice_tab.isnowthi])
else:
QtGui.QMessageBox.information(self, unicode('Something is wrong').encode('utf-8'), unicode('Not all the variables under the ice tab have been defined!').encode('utf-8'), QtGui.QMessageBox.Ok, QtGui.QMessageBox.Ok)
if self.dynamic_tab.isEnabled() is True:
if self.dynamic_tab.vozocrtx.src != "" and \
self.dynamic_tab.vozocrtx.src != "" and \
self.dynamic_tab.sossheig.src != "" :
tabsList.extend([self.dynamic_tab.vozocrtx, self.dynamic_tab.vomecrty, self.dynamic_tab.sossheig])
else:
QtGui.QMessageBox.information(self, unicode('Something is wrong').encode('utf-8'), unicode('Not all the variables under the dynamics tab have been defined!').encode('utf-8'), QtGui.QMessageBox.Ok, QtGui.QMessageBox.Ok)
if self.grid_tab.isEnabled() is True:
if self.grid_tab.gdept.src != "" and \
self.grid_tab.gdepw.src != "" and \
self.grid_tab.mbathy.src != "" and \
self.grid_tab.e3t.src != "" and \
self.grid_tab.e3u.src != "" and \
self.grid_tab.e3v.src != "" :
tabsList.extend([self.grid_tab.gdept, self.grid_tab.gdepw, self.grid_tab.mbathy, self.grid_tab.e3t, self.grid_tab.e3u, self.grid_tab.e3v])
else:
QtGui.QMessageBox.information(self, unicode('Something is wrong').encode('utf-8'), unicode('Not all the variables under the grid tab have been defined!').encode('utf-8'), QtGui.QMessageBox.Ok, QtGui.QMessageBox.Ok)
try:
self.generateNcML(tabsList) #go ahead and do it
except:
raise
QtGui.QMessageBox.information(self, unicode('Success.').encode('utf-8'), unicode('NcML file generated.').encode('utf-8'), QtGui.QMessageBox.Ok, QtGui.QMessageBox.Ok)
'''
Function to generates the NcML text and write it to the user defined output file
'''
def generateNcML(self, tabsList):
#first open the default base file
ns = '{http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2}'
self.tree = self._parseNcml()
self.root = self.tree.getroot()
#create a netcdf element for each tab variable
for tab in tabsList:
netcdfE = ET.Element(ns+unicode('netcdf').encode('utf-8')) #src directory is converted to the correct format when added/
if str(tab.src).startswith("http:") or str(tab.src).startswith("https:"):
#Its url so use thredds crawler to get the urls
urls = self.url_trawler(tab.src,str(tab.regex))
aggE = ET.Element(ns+unicode('aggregation').encode('utf-8'), name=unicode(str(tab.name)).encode('utf-8'), type=unicode('joinExisting').encode('utf-8'), dimName=unicode('time_counter').encode('utf-8')) #tab.name already encoded
for nc_url in urls:
tcNetcdf = ET.Element(ns+unicode('netcdf').encode('utf-8'), location=unicode(str(nc_url)).encode('utf-8'))
aggE.append(tcNetcdf)
netcdfE.append(aggE)
else:
scanE = ET.Element(ns+unicode('scan').encode('utf-8'), location=unicode(str(tab.src)).encode('utf-8'), regExp=unicode(str(tab.regex)).encode('utf-8'))
if tab.subdirs == True:
scanE.set(unicode('subdirs').encode('utf-8'), unicode('true').encode('utf-8'))
aggE = ET.Element(ns+unicode('aggregation').encode('utf-8'), name=unicode(str(tab.name)).encode('utf-8'), type=unicode('joinExisting').encode('utf-8'), dimName=unicode('time_counter').encode('utf-8')) #tab.name already encoded
aggE.append(scanE)
netcdfE.append(aggE)
self.root[0].append(netcdfE) #add the new netcdf element to the top aggregation
#deal with variable name change TODO put this into a loop?
if tab.old_name is not None and tab.old_name != "":
vname = unicode('variable').encode('utf-8')
#v is None
if tab.name == unicode('temperature').encode('utf-8') and tab.old_name != unicode('votemper').encode('utf-8'):
v = ET.Element(ns+vname, name='votemper', orgName = str(tab.old_name))
self.root.append(v)
elif tab.name == unicode('salinity').encode('utf-8') and tab.old_name != unicode('vosaline').encode('utf-8'):
v = ET.Element(ns+vname, name='vosaline', orgName = str(tab.old_name))
self.root.append(v)
elif tab.name == unicode('ice_thickness').encode('utf-8') and tab.old_name != unicode('iicethic').encode('utf-8'):
v = ET.Element(ns+vname, name='iicethic', orgName = str(tab.old_name))
self.root.append(v)
elif tab.name == unicode('leads_fraction').encode('utf-8') and tab.old_name != unicode('ileadfra').encode('utf-8'):
v = ET.Element(ns+vname, name='ileadfra', orgName = str(tab.old_name))
self.root.append(v)
elif tab.name == unicode('snow_thickness').encode('utf-8') and tab.old_name != unicode('isnowthi').encode('utf-8'):
v = ET.Element(ns+vname, name='isnowthi', orgName = str(tab.old_name))
self.root.append(v)
elif tab.name == unicode('zonal_velocity').encode('utf-8') and tab.old_name != unicode('vozocrtx').encode('utf-8'):
v = ET.Element(ns+vname, name='vozocrtx', orgName = str(tab.old_name))
self.root.append(v)
elif tab.name == unicode('meridian_velocity').encode('utf-8') and tab.old_name != unicode('vomecrty').encode('utf-8'):
v = ET.Element(ns+vname, name='vomecrty', orgName = str(tab.old_name))
self.root.append(v)
elif tab.name == unicode('sea_surface_height').encode('utf-8') and tab.old_name != unicode('sossheig').encode('utf-8'):
v = ET.Element(ns+vname, name='sossheig', orgName = str(tab.old_name))
self.root.append(v)
elif tab.name == unicode('depth_at_t_points').encode('utf-8') and tab.old_name != unicode('gdept').encode('utf-8'):
v = ET.Element(ns+vname, name='gdept', orgName = str(tab.old_name))
self.root.append(v)
elif tab.name == unicode('depth_at_w_points').encode('utf-8') and tab.old_name != unicode('gdepw').encode('utf-8'):
v = ET.Element(ns+vname, name='gdepw', orgName = str(tab.old_name))
self.root.append(v)
elif tab.name == unicode('number_of_wet_levels').encode('utf-8') and tab.old_name != unicode('mbathy').encode('utf-8'):
v = ET.Element(ns+vname, name='mbathy', orgName = str(tab.old_name))
self.root.append(v)
elif tab.name == unicode('vertical_scale_factors_at_t_points').encode('utf-8') and tab.old_name != unicode('e3t').encode('utf-8'):
v = ET.Element(ns+vname, name='e3t', orgName = str(tab.old_name))
self.root.append(v)
elif tab.name == unicode('vertical_scale_factors_at_u_points').encode('utf-8') and tab.old_name != unicode('e3u').encode('utf-8'):
v = ET.Element(ns+vname, name='e3u', orgName = str(tab.old_name))
self.root.append(v)
elif tab.name == unicode('vertical_scale_factors_at_v_points').encode('utf-8') and tab.old_name != unicode('e3v').encode('utf-8'):
v = ET.Element(ns+vname, name='e3v', orgName = str(tab.old_name))
self.root.append(v)
#write ncml to file
try:
self.indent(self.root, 0) #24Aug15 format the xml for pretty printing
self.tree.write(self.filename, encoding='utf-8')
except IOError as (errno, strerror):
self.logger.error("I/O error({0}): {1}".format(errno, strerror))
except:
self.logger.error('Error generating ncml file')
raise
'''
Function to retrieve the NcML file template
'''
def _parseNcml(self):
try:
parser = ET.XMLParser(encoding="utf-8")
tree = ET.parse(self.baseFile, parser=parser)
return tree
except ET.ParseError, v:
row, column = v.position
print "error on row", row, "column", column, ":", v
'''
Function to format xml. Based on code provided by http://effbot.org/zone/element-lib
'''
def indent(self, elem, level=0):
i = "\n" + level*" "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
self.indent(elem, level+1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
"""
This method trawls throught the url with a given expression and returns the
list of urls that match the expression
"""
def url_trawler(self, url, expr):
if url.endswith(".xml"):
c = Crawl(url, select=[expr])
elif url.endswith("/"): # we'll try and add catalog.xml as the user may have just provided a directory
c = Crawl(url+"catalog.xml", select=[expr])
else: # we'll try and add catalog.xml as the user may have just provided a directory
c = Crawl(url+"/catalog.xml", select=[expr])
urls = [s.get("url") for d in c.datasets for s in d.services if s.get("service").lower()=="opendap"]
return urls
This diff is collapsed.
pynemo/gui/plus.png

1.71 KB

pynemo/gui/rectangle.png

164 Bytes

pynemo/gui/reset.png

1.32 KB

Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment