def _icon(self, name): if name.startswith(imgutils.RESSOURCE_PATH): img = QtGui.QImage() img.loadFromData( pkg_resources.resource_string(imgutils.__name__, name)) return QtGui.QIcon(QtGui.QPixmap.fromImage(img)) return NavigationToolbar._icon(self, name)
class PyWeramiWindow(QtWidgets.QMainWindow, Ui_MainWindow): def __init__(self, filename=None, parent=None): super(PyWeramiWindow, self).__init__(parent) self.settings = QtCore.QSettings("LX", "pywerami") self.setupUi(self) self._fig = Figure(facecolor="white") self._ax = self._fig.add_subplot(111) self._canvas = FigureCanvas(self._fig) self._canvas.setParent(self.widget) self._canvas.setFocusPolicy(QtCore.Qt.StrongFocus) self.matplot.addWidget(self._canvas) self.mpl_toolbar = NavigationToolbar(self._canvas, self.widget) self.mpl_toolbar.hide() self.matplot.addWidget(self.mpl_toolbar) self.setWindowTitle('PyWerami') window_icon = resource_filename(__name__, 'images/pywerami.png') self.setWindowIcon(QtGui.QIcon(window_icon)) self.about_dialog = AboutDialog(__version__) #set combos self.cmaps = [ 'viridis', 'inferno', 'plasma', 'magma', 'Blues', 'BuGn', 'BuPu', 'GnBu', 'Greens', 'Greys', 'Oranges', 'OrRd', 'PuBu', 'PuBuGn', 'PuRd', 'Purples', 'RdPu', 'Reds', 'YlGn', 'YlGnBu', 'YlOrBr', 'YlOrRd', 'afmhot', 'autumn', 'bone', 'cool', 'copper', 'gist_heat', 'gray', 'hot', 'pink', 'spring', 'summer', 'winter', 'BrBG', 'bwr', 'coolwarm', 'PiYG', 'PRGn', 'PuOr', 'RdBu', 'RdGy', 'RdYlBu', 'RdYlGn', 'Spectral', 'seismic', 'gist_earth', 'terrain', 'ocean', 'gist_stern', 'brg', 'CMRmap', 'cubehelix', 'gnuplot', 'gnuplot2', 'gist_ncar', 'nipy_spectral', 'jet', 'rainbow', 'gist_rainbow', 'hsv', 'flag', 'prism' ] self.mapstyle.addItems(self.cmaps) # set validators self.levelmin.setValidator(QtGui.QDoubleValidator(self.levelmin)) self.levelmax.setValidator(QtGui.QDoubleValidator(self.levelmax)) self.levelnum.setValidator(QtGui.QIntValidator(self.levelnum)) self.levelstep.setValidator(QtGui.QDoubleValidator(self.levelstep)) self.clipmin.setValidator(QtGui.QDoubleValidator(self.clipmin)) self.clipmax.setValidator(QtGui.QDoubleValidator(self.clipmax)) # Set icons in toolbar self.actionOpen.setIcon(QtGui.QIcon.fromTheme('document-open')) self.actionSave.setIcon(QtGui.QIcon.fromTheme('document-save')) self.actionSaveas.setIcon(QtGui.QIcon.fromTheme('document-save-as')) self.actionImport.setIcon( QtGui.QIcon.fromTheme('x-office-spreadsheet')) self.actionHome.setIcon(self.mpl_toolbar._icon('home.png')) self.actionPan.setIcon(self.mpl_toolbar._icon('move.png')) self.actionZoom.setIcon(self.mpl_toolbar._icon('zoom_to_rect.png')) self.actionGrid.setIcon(QtGui.QIcon.fromTheme('format-justify-fill')) self.actionAxes.setIcon( self.mpl_toolbar._icon('qt4_editor_options.png')) self.actionSavefig.setIcon(self.mpl_toolbar._icon('filesave.png')) #self.action3D.setIcon(QtGui.QIcon.fromTheme('')) self.actionProperties.setIcon( QtGui.QIcon.fromTheme('preferences-other')) self.actionQuit.setIcon(QtGui.QIcon.fromTheme('application-exit')) self.actionAbout.setIcon(QtGui.QIcon.fromTheme('help-about')) # connect signals self.actionOpen.triggered.connect(self.openProject) self.actionSave.triggered.connect(self.saveProject) self.actionSaveas.triggered.connect(self.saveProjectAs) self.actionImport.triggered.connect(self.import_data) self.actionHome.triggered.connect(self.mpl_toolbar.home) self.actionPan.triggered.connect(self.plotpan) self.actionZoom.triggered.connect(self.plotzoom) self.actionGrid.triggered.connect(self.plotgrid) self.actionAxes.triggered.connect(self.mpl_toolbar.edit_parameters) self.actionSavefig.triggered.connect(self.mpl_toolbar.save_figure) self.actionProperties.triggered.connect(self.edit_options) self.actionQuit.triggered.connect(self.close) self.actionAbout.triggered.connect(self.about_dialog.exec) # buttons signals self.buttonBox.button( QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.apply_props) self.buttonBox.button( QtWidgets.QDialogButtonBox.RestoreDefaults).clicked.connect( self.restore_props) self.contcolor.clicked.connect(self.contours_color) self.action3D.triggered.connect(self.switch3d) # signals to calculate step size self.levelmin.editingFinished.connect(self.step_from_levels) self.levelmax.editingFinished.connect(self.step_from_levels) self.levelnum.editingFinished.connect(self.step_from_levels) self.setlevels.toggled.connect(self.step_from_levels) # almost done self.ready = False self.changed = False self.project = None if filename: self.import_data(filename) # ready self.statusbar.showMessage("Ready", 5000) def closeEvent(self, event): if self.changed: quit_msg = 'Project have been changed. Save ?' qb = QtWidgets.QMessageBox reply = qb.question(self, 'Message', quit_msg, qb.Cancel | qb.Discard | qb.Save, qb.Save) if reply == qb.Save: self.do_save() if self.project is not None: event.accept() else: event.ignore() elif reply == qb.Discard: event.accept() else: event.ignore() def import_data(self, filename=None): if not filename: filename = QtWidgets.QFileDialog.getOpenFileName( self, "Import data file", ".", "Perple_X Table (*.tab *.TAB);;TCInvestigator (*.tci *.TCI)" )[0] if filename: if filename.lower().endswith('.tab'): self.data = GridData.from_tab(filename) elif filename.lower().endswith('.tci'): self.data = GridData.from_tci(filename) else: raise Exception('Unsupported file format') # populate listview and setup properties self.datafilename = filename self.ready = True self.project = None self.changed = True self.props = {} self._model = QtGui.QStandardItemModel(self.listView) for var in self.data.dep: item = QtGui.QStandardItem(var) item.setCheckable(True) self._model.appendRow(item) self.default_var_props(var) self.listView.setModel(self._model) self.listView.show() # connect listview signals self.varSel = self.listView.selectionModel() try: elf.varSel.selectionChanged.disconnect() except Exception: pass self.varSel.selectionChanged.connect(self.on_var_changed) try: self._model.itemChanged.disconnect() except Exception: pass self._model.itemChanged.connect(self.plot) # all done focus self.action3D.setChecked(False) # no 3d on import self.varSel.setCurrentIndex( self._model.index(0, 0), QtCore.QItemSelectionModel.ClearAndSelect | QtCore.QItemSelectionModel.Rows) self.listView.setFocus() self.plot() self.statusbar.showMessage( "Data from {} imported".format(self.data.label), 5000) def openProject(self, checked, projfile=None): """Open pywerami project """ if self.changed: quit_msg = 'Project have been changed. Save ?' qb = QtWidgets.QMessageBox reply = qb.question(self, 'Message', quit_msg, qb.Discard | qb.Save, qb.Save) if reply == qb.Save: self.do_save() if projfile is None: qd = QtWidgets.QFileDialog filt = 'pywermi project (*.pwp)' projfile = qd.getOpenFileName(self, 'Open project', os.path.expanduser('~'), filt)[0] if os.path.exists(projfile): stream = gzip.open(projfile, 'rb') data = pickle.load(stream) stream.close() # set actual working dir in case folder was moved self.datafilename = data['datafilename'] self.import_data(self.datafilename) self.props = data['props'] # all done self.ready = True self.project = projfile self.changed = False # all done focus self.action3D.setChecked(False) # no 3d on import self.varSel.setCurrentIndex( self._model.index(0, 0), QtCore.QItemSelectionModel.ClearAndSelect | QtCore.QItemSelectionModel.Rows) self.listView.setFocus() self.plot() self.statusbar.showMessage("Project loaded.", 5000) def saveProject(self): """Save project """ if self.ready: if self.project is None: filename = QtWidgets.QFileDialog.getSaveFileName( self, 'Save current project', os.path.dirname(self.datafilename), 'pywerami project (*.pwp)')[0] if filename: if not filename.lower().endswith('.pwp'): filename = filename + '.pwp' self.project = filename self.do_save() else: self.do_save() def saveProjectAs(self): """Save project as """ if self.ready: filename = QtWidgets.QFileDialog.getSaveFileName( self, 'Save current project as', os.path.dirname(self.datafilename), 'pywerami project (*.pwp)')[0] if filename: if not filename.lower().endswith('.pwp'): filename = filename + '.pwp' self.project = filename self.do_save() def do_save(self): """Do saving of poject """ if self.project: # put to dict data = {'datafilename': self.datafilename, 'props': self.props} # do save stream = gzip.open(self.project, 'wb') pickle.dump(data, stream) stream.close() self.changed = False self.statusBar().showMessage('Project saved.') def contours_color(self): if self.ready: col = QtWidgets.QColorDialog.getColor() if col.isValid(): self.contcolor.setStyleSheet("background-color: {}".format( col.name())) def step_from_levels(self): if self.ready: if int(self.levelnum.text()) < 2: self.levelnum.setText('2') if float(self.levelmax.text()) < float(self.levelmin.text()): self.levelmin.setText(self.levelmax.text()) if self.setlevels.isChecked(): step = (float(self.levelmax.text()) - float( self.levelmin.text())) / (int(self.levelnum.text()) - 1) self.levelstep.setText(repr(step)) self.props[self.var]['step'] = step self.changed = True def default_var_props(self, var): if self.ready: data = self.data.get_var(var) prop = {} #levels prop['min'] = data.min() prop['max'] = data.max() prop['num'] = 10 prop['step'] = (prop['max'] - prop['min']) / (prop['num'] - 1) prop['levels'] = 'num' prop['type'] = 'linear' #style prop['fill'] = False prop['opacity'] = 100 prop['cmap'] = 'viridis' prop['contours'] = 'color' prop['color'] = '#000000' prop['label'] = False prop['digits'] = 3 #processing prop['resample'] = 1 prop['median'] = 1 prop['gauss'] = 0 prop['clipmin'] = data.min() prop['clipmax'] = data.max() self.props[var] = prop def set_var_props(self, var): if self.ready: #levels self.levelmin.setText(repr(self.props[var]['min'])) self.levelmax.setText(repr(self.props[var]['max'])) self.levelnum.setText(repr(self.props[var]['num'])) self.levelstep.setText(repr(self.props[var]['step'])) if self.props[var]['levels'] == 'num': self.setlevels.setChecked(True) else: self.setstep.setChecked(True) if self.props[var]['type'] == 'linear': self.linlevel.setChecked(True) else: self.cdflevel.setChecked(True) #style if self.props[var]['fill']: self.fillstyle.setChecked(True) else: self.fillstyle.setChecked(False) self.opacity.setValue(self.props[var]['opacity']) self.mapstyle.setCurrentIndex( self.cmaps.index(self.props[var]['cmap'])) self.contcolor.setStyleSheet("background-color: {}".format( self.props[var]['color'])) self.labelDigits.setValue(self.props[var]['digits']) if self.props[var]['contours'] == 'map': self.contcheckmap.setChecked(True) elif self.props[var]['contours'] == 'color': self.contcheckcolor.setChecked(True) else: self.contchecknone.setChecked(True) if self.props[var]['label']: self.contlabel.setChecked(True) else: self.contlabel.setChecked(False) #processing self.resample.setValue(self.props[var]['resample']) self.filtersize.setValue(self.props[var]['median']) self.filtersigma.setValue(self.props[var]['gauss']) self.clipmin.setText(repr(self.props[var]['clipmin'])) self.clipmax.setText(repr(self.props[var]['clipmax'])) def on_var_changed(self, selected): if self.ready: self.var = self.data.dep[selected.indexes()[0].row()] self.set_var_props(self.var) if self.action3D.isChecked(): self.plot() def apply_props(self): if self.ready: #levels self.props[self.var]['min'] = float(self.levelmin.text()) self.props[self.var]['max'] = float(self.levelmax.text()) self.props[self.var]['num'] = int(self.levelnum.text()) self.props[self.var]['step'] = float(self.levelstep.text()) if self.setlevels.isChecked(): self.props[self.var]['levels'] = 'num' else: self.props[self.var]['levels'] = 'step' if self.linlevel.isChecked(): self.props[self.var]['type'] = 'linear' else: self.props[self.var]['type'] = 'cdf' #style if self.fillstyle.isChecked(): self.props[self.var]['fill'] = True else: self.props[self.var]['fill'] = False self.props[self.var]['opacity'] = self.opacity.value() self.props[self.var]['cmap'] = str(self.mapstyle.currentText()) self.props[self.var]['color'] = str( self.contcolor.palette().color(1).name()) self.props[self.var]['digits'] = self.labelDigits.value() if self.contcheckmap.isChecked(): self.props[self.var]['contours'] = 'map' elif self.contcheckcolor.isChecked(): self.props[self.var]['contours'] = 'color' else: self.props[self.var]['contours'] = '' if self.contlabel.isChecked(): self.props[self.var]['label'] = True else: self.props[self.var]['label'] = False #processing self.props[self.var]['resample'] = self.resample.value() self.props[self.var]['median'] = self.filtersize.value() self.props[self.var]['gauss'] = self.filtersigma.value() self.props[self.var]['clipmin'] = float(self.clipmin.text()) self.props[self.var]['clipmax'] = float(self.clipmax.text()) self.changed = True self.plot() def restore_props(self): if self.ready: self.default_var_props(self.var) self.set_var_props(self.var) self.plot() def edit_options(self): dlg = OptionsForm(self) dlg.exec_() def plotpan(self): self.actionZoom.setChecked(False) self.mpl_toolbar.pan() def plotzoom(self): self.actionPan.setChecked(False) self.mpl_toolbar.zoom() def plotgrid(self): self._ax.grid() self._canvas.draw() def switch3d(self): if self.ready: if not self.action3D.isChecked(): self._fig.clear() self._ax = self._fig.add_subplot(111) else: self._fig.clear() self._ax = self._fig.add_subplot(111, projection='3d') self.plot() def plot(self, item=None): if self.ready: self._ax.cla() if item: index = self._model.createIndex(item.row(), item.column()) if index.isValid(): self.listView.setCurrentIndex(index) if not self.action3D.isChecked(): extent = self.data.get_extent() i = 0 while self._model.item(i): if self._model.item(i).checkState(): CS = None var = str(self._model.item(i).text()) # get data, smooth and clip data = self.data.get_var(var, nan=np.float( self.settings.value( "nan", "NaN", type=str))) if self.props[var]['resample'] > 1: data = np.ma.array( ndimage.zoom(data.filled(0), self.props[var]['resample']), mask=ndimage.zoom(data.mask, self.props[var]['resample'], order=0)) if self.props[var]['median'] > 1: data = np.ma.array(ndimage.median_filter( data, size=self.props[var]['median'] * self.props[var]['resample']), mask=data.mask) if self.props[var]['gauss'] > 0: data = np.ma.array(ndimage.gaussian_filter( data, sigma=self.props[var]['gauss'] * self.props[var]['resample']), mask=data.mask) data = np.ma.masked_outside(data, self.props[var]['clipmin'], self.props[var]['clipmax']) if self.props[var]['fill']: self._ax.imshow( data, interpolation='none', origin='lower', extent=extent, aspect='auto', cmap=cm.get_cmap(self.props[var]['cmap']), alpha=self.props[var]['opacity'] / 100.0) if self.props[var]['min'] == self.props[var]['max']: clevels = np.array([self.props[var]['min']]) else: if self.props[var]['type'] == 'linear': if self.props[var]['levels'] == 'num': clevels = np.linspace( self.props[var]['min'], self.props[var]['max'], self.props[var]['num']) else: # trick to include max in levels clevels = np.arange( self.props[var]['min'], self.props[var]['max'] + np.finfo(np.float32).eps, self.props[var]['step']) else: # cdf based on histogram binned acording to the Freedman-Diaconis rule data = np.ma.masked_outside( data, self.props[var]['min'], self.props[var]['max']) v = np.sort(data.compressed()) IQR = v[int(round( (v.size - 1) * float(0.75)))] - v[int( round((v.size - 1) * float(0.25)))] bin_size = 2 * IQR * v.size**(-1.0 / 3) nbins = int( round( max(self.props[var]['num'], (v[-1] - v[0]) / (bin_size + 0.001)))) hist, bin_edges = np.histogram(v, bins=nbins) cdf = np.cumsum(hist) cdfx = np.cumsum(np.diff( bin_edges)) + bin_edges[:2].sum() / 2 #clevels = np.interp(np.linspace(cdf[0],cdf[-1],self.props[var]['num'] + 2)[1:-1], cdf, cdfx) clevels = np.interp( np.linspace(cdf[0], cdf[-1], self.props[var]['num']), cdf, cdfx) clevels = np.round( 10**self.props[var]['digits'] * clevels) / 10**self.props[var]['digits'] # Contour levels must be increasing clevels = np.append(clevels[:1], clevels[1:][np.diff(clevels) > 0]) if self.props[var]['contours'] == 'map': CS = self._ax.contour( self.data.get_xrange( self.props[var]['resample']), self.data.get_yrange( self.props[var]['resample']), data, clevels, cmap=cm.get_cmap(self.props[var]['cmap'])) elif self.props[var]['contours'] == 'color': CS = self._ax.contour( self.data.get_xrange( self.props[var]['resample']), self.data.get_yrange( self.props[var]['resample']), data, clevels, colors=self.props[var]['color']) if self.props[var]['label'] and CS: self._ax.clabel(CS, fontsize=8, inline=1, fmt='%g') i += 1 self._ax.axis(extent) self._ax.set_title(self.data.label) else: # get data, smooth and clip data = self.data.get_var(self.var) if self.props[self.var]['resample'] > 1: data = np.ma.array( ndimage.zoom(data.filled(0), self.props[self.var]['resample']), mask=ndimage.zoom(data.mask, self.props[self.var]['resample'], order=0)) if self.props[self.var]['median'] > 1: data = np.ma.array(ndimage.median_filter( data, size=self.props[self.var]['median'] * self.props[self.var]['resample']), mask=data.mask) if self.props[self.var]['gauss'] > 0: data = np.ma.array(ndimage.gaussian_filter( data, sigma=self.props[self.var]['gauss'] * self.props[self.var]['resample']), mask=data.mask) data = np.ma.masked_outside(data, self.props[self.var]['clipmin'], self.props[self.var]['clipmax']) x, y = np.meshgrid( self.data.get_xrange(self.props[self.var]['resample']), self.data.get_yrange(self.props[self.var]['resample'])) self._ax.plot_surface( x, y, data.filled(np.NaN), vmin=data.min(), vmax=data.max(), cmap=cm.get_cmap(self.props[self.var]['cmap']), linewidth=0.5, alpha=self.props[self.var]['opacity'] / 100.0) self._ax.view_init(azim=235, elev=30) self._ax.set_xlabel(self.data.ind[self.data.xvar]['name']) self._ax.set_ylabel(self.data.ind[self.data.yvar]['name']) self._fig.tight_layout() self._canvas.draw()
class MplWidget(QtWidgets.QWidget): """ Widget defined in Qt Designer. """ zoom_to_full_view = pyqtSignal() map_press = pyqtSignal(float, float) def __init__(self, parent = None): # initialization of Qt MainWindow widget QtWidgets.QWidget.__init__(self, parent) # set the canvas to the Matplotlib widget self.canvas = MplCanvas() self.ntb = NavigationToolbar(self.canvas, self) #self.ntb.removeAction(self.ntb.buttons[0]) self.ntb.clear() program_folder = os.path.join(os.path.dirname(__file__), "ims") a = self.ntb.addAction(self.ntb._icon(os.path.join(program_folder, "world.png")), 'Home', self.zoom2fullextent) a.setToolTip('Reset original view') a = self.ntb.addAction(self.ntb._icon(os.path.join(program_folder, "arrow_left.png")), 'Back', self.ntb.back) a.setToolTip('Back to previous view') a = self.ntb.addAction(self.ntb._icon(os.path.join(program_folder, "arrow_right.png")), 'Forward', self.ntb.forward) a.setToolTip('Forward to next view') a = self.ntb.addAction(self.ntb._icon(os.path.join(program_folder, "arrow_out.png")), 'Pan', self.ntb.pan) a.setToolTip('Pan axes with left mouse, zoom with right') a = self.ntb.addAction(self.ntb._icon(os.path.join(program_folder, "zoom.png")), 'Zoom', self.ntb.zoom) a.setToolTip('Zoom to rectangle') action_SetSrcPt = self.ntb.addAction(self.ntb._icon(os.path.join(program_folder, "bullet_red.png")), 'Source point', self.pt_map) action_SetSrcPt.setToolTip('Set source point in map') a = self.ntb.addAction(self.ntb._icon(os.path.join(program_folder, "camera.png")), 'Save', self.ntb.save_figure) a.setToolTip('Save map as image') a = self.ntb.addAction(self.ntb._icon(os.path.join(program_folder, "help.png")), 'Help', self.openHelp) a.setToolTip('Help') a = self.ntb.addAction(self.ntb._icon(os.path.join(program_folder, "information.png")), 'About', self.helpAbout) a.setToolTip('About') # create a vertical box layout self.vbl = QtWidgets.QVBoxLayout() # add widgets to the vertical box self.vbl.addWidget(self.canvas) self.vbl.addWidget(self.ntb) # set the layout to the vertical box self.setLayout(self.vbl) def onclick(self, event): """ Emit a signal to induce the update of source point location. @param event: press event. @type event: Matplotlib event. """ global set_srcpt_count, cid set_srcpt_count += 1 if set_srcpt_count == 1: self.map_press.emit(event.xdata, event.ydata) self.canvas.fig.canvas.mpl_disconnect(cid) def pt_map(self): """ Connect the press event with the function for updating the source point location. """ global set_srcpt_count, cid set_srcpt_count = 0 cid = self.canvas.fig.canvas.mpl_connect('button_press_event', self.onclick) def zoom2fullextent(self): """ Emit the signal for updating the map view to the extent of the DEM, in alternative of the shapefile, or at the standard extent. """ self.zoom_to_full_view.emit() def openHelp(self): """ Open an Help HTML file after CADTOOLS module in QG """ help_path = os.path.join(os.path.dirname(__file__), 'help', 'help.html') webbrowser.open(help_path) def helpAbout(self): """ Visualize an About window. """ QtWidgets.QMessageBox.about(self, "About gSurf", """ <p>gSurf<br />License: GPL v. 3</p> <p>This application calculates the intersection between a plane and a DEM in an interactive way. The result is a set of points/lines that can be saved as shapefiles. </p> <p>Report any bug to <a href="mailto:[email protected]">[email protected]</a></p> """)