Source code for tuiview.profilewindow

"""
Module that contains the ProfileDockWidget
"""

# This file is part of 'TuiView' - a simple Raster viewer
# Copyright (C) 2012  Sam Gillingham
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

from PyQt5.QtGui import QPen, QIcon
from PyQt5.QtWidgets import QDockWidget, QWidget, QToolBar, QVBoxLayout, QLabel
from PyQt5.QtWidgets import QAction
from PyQt5.QtCore import Qt, pyqtSignal, QLocale
import numpy

from . import plotwidget


[docs]class ProfileDockWidget(QDockWidget): """ Dockable window that is a combined profile and ruler """ # signals profileClosed = pyqtSignal(QDockWidget, name='profileClosed') "emitted when Window closed" def __init__(self, parent, viewwidget): QDockWidget.__init__(self, "Profile", parent) self.viewwidget = viewwidget # create a new widget that lives in the dock window self.dockWidget = QWidget() self.mainLayout = QVBoxLayout() self.toolBar = QToolBar(self.dockWidget) self.setupActions() self.setupToolbar() self.mainLayout.addWidget(self.toolBar) self.plotWidget = plotwidget.PlotLineWidget(self) self.mainLayout.addWidget(self.plotWidget) self.whitePen = QPen(Qt.white) self.whitePen.setWidth(1) self.redPen = QPen(Qt.red) self.redPen.setWidth(1) self.greenPen = QPen(Qt.green) self.greenPen.setWidth(1) self.bluePen = QPen(Qt.blue) self.bluePen.setWidth(1) self.distanceLabel = QLabel() self.mainLayout.addWidget(self.distanceLabel) self.dockWidget.setLayout(self.mainLayout) # tell the dock window this is the widget to display self.setWidget(self.dockWidget) self.resize(400, 200) # allow plot scaling to be changed by user # Min, Max. None means 'auto'. self.plotScaling = (None, None) # store the range of the data so we can init plot scale dlg self.dataRange = None # last polyLine so we can quickly resraw if plot scale changes self.lastPolyLine = None # connect if the layers have changed and we can close if our layer # no longer exists self.viewwidget.layers.layersChanged.connect(self.layersChanged)
[docs] def setupActions(self): """ Create the actions to be shown on the toolbar """ self.followAction = QAction(self) self.followAction.setText("&Follow Query Tool") self.followAction.setStatusTip("Follow Query Tool") self.followAction.setIcon(QIcon(":/viewer/images/profileruler.png")) self.followAction.setCheckable(True) self.followAction.setChecked(True) self.savePlotAction = QAction(self, triggered=self.savePlot) self.savePlotAction.setText("&Save Plot") self.savePlotAction.setStatusTip("Save Plot") self.savePlotAction.setIcon(QIcon(":/viewer/images/saveplot.png")) self.plotScalingAction = QAction(self, triggered=self.onPlotScaling) self.plotScalingAction.setText("Set Plot Scaling") self.plotScalingAction.setStatusTip("Set Plot Scaling") icon = QIcon(":/viewer/images/setplotscale.png") self.plotScalingAction.setIcon(icon)
[docs] def setupToolbar(self): """ Add the actions to the toolbar """ self.toolBar.addAction(self.followAction) self.toolBar.addAction(self.savePlotAction) self.toolBar.addAction(self.plotScalingAction)
[docs] def savePlot(self): """ Save the plot as a file. Either .pdf or .ps QPrinter chooses format based on extension. """ from PyQt5.QtPrintSupport import QPrinter from PyQt5.QtWidgets import QFileDialog from PyQt5.QtGui import QPainter fname, filter = QFileDialog.getSaveFileName(self, "Plot File", filter="PDF (*.pdf);;Postscript (*.ps)") if fname != '': printer = QPrinter() printer.setOrientation(QPrinter.Landscape) printer.setColorMode(QPrinter.Color) printer.setOutputFileName(fname) printer.setResolution(96) painter = QPainter() painter.begin(printer) self.plotWidget.render(painter) painter.end()
[docs] def plotProfile(self, xdata, ydata, mask, pen): """ Plot the xdata vs ydata. Use the mask to split the plot up so values only plotted where mask = True """ # get all the indices where the mask # changes from True to False and vice versa changeidx = numpy.diff(mask).nonzero()[0] # because diff() starts from the second element # we actually want the indices relative to the start changeidx = changeidx + 1 # go all the way to the end changeidx = numpy.append(changeidx, mask.size) lastidx = 0 # start at the beginning of the array for idx in changeidx: if mask[lastidx]: # we are in a run of True's xdatasub = xdata[lastidx:idx] ydatasub = ydata[lastidx:idx] curve = plotwidget.PlotCurve(xdatasub, ydatasub, pen) self.plotWidget.addCurve(curve) lastidx = idx
[docs] def newLine(self, polyLine): """ Widget has collected a new line """ if not self.followAction.isChecked(): return title = "Profile: %s" % polyLine.layer.title self.setWindowTitle(title) # get the info we need out of the PolylineToolInfo profiledata, profilemask, distance = polyLine.getProfile() # set the distance text with commas etc as defined # by the system locale txt = QLocale.system().toString(distance[-1]) fmt = "Total Distance: %s" % txt self.distanceLabel.setText(fmt) # get rid of curves from last time self.plotWidget.removeCurves() if isinstance(profiledata, list): # RGB penList = [self.redPen, self.greenPen, self.bluePen] minValue = None maxValue = None for data, pen in zip(profiledata, penList): # record the range of the data for scaling masked = numpy.compress(profilemask, data) if masked.size == 0: # can't do anything if no valid data continue bandMinValue = masked.min() bandMaxValue = masked.max() self.plotProfile(distance, data, profilemask, pen) if minValue is None or bandMinValue < bandMinValue: minValue = bandMinValue if maxValue is None or bandMaxValue > maxValue: maxValue = bandMaxValue else: # greyscale # find range of data for scaling masked = numpy.compress(profilemask, profiledata) if masked.size == 0: # can't do anything if no valid data return minValue = masked.min() maxValue = masked.max() pen = self.whitePen self.plotProfile(distance, profiledata, profilemask, pen) self.dataRange = (minValue, maxValue) # include total distance in case start or end off image self.plotWidget.setXRange(0, distance[-1]) # set scaling if needed minScale, maxScale = self.plotScaling if minScale is None and maxScale is None: # set back to auto self.plotWidget.setYRange() else: # we need to provide both min and max so # derive from data if needed if minScale is None: minScale = minValue if maxScale is None: maxScale = maxValue self.plotWidget.setYRange(minScale, maxScale) self.lastPolyLine = polyLine
[docs] def onPlotScaling(self): """ Allows the user to change the Y axis scaling of the plot """ from .plotscalingdialog import PlotScalingDialog if self.dataRange is not None: minValue, maxValue = self.dataRange # should inherit type of minValue etc # which should be the same as the data array data = numpy.array([minValue, maxValue]) else: # uint8 default if no data data = numpy.array([0, 1], dtype=numpy.uint8) dlg = PlotScalingDialog(self, self.plotScaling, data) if dlg.exec_() == PlotScalingDialog.Accepted: self.plotScaling = dlg.getScale() if self.lastPolyLine is not None: self.newLine(self.lastPolyLine)
[docs] def closeEvent(self, event): """ Window is being closed - inform parent window """ self.profileClosed.emit(self)
[docs] def layersChanged(self): """ Layers have changed - if polyLine.layer no longer in our list of layers then close this window. """ if self.lastPolyLine is not None: if self.lastPolyLine.layer not in self.viewwidget.layers.layers: # remove reference self.lastPolyLine = None # close window self.close()