Newer
Older
# -*- coding: utf-8 -*-
"""
/***************************************************************************
HUB TimeSeriesViewer
-------------------
begin : 2017-08-04
git sha : $Format:%H$
copyright : (C) 2017 by HU-Berlin
email : benjamin.jakimow@geo.hu-berlin.de
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
"""
# noinspection PyPep8Naming
from __future__ import absolute_import
import os, sys, pickle, datetime
from qgis.gui import *
from qgis.core import *
from PyQt4.QtCore import *
from PyQt4.QtXml import *
from PyQt4.QtGui import *
from timeseriesviewer import jp, SETTINGS
from timeseriesviewer.timeseries import *
from timeseriesviewer.utils import SpatialExtent, SpatialPoint, px2geo
from timeseriesviewer.ui.docks import TsvDockWidgetBase, loadUI
from timeseriesviewer.plotstyling import PlotStyle, PlotStyleButton
from timeseriesviewer.pixelloader import PixelLoader, PixelLoaderTask
from timeseriesviewer.sensorvisualization import SensorListModel
import pyqtgraph as pg
from pyqtgraph import functions as fn
from pyqtgraph import AxisItem
import datetime
from osgeo import gdal, gdal_array
import numpy as np
LABEL_DN = 'DN or Index'
LABEL_TIME = 'Date'
OPENGL_AVAILABLE = False
try:
import OpenGL
OPENGL_AVAILABLE = True
except:
pass
def getTextColorWithContrast(c):
assert isinstance(c, QColor)
if c.lightness() < 0.5:
return QColor('white')
else:
return QColor('black')
def bandIndex2bandKey(i):
assert isinstance(i, int)
assert i >= 0
return 'b{}'.format(i + 1)
def bandKey2bandIndex(key):
match = PlotSettingsModel.regBandKeyExact.search(key)
assert match
idx = int(match.group()[1:]) - 1
return idx
def selectedModelIndices(tableView):
assert isinstance(tableView, QTableView)
result = {}
sm = tableView.selectionModel()
m = tableView.model()
if isinstance(sm, QItemSelectionModel) and isinstance(m, QAbstractItemModel):
for idx in sm.selectedIndexes():
assert isinstance(idx, QModelIndex)
if idx.row() not in result.keys():
result[idx.row()] = idx
return result.values()
class DateTimeAxis(pg.AxisItem):
def __init__(self, *args, **kwds):
super(DateTimeAxis, self).__init__(*args, **kwds)
self.setRange(1,3000)
self.enableAutoSIPrefix(False)
self.labelAngle = 0
def logTickStrings(self, values, scale, spacing):
s = ""
def tickStrings(self, values, scale, spacing):
strns = []
if len(values) == 0:
return []
#assert isinstance(values[0],
values = [num2date(v) if v > 0 else num2date(1) for v in values]
rng = max(values)-min(values)
ndays = rng.astype(int)
strns = []
for v in values:
if ndays == 0:
strns.append(v.astype(str))
strns.append(v.astype(str))
return strns
def tickValues(self, minVal, maxVal, size):
d = super(DateTimeAxis, self).tickValues(minVal, maxVal, size)
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def drawPicture(self, p, axisSpec, tickSpecs, textSpecs):
p.setRenderHint(p.Antialiasing, False)
p.setRenderHint(p.TextAntialiasing, True)
## draw long line along axis
pen, p1, p2 = axisSpec
p.setPen(pen)
p.drawLine(p1, p2)
p.translate(0.5, 0) ## resolves some damn pixel ambiguity
## draw ticks
for pen, p1, p2 in tickSpecs:
p.setPen(pen)
p.drawLine(p1, p2)
## Draw all text
if self.tickFont is not None:
p.setFont(self.tickFont)
p.setPen(self.pen())
#for rect, flags, text in textSpecs:
# p.drawText(rect, flags, text)
# # p.drawRect(rect)
#see https://github.com/pyqtgraph/pyqtgraph/issues/322
for rect, flags, text in textSpecs:
p.save() # save the painter state
p.translate(rect.center()) # move coordinate system to center of text rect
p.rotate(self.labelAngle) # rotate text
p.translate(-rect.center()) # revert coordinate system
p.drawText(rect, flags, text)
p.restore() # restore the painter state
def __init__(self, *args, **kwds):
# menu creation is deferred because it is expensive and often
# the user will never see the menu anyway.
self.menu = None
def boundingRect(self):
return super(_SensorPoints,self).boundingRect()
def paint(self, p, *args):
super(_SensorPoints, self).paint(p, *args)
# On right-click, raise the context menu
def mouseClickEvent(self, ev):
if ev.button() == QtCore.Qt.RightButton:
if self.raiseContextMenu(ev):
ev.accept()
def raiseContextMenu(self, ev):
menu = self.getContextMenus()
# Let the scene add on to the end of our context menu
# (this is optional)
menu = self.scene().addParentContextMenus(self, menu, ev)
pos = ev.screenPos()
menu.popup(QtCore.QPoint(pos.x(), pos.y()))
return True
# This method will be called when this item's _children_ want to raise
# a context menu that includes their parents' menus.
def getContextMenus(self, event=None):
if self.menu is None:
self.menu = QMenu()
self.menu.setTitle(self.name + " options..")
green = QAction("Turn green", self.menu)
green.triggered.connect(self.setGreen)
self.menu.addAction(green)
self.menu.green = green
blue = QAction("Turn blue", self.menu)
blue.triggered.connect(self.setBlue)
self.menu.addAction(blue)
self.menu.green = blue
alpha = QWidgetAction(self.menu)
alphaSlider = QSlider()
alphaSlider.setOrientation(QtCore.Qt.Horizontal)
alphaSlider.setMaximum(255)
alphaSlider.setValue(255)
alphaSlider.valueChanged.connect(self.setAlpha)
alpha.setDefaultWidget(alphaSlider)
self.menu.addAction(alpha)
self.menu.alpha = alpha
self.menu.alphaSlider = alphaSlider
return self.menu
class TemporalProfilePlotDataItem(pg.PlotDataItem):
def __init__(self, plotStyle, parent=None):
assert isinstance(plotStyle, TemporalProfilePlotStyle)
super(TemporalProfilePlotDataItem, self).__init__([], [], parent=parent)

benjamin.jakimow@geo.hu-berlin.de
committed
self.menu = None
#self.setFlags(QGraphicsItem.ItemIsSelectable)

benjamin.jakimow@geo.hu-berlin.de
committed
self.setAcceptedMouseButtons(Qt.LeftButton | Qt.RightButton)
self.mPlotStyle.sigUpdated.connect(self.updateDataAndStyle)

benjamin.jakimow@geo.hu-berlin.de
committed
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# On right-click, raise the context menu
def mouseClickEvent(self, ev):
if ev.button() == QtCore.Qt.RightButton:
if self.raiseContextMenu(ev):
ev.accept()
def raiseContextMenu(self, ev):
menu = self.getContextMenus()
# Let the scene add on to the end of our context menu
# (this is optional)
menu = self.scene().addParentContextMenus(self, menu, ev)
pos = ev.screenPos()
menu.popup(QtCore.QPoint(pos.x(), pos.y()))
return True
# This method will be called when this item's _children_ want to raise
# a context menu that includes their parents' menus.
def getContextMenus(self, event=None):
if self.menu is None:
self.menu = QMenu()
self.menu.setTitle(self.name + " options..")
green = QAction("Turn green", self.menu)
green.triggered.connect(self.setGreen)
self.menu.addAction(green)
self.menu.green = green
blue = QAction("Turn blue", self.menu)
blue.triggered.connect(self.setBlue)
self.menu.addAction(blue)
self.menu.green = blue
alpha = QWidgetAction(self.menu)
alphaSlider = QSlider()
alphaSlider.setOrientation(QtCore.Qt.Horizontal)
alphaSlider.setMaximum(255)
alphaSlider.setValue(255)
alphaSlider.valueChanged.connect(self.setAlpha)
alpha.setDefaultWidget(alphaSlider)
self.menu.addAction(alpha)
self.menu.alpha = alpha
self.menu.alphaSlider = alphaSlider
return self.menu
def updateDataAndStyle(self):
TP = self.mPlotStyle.temporalProfile()
sensor = self.mPlotStyle.sensor()
if isinstance(TP, TemporalProfile) and isinstance(sensor, SensorInstrument):
x, y = TP.dataFromExpression(self.mPlotStyle.sensor(), self.mPlotStyle.expression())
x = np.asarray(x, dtype=np.float)
y = np.asarray(y, dtype=np.float)
self.setData(x=[], y=[]) #dummy
self.updateStyle()
def updateStyle(self):
"""
Updates visibility properties
"""
if DEBUG:
print('{} updateStyle'.format(self))
from pyqtgraph.graphicsItems.ScatterPlotItem import drawSymbol
# path = drawSymbol(p, self.markerSymbol, self.markerSize, self.markerPen, self.markerBrush)
# #painter, symbol, size, pen, brush
self.setSymbol(self.mPlotStyle.markerSymbol)
self.setSymbolSize(self.mPlotStyle.markerSize)
self.setSymbolBrush(self.mPlotStyle.markerBrush)
self.setSymbolPen(self.mPlotStyle.markerPen)
self.update()
#self.setPen(fn.mkPen(self.mPlotStyle.linePen))
#self.setFillBrush(fn.mkBrush(self.mPlotStyle.mExpression))
#self.setSymbolBrush(fn.mkBrush(self.mPlotStyle.markerBrush))
# self.setFillBrush(self.mPlotStyle.)
#self.update()
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
def setClickable(self, b, width=None):
assert isinstance(b, bool)
self.curve.setClickable(b, width=width)
def setColor(self, color):
if not isinstance(color, QColor):
color = QColor(color)
self.setPen(color)
def pen(self):
return fn.mkPen(self.opts['pen'])
def color(self):
return self.pen().color()
def setLineWidth(self, width):
pen = pg.mkPen(self.opts['pen'])
assert isinstance(pen, QPen)
pen.setWidth(width)
self.setPen(pen)
class PlotSettingsWidgetDelegate(QStyledItemDelegate):
"""
def __init__(self, tableView, timeSeries, temporalProfileListModel, parent=None):
super(PlotSettingsWidgetDelegate, self).__init__(parent=parent)
self._preferedSize = QgsFieldExpressionWidget().sizeHint()
self.tableView = tableView
self.timeSeries = timeSeries
self.temporalProfileListModel = temporalProfileListModel
def setItemDelegates(self, tableView):
assert isinstance(tableView, QTableView)
model = tableView.model()
assert isinstance(model, PlotSettingsModel)
for c in [model.cnSensor, model.cnExpression, model.cnStyle, model.cnTemporalProfile]:
i = model.columNames.index(c)
tableView.setItemDelegateForColumn(i, self)
def getColumnName(self, index):
assert index.isValid()
model = index.model()
assert isinstance(model, PlotSettingsModel)
return model.columNames[index.column()]
"""
def sizeHint(self, options, index):
s = super(ExpressionDelegate, self).sizeHint(options, index)
exprString = self.tableView.model().data(index)
l = QLabel()
l.setText(exprString)
x = l.sizeHint().width() + 100
s = QSize(x, s.height())
return self._preferedSize
"""
def createEditor(self, parent, option, index):
cname = self.getColumnName(index)
model = self.tableView.model()
w = None
if index.isValid() and isinstance(model, PlotSettingsModel):
plotStyle = model.idx2plotStyle(index)
if isinstance(plotStyle, TemporalProfilePlotStyle):
if cname == model.cnExpression:
w = QgsFieldExpressionWidget(parent=parent)
#todo: w.setLayer(sv.memLyr)
w.setExpressionDialogTitle('Values')
w.setToolTip('Set an expression to specify the image band or calculate a spectral index.')
w.fieldChanged.connect(lambda : self.checkData(w, w.expression()))
elif cname == model.cnStyle:
w = PlotStyleButton(parent=parent)
w.setPlotStyle(plotStyle)
w.setToolTip('Set style.')
w.sigPlotStyleChanged.connect(lambda: self.checkData(w, w.plotStyle()))
elif cname == model.cnSensor:
w = QComboBox(parent=parent)
m = SensorListModel(self.timeSeries)
w.setModel(m)
elif cname == model.cnTemporalProfile:
w = QComboBox(parent=parent)
w.setModel(self.temporalProfileListModel)
else:
raise NotImplementedError()
return w
def checkData(self, w, expression):
if isinstance(w, QgsFieldExpressionWidget):
assert expression == w.expression()
assert w.isExpressionValid(expression) == w.isValidExpression()
if w.isValidExpression():
self.commitData.emit(w)
else:
s = ""
#print(('Delegate commit failed',w.asExpression()))
self.commitData.emit(w)
def setEditorData(self, editor, index):
cname = self.getColumnName(index)
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
model = self.tableView.model()
w = None
if index.isValid() and isinstance(model, PlotSettingsModel):
cname = self.getColumnName(index)
if cname == model.cnExpression:
lastExpr = index.model().data(index, Qt.DisplayRole)
assert isinstance(editor, QgsFieldExpressionWidget)
editor.setProperty('lastexpr', lastExpr)
editor.setField(lastExpr)
elif cname == model.cnStyle:
style = index.data()
assert isinstance(editor, PlotStyleButton)
editor.setPlotStyle(style)
elif cname == model.cnSensor:
assert isinstance(editor, QComboBox)
m = editor.model()
assert isinstance(m, SensorListModel)
sensor = index.data(role=Qt.UserRole)
if isinstance(sensor, SensorInstrument):
idx = m.sensor2idx(sensor)
editor.setCurrentIndex(idx.row())
elif cname == model.cnTemporalProfile:
assert isinstance(editor, QComboBox)
m = editor.model()
assert isinstance(m, TemporalProfileCollectionListModel)
TP = index.data(role=Qt.UserRole)
if isinstance(TP, TemporalProfile):
idx = m.tp2idx(TP)
editor.setCurrentIndex(idx)
else:
raise NotImplementedError()
def setModelData(self, w, model, index):
cname = self.getColumnName(index)
if index.isValid() and isinstance(model, PlotSettingsModel):
if cname == model.cnExpression:
assert isinstance(w, QgsFieldExpressionWidget)
expr = w.asExpression()
exprLast = model.data(index, Qt.DisplayRole)
if w.isValidExpression() and expr != exprLast:
model.setData(index, w.asExpression(), Qt.EditRole)
elif cname == model.cnStyle:
assert isinstance(w, PlotStyleButton)
model.setData(index, w.plotStyle(), Qt.EditRole)
elif cname == model.cnSensor:
assert isinstance(w, QComboBox)
sensor = w.itemData(w.currentIndex(), role=Qt.UserRole)
assert isinstance(sensor, SensorInstrument)
model.setData(index, sensor, Qt.EditRole)
elif cname == model.cnTemporalProfile:
assert isinstance(w, QComboBox)
TP = w.itemData(w.currentIndex(), role=Qt.UserRole)
assert isinstance(TP, TemporalProfile)
model.setData(index, TP, Qt.EditRole)
else:
raise NotImplementedError()
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
class SensorPixelDataMemoryLayer(QgsVectorLayer):
def __init__(self, sensor, crs=None):
assert isinstance(sensor, SensorInstrument)
if crs is None:
crs = QgsCoordinateReferenceSystem('EPSG:4862')
uri = 'Point?crs={}'.format(crs.authid())
super(SensorPixelDataMemoryLayer, self).__init__(uri, 'Pixels_sensor_' + sensor.name(), 'memory', False)
self.mSensor = sensor
#initialize fields
assert self.startEditing()
# standard field names, types, etc.
fieldDefs = [('pxid', QVariant.String, 'integer'),
('date', QVariant.String, 'char'),
('doy', QVariant.Int, 'integer'),
('geo_x', QVariant.Double, 'decimal'),
('geo_y', QVariant.Double, 'decimal'),
('px_x', QVariant.Int, 'integer'),
('px_y', QVariant.Int, 'integer'),
]
# one field for each band
for b in range(sensor.nb):
fName = 'b{}'.format(b + 1)
fieldDefs.append((fName, QVariant.Double, 'decimal'))
# initialize fields
for fieldDef in fieldDefs:
field = QgsField(fieldDef[0], fieldDef[1], fieldDef[2])
self.addAttribute(field)
self.commitChanges()
def sensor(self):
return self.mSensor
def nPixels(self):
raise NotImplementedError()
def dates(self):
raise NotImplementedError()
class TemporalProfileCollectionListModel(QAbstractListModel):
def __init__(self, temporalProfileCollection, *args, **kwds):
super(TemporalProfileCollectionListModel, self).__init__(*args, **kwds)
assert isinstance(temporalProfileCollection, TemporalProfileCollection)
self.mTPColl = temporalProfileCollection
self.mTPColl.rowsAboutToBeInserted.connect(self.rowsAboutToBeInserted)
self.mTPColl.rowsInserted.connect(self.rowsInserted.emit)
#self.mTPColl.rowsAboutToBeRemoved.connect(self.rowsAboutToBeRemoved)
self.mTPColl.rowsRemoved.connect(lambda : self.modelReset.emit())
def idx2tp(self, *args, **kwds):
return self.mTPColl.idx2tp(*args, **kwds)
def tp2idx(self, *args, **kwds):
return self.mTPColl.tp2idx(*args, **kwds)
def flags(self, index):
if index.isValid():
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
return flags
#return item.qt_flags(index.column())
return Qt.NoItemFlags
def rowCount(self, *args, **kwds):
return self.mTPColl.rowCount(*args, **kwds)
def data(self, index, role=Qt.DisplayRole):
if role is None or not index.isValid():
return None
TP = self.mTPColl.idx2tp(index)
value = None
if isinstance(TP, TemporalProfile):
if role == Qt.DisplayRole:
value = '{}'.format(TP.name())
elif role == Qt.ToolTipRole:
value = '#{} "{}" {}'.format(TP.mID, TP.name(), TP.mCoordinate)
elif role == Qt.UserRole:
value = TP
return value
class TemporalProfileCollection(QAbstractTableModel):
A collection to store the TemporalProfile data delivered by a PixelLoader
"""
#sigSensorAdded = pyqtSignal(SensorInstrument)
#sigSensorRemoved = pyqtSignal(SensorInstrument)
#sigPixelAdded = pyqtSignal()
#sigPixelRemoved = pyqtSignal()
sigTemporalProfilesAdded = pyqtSignal(list)
sigTemporalProfilesRemoved = pyqtSignal(list)
sigMaxProfilesChanged = pyqtSignal(int)
super(TemporalProfileCollection, self).__init__()
#self.sensorPxLayers = dict()
#self.memLyrCrs = QgsCoordinateReferenceSystem('EPSG:4326')
self.mcnID = 'id'
self.mcnCoordinate = 'Coordinate'
self.mcnLoaded = 'Loading'
self.mcnName = 'Name'
self.mColumNames = [self.mcnName, self.mcnLoaded, self.mcnCoordinate]
crs = QgsCoordinateReferenceSystem('EPSG:4862')
uri = 'Point?crs={}'.format(crs.authid())
self.TS = None
self.mLocations = QgsVectorLayer(uri, 'LOCATIONS', 'memory', False)
self.mTemporalProfiles = []
self.mTPLookupSpatialPoint = {}
self.mTPLookupID = {}
self.mMaxProfiles = 10
self.nextID = 0
def __len__(self):
return len(self.mTemporalProfiles)
def __iter__(self):
return iter(self.mTemporalProfiles)
def __getitem__(self, slice):
return self.mTemporalProfiles[slice]
def __contains__(self, item):
return item in self.mTemporalProfiles
def rowCount(self, parent=None, *args, **kwargs):
return len(self.mTemporalProfiles)
def columnCount(self, QModelIndex_parent=None, *args, **kwargs):
return len(self.mColumNames)
def idx2tp(self, index):
if index.isValid() and index.row() < len(self.mTemporalProfiles) :
return self.mTemporalProfiles[index.row()]
return None
def tp2idx(self, temporalProfile):
assert isinstance(temporalProfile, TemporalProfile)
row = self.mTemporalProfiles.index(temporalProfile)
return self.createIndex(row, 0)
else:
return QModelIndex()
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
def data(self, index, role = Qt.DisplayRole):
if role is None or not index.isValid():
return None
value = None
columnName = self.mColumNames[index.column()]
TP = self.idx2tp(index)
if not isinstance(TP, TemporalProfile):
return None
#self.mColumNames = ['id','coordinate','loaded']
if role == Qt.DisplayRole:
if columnName == self.mcnID:
value = TP.mID
elif columnName == self.mcnName:
value = TP.name()
elif columnName == self.mcnCoordinate:
value = '{}'.format(TP.mCoordinate)
elif columnName == self.mcnLoaded:
nIs, nMax = TP.loadingStatus()
if nMax > 0:
value = '{}/{} ({:0.2f} %)'.format(nIs, nMax, float(nIs) / nMax * 100)
elif role == Qt.EditRole:
if columnName == self.mcnName:
value = TP.name()
elif role == Qt.ToolTipRole:
if columnName == self.mcnID:
value = 'ID Temporal Profile'
elif columnName == self.mcnName:
value = TP.name()
elif columnName == self.mcnCoordinate:
value = '{}'.format(TP.mCoordinate)
elif columnName == self.mcnLoaded:
nIs, nMax = TP.loadingStatus()
value = '{}'.format(TP.mCoordinate)
elif role == Qt.UserRole:
value = TP
return value
def flags(self, index):
if index.isValid():
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
cName = self.mColumNames[index.column()]
if cName == self.mcnName:
flags = flags | Qt.ItemIsEditable
return flags
#return item.qt_flags(index.column())
return None
def setData(self, index, value, role=None):
if role is None or not index.isValid():
return None
cName = self.mColumNames[index.column()]
TP = self.idx2tp(index)
if isinstance(TP, TemporalProfile):
if role == Qt.EditRole and cName == self.mcnName:
if len(value) == 0: #do not accept empty strings
return False
else:
TP.setName(value)
return True
return False
def headerData(self, col, orientation, role):
if Qt is None:
return None
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return self.mColumNames[col]
elif orientation == Qt.Vertical:
return col
return None
def insertTemporalProfiles(self, temporalProfiles, i=None):
if isinstance(temporalProfiles, TemporalProfile):
temporalProfiles = [temporalProfiles]
assert isinstance(temporalProfiles, list)
for temporalProfile in temporalProfiles:
assert isinstance(temporalProfile, TemporalProfile)
if i is None:
i = len(self.mTemporalProfiles)
if len(temporalProfiles) > 0:
self.beginInsertRows(QModelIndex(), i, i + len(temporalProfiles) - 1)
for temporalProfile in temporalProfiles:
assert isinstance(temporalProfile, TemporalProfile)
id = self.nextID
self.nextID += 1
temporalProfile.mID = id
self.mTemporalProfiles.insert(i, temporalProfile)
self.mTPLookupID[id] = temporalProfile
self.mTPLookupSpatialPoint[temporalProfile.mCoordinate] = temporalProfile
i += 1
self.endInsertRows()
self.sigTemporalProfilesAdded.emit(temporalProfiles)
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
def temporalProfileFromGeometry(self, geometry):
if geometry in self.mTPLookupSpatialPoint.keys():
return self.mTPLookupSpatialPoint[geometry]
else:
return None
def temporalProfileFromID(self, id):
if id in self.mTPLookupID.keys():
return self.mTPLookupID[id]
else:
return None
def id(self, temporalProfile):
"""
Returns the id of an TemporalProfile
:param temporalProfile: TemporalProfile
:return: id or None, inf temporalProfile is not part of this collections
"""
for k, tp in self.mTPLookupID.items():
if tp == temporalProfile:
return k
return None
def fromID(self, id):
if self.mTPLookupID.has_key(id):
return self.mTPLookupID[id]
else:
return None
def fromSpatialPoint(self, spatialPoint):
if self.mTPLookupSpatialPoint.has_key(spatialPoint):
return self.mTPLookupSpatialPoint[spatialPoint]
else:
return None
def removeTemporalProfiles(self, temporalProfiles):
"""
Removes temporal profiles from this collection
:param temporalProfile: TemporalProfile
"""
if isinstance(temporalProfiles, TemporalProfile):
temporalProfiles = [temporalProfiles]
assert isinstance(temporalProfiles, list)
temporalProfiles = [tp for tp in temporalProfiles if isinstance(tp, TemporalProfile) and tp in self.mTemporalProfiles]
if len(temporalProfiles) > 0:
def deleteFromDict(d, value):
assert isinstance(d, dict)
if value in d.values():
key = d.keys()[d.values().index(value)]
d.pop(key)
for temporalProfile in temporalProfiles:
assert isinstance(temporalProfile, TemporalProfile)
idx = self.tp2idx(temporalProfile)
row = idx.row()
self.beginRemoveRows(QModelIndex(), row, row)
self.mTemporalProfiles.remove(temporalProfile)
deleteFromDict(self.mTPLookupID, temporalProfile)
deleteFromDict(self.mTPLookupSpatialPoint, temporalProfile)
self.sigTemporalProfilesRemoved.emit(temporalProfiles)
self.clear()
if isinstance(timeSeries, TimeSeries):
self.TS = timeSeries
#for sensor in self.TS.Sensors:
# self.addSensor(sensor)
#self.TS.sigSensorAdded.connect(self.addSensor)
#self.TS.sigSensorRemoved.connect(self.removeSensor)
def setMaxProfiles(self, n):
"""
Sets the maximum number of temporal profiles to be stored in this container.
:param n: number of profiles, must be >= 1
"""
old = self.mMaxProfiles
if old != n:
self.mMaxProfiles = n
self.prune()
self.sigMaxProfilesChanged.emit(self.mMaxProfiles)
def prune(self):
"""
Reduces the number of temporal profile to the value n defined with .setMaxProfiles(n)
:return: [list-of-removed-TemporalProfiles]
"""
def getFieldDefn(self, name, values):
if isinstance(values, np.ndarray):
# add bands
if values.dtype in [np.int8, np.int16, np.int32, np.int64,
np.uint8, np.uint16, np.uint32, np.uint64]:
fType = QVariant.Int
fTypeName = 'integer'
elif values.dtype in [np.float16, np.float32, np.float64]:
fType = QVariant.Double
fTypeName = 'decimal'
else:
raise NotImplementedError()
return QgsField(name, fType, fTypeName)
def setFeatureAttribute(self, feature, name, value):
assert isinstance(feature, QgsFeature)
assert isinstance(name, str)
i = feature.fieldNameIndex(name)
assert i >= 0, 'Field "{}" does not exist'.format(name)
field = feature.fields()[i]
if field.isNumeric():
if field.type() == QVariant.Int:
value = int(value)
elif field.type() == QVariant.Double:
value = float(value)
else:
raise NotImplementedError()
feature.setAttribute(i, value)
def sort(self, col, order):
if self.rowCount() == 0:
return
self.layoutAboutToBeChanged.emit()
colName = self.mColumNames[col]
r = order != Qt.AscendingOrder
if colName == self.mcnName:
self.items.sort(key = lambda TP:TP.name(), reverse=r)
elif colName == self.mcnCoordinate:
self.items.sort(key=lambda TP: str(TP.mCoordinate), reverse=r)
elif colName == self.mcnID:
self.items.sort(key=lambda TP: TP.mID, reverse=r)
elif colName == self.mcnLoaded:
self.items.sort(key=lambda TP: TP.loadingStatus(), reverse=r)
self.layoutChanged.emit()
def addPixelLoaderResult(self, d):
assert isinstance(d, PixelLoaderTask)
if d.success():
TP = self.temporalProfileFromID(TPid)
tsd = self.TS.getTSD(d.sourcePath)
assert isinstance(tsd, TimeSeriesDatum)
if isinstance(TP, TemporalProfile):
profileData = d.resProfiles[i]
if not isinstance(profileData, tuple):
s = ""
validValues = not isinstance(vMean, str)
for iBand, bandIndex in enumerate(d.bandIndices):
key = 'b{}'.format(bandIndex + 1)
values[key] = vMean[iBand] if validValues else None
values[key] = vStd[iBand] if validValues else None
#indicesY, indicesX = d.imagePixelIndices()
#values['px_x'] = indicesX
#values['px_y'] = indicesY
TP.updateData(tsd, values)
def clear(self):
#todo: remove TS Profiles
#self.mTemporalProfiles.clear()
#self.sensorPxLayers.clear()
pass

benjamin.jakimow@geo.hu-berlin.de
committed
sigExpressionUpdated = pyqtSignal()
def __init__(self, temporalProfile):
super(TemporalProfilePlotStyle, self).__init__()
assert isinstance(temporalProfile, TemporalProfile)
self.mSensor = None
self.mTP = temporalProfile
self.mExpression = u'"b1"'
self.mPlotItems = []
if isinstance(temporalProfile, TemporalProfile):
self.setTemporalProfile(temporalProfile)
def createPlotItem(self, plotWidget):
pdi = TemporalProfilePlotDataItem(self)
self.mPlotItems.append(pdi)
return pdi
def setTemporalProfile(self, temporalPofile):
assert isinstance(temporalPofile, TemporalProfile)
b = temporalPofile != self.mTP
self.mTP = temporalPofile
assert isinstance(sensor, SensorInstrument)
self.mSensor = sensor
if b: self.update()
def update(self):
super(TemporalProfilePlotStyle, self).update()
for pdi in self.mPlotItems:
assert isinstance(pdi, TemporalProfilePlotDataItem)
pdi.updateStyle()
#pdi.updateItems()
def sensor(self):
return self.mSensor
def setExpression(self, exp):
assert isinstance(exp, unicode)
b = self.mExpression != exp
self.mExpression = exp
if b:
self.update()
self.sigExpressionUpdated.emit()
def expression(self):
return self.mExpression
def __reduce_ex__(self, protocol):
return self.__class__, (), self.__getstate__()
def __getstate__(self):
result = super(TemporalProfilePlotStyle, self).__getstate__()
del result['mSensor']
return result
class DateTimeViewBox(pg.ViewBox):
"""
Subclass of ViewBox
"""
sigMoveToDate = pyqtSignal(np.datetime64)
def __init__(self, parent=None):
"""
Constructor of the CustomViewBox
"""
super(DateTimeViewBox, self).__init__(parent)
#self.menu = None # Override pyqtgraph ViewBoxMenu
#self.menu = self.getMenu() # Create the menu
#self.menu = None
def raiseContextMenu(self, ev):
pt = self.mapDeviceToView(ev.pos())
print(pt.x(), pt.y())
date = num2date(pt.x())
menu = QMenu(None)
a = menu.addAction('Move to {}'.format(date))
a.setData(date)
a.triggered.connect(lambda : self.sigMoveToDate.emit(date))
self.scene().addParentContextMenus(self, menu, ev)
menu.exec_(ev.screenPos().toPoint())
class DateTimePlotWidget(pg.PlotWidget):
"""
Subclass of PlotWidget
"""
def __init__(self, parent=None):
"""
Constructor of the widget
"""
super(DateTimePlotWidget, self).__init__(parent, viewBox=DateTimeViewBox())
axisItems={'bottom':DateTimeAxis(orientation='bottom')},
self.setCentralItem(self.plotItem)
self.xAxisInitialized = False
class PlotSettingsModel(QAbstractTableModel):
#sigSensorAdded = pyqtSignal(SensorPlotSettings)
sigVisibilityChanged = pyqtSignal(TemporalProfilePlotStyle)
sigDataChanged = pyqtSignal(TemporalProfilePlotStyle)
sigPlotStylesAdded = pyqtSignal(list)
sigPlotStylesRemoved = pyqtSignal(list)
regBandKey = re.compile("(?<!\w)b\d+(?!\w)", re.IGNORECASE)
regBandKeyExact = re.compile('^' + regBandKey.pattern + '$', re.IGNORECASE)
def __init__(self, temporalProfileCollection, plotWidget, parent=None, *args):
#assert isinstance(tableView, QTableView)
super(PlotSettingsModel, self).__init__(parent=parent)
assert isinstance(temporalProfileCollection, TemporalProfileCollection)
self.cnID = 'ID'
self.cnSensor = 'sensor'
self.cnExpression = LABEL_DN
self.cnStyle = 'style'
self.cnTemporalProfile = 'px'
self.columNames = [self.cnTemporalProfile, self.cnSensor, self.cnStyle, self.cnExpression]
self.mPlotSettings = []
#assert isinstance(plotWidget, DateTimePlotWidget)
self.mPlotWidget = plotWidget
self.sortColumnIndex = 0
self.sortOrder = Qt.AscendingOrder
#self.tpCollection.sigTemporalProfilesRemoved.connect(lambda removedTPs : self.removePlotStyles([p for p in self.mPlotSettings if p.temporalProfile() in removedTPs]))
assert isinstance(self.tpCollection.TS, TimeSeries)
#self.tpCollection.TS.sigSensorAdded.connect(self.addPlotItem)
#self.tpCollection.TS.sigSensorRemoved.connect(self.removeSensor)
self.sort(0, Qt.AscendingOrder)
self.dataChanged.connect(self.signaler)
def __iter__(self):
return iter(self.mPlotSettings)

benjamin.jakimow@geo.hu-berlin.de
committed
def __getitem__(self, slice):
return self.mPlotSettings[slice]
def __contains__(self, item):
return item in self.mPlotSettings
def testSlot(self, *args):
print(('TESTSLOT', args))
def columnIndex(self, name):
return self.columNames.index(name)
def signaler(self, idxUL, idxLR):
if idxUL.isValid():
plotStyle = self.idx2plotStyle(idxUL)
cname = self.columNames[idxUL.column()]
if cname in [self.cnSensor,self.cnStyle]:
self.sigVisibilityChanged.emit(plotStyle)
if cname in [self.cnExpression]:
self.sigDataChanged.emit(plotStyle)
def requiredBandsIndices(self, sensor):
"""
Returns the band indices required to calculate the values for
the different PlotStyle expressions making use of sensor
:param sensor: SensorInstrument for which the band indices are to be returned.
:return: [list-of-band-indices]
"""
bandIndices = set()
assert isinstance(sensor, SensorInstrument)
for p in [p for p in self.mPlotSettings if p.sensor() == sensor]:
assert isinstance(p, TemporalProfilePlotStyle)
expression = p.expression()
#remove leading & tailing "
bandKeys = PlotSettingsModel.regBandKey.findall(expression)
for bandIndex in [bandKey2bandIndex(key) for key in bandKeys]:

benjamin.jakimow@geo.hu-berlin.de
committed
def insertPlotStyles(self, plotStyles, i=None):
"""
Inserts PlotStyle
:param plotStyles: TemporalProfilePlotStyle | [list-of-TemporalProfilePlotStyle]
:param i: index to insert, defaults to the last list position
"""
if isinstance(plotStyles, TemporalProfilePlotStyle):
plotStyles = [plotStyles]
assert isinstance(plotStyles, list)
for plotStyle in plotStyles:
assert isinstance(plotStyle, TemporalProfilePlotStyle)

benjamin.jakimow@geo.hu-berlin.de
committed
if len(plotStyles) > 0:
self.beginInsertRows(QModelIndex(), i, i + len(plotStyles)-1)
for j, plotStyle in enumerate(plotStyles):
assert isinstance(plotStyle, TemporalProfilePlotStyle)
self.mPlotSettings.insert(i+j, plotStyle)
self.endInsertRows()
self.sigPlotStylesAdded.emit(plotStyles)
def removePlotStyles(self, plotStyles):
"""
Removes PlotStyle instances
:param plotStyles: TemporalProfilePlotStyle | [list-of-TemporalProfilePlotStyle]
"""
if isinstance(plotStyles, PlotStyle):
plotStyles = [plotStyles]
assert isinstance(plotStyles, list)
if len(plotStyles) > 0:
for plotStyle in plotStyles:
assert isinstance(plotStyle, PlotStyle)
if plotStyle in self.mPlotSettings:
idx = self.plotStyle2idx(plotStyle)
self.beginRemoveRows(QModelIndex(), idx.row(),idx.row())
self.mPlotSettings.remove(plotStyle)
self.endRemoveRows()
if isinstance(plotStyle, TemporalProfilePlotStyle):
for pi in plotStyle.mPlotItems:
self.mPlotWidget.getPlotItem().removeItem(pi)
self.sigPlotStylesRemoved.emit(plotStyles)
def sort(self, col, order):
if self.rowCount() == 0:
return
colName = self.columnames[col]
r = order != Qt.AscendingOrder
#self.beginMoveRows(idxSrc,
if colName == self.cnSensor:
self.mPlotSettings.sort(key = lambda sv:sv.sensor().name(), reverse=r)
elif colName == self.cnExpression:
self.mPlotSettings.sort(key=lambda sv: sv.expression(), reverse=r)
elif colName == self.cnStyle:
self.mPlotSettings.sort(key=lambda sv: sv.color, reverse=r)
def rowCount(self, parent = QModelIndex()):
return len(self.mPlotSettings)
def removeRows(self, row, count , parent = QModelIndex()):
for tsd in toRemove:
self.endRemoveRows()
if plotStyle in self.mPlotSettings:
i = self.mPlotSettings.index(plotStyle)
return self.createIndex(i, 0)
else:
return QModelIndex()
def idx2plotStyle(self, index):
if index.isValid() and index.row() < self.rowCount():
return self.mPlotSettings[index.row()]
return None
def columnCount(self, parent = QModelIndex()):
def data(self, index, role = Qt.DisplayRole):
if role is None or not index.isValid():
return None
value = None
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
columnName = self.columNames[index.column()]
plotStyle = self.idx2plotStyle(index)
if isinstance(plotStyle, TemporalProfilePlotStyle):
sensor = plotStyle.sensor()
#print(('data', columnName, role))
if role == Qt.DisplayRole:
if columnName == self.cnSensor:
if isinstance(sensor, SensorInstrument):
value = sensor.name()
else:
value = '<Select Sensor>'
elif columnName == self.cnExpression:
value = plotStyle.expression()
elif columnName == self.cnTemporalProfile:
value = plotStyle.temporalProfile().name()
#elif role == Qt.DecorationRole:
# if columnName == self.cnStyle:
# value = plotStyle.createIcon(QSize(96,96))
elif role == Qt.CheckStateRole:
if columnName == self.cnTemporalProfile:
value = Qt.Checked if plotStyle.isVisible() else Qt.Unchecked
elif role == Qt.UserRole:
value = plotStyle
if columnName == self.cnSensor:
value = plotStyle.sensor()
elif columnName == self.cnExpression:
value = plotStyle.expression()
elif columnName == self.cnStyle:
value = plotStyle
elif columnName == self.cnTemporalProfile:
value == plotStyle.temporalProfile()
else:
value = plotStyle
#print(('get data',value))
return value
def setData(self, index, value, role=None):
if role is None or not index.isValid():
return False
#print(('Set data', index.row(), index.column(), value, role))

benjamin.jakimow@geo.hu-berlin.de
committed
if value is None:
return False
result = False
plotStyle = self.idx2plotStyle(index)
if isinstance(plotStyle, TemporalProfilePlotStyle):
if role in [Qt.DisplayRole]:
if columnName == self.cnExpression:
plotStyle.setExpression(value)
result = True
elif columnName == self.cnStyle:
if isinstance(value, PlotStyle):
result = True
if role == Qt.CheckStateRole:
if columnName == self.cnTemporalProfile:
plotStyle.setVisibility(value == Qt.Checked)
result = True
if role == Qt.EditRole:
if columnName == self.cnExpression:
plotStyle.setExpression(value)
result = True
elif columnName == self.cnStyle:
plotStyle.copyFrom(value)
result = True
elif columnName == self.cnSensor:
plotStyle.setSensor(value)
result = True
elif columnName == self.cnTemporalProfile:
plotStyle.setTemporalProfile(value)
result = True
if result:
#save plot-style
self.dataChanged.emit(index, index)
return result
def savePlotSettings(self, sensorPlotSettings, index='DEFAULT'):
return
#todo
assert isinstance(sensorPlotSettings, TemporalProfilePlotStyle)
#todo: avoid dumps
id = 'SPS.{}.{}'.format(index, sensorPlotSettings.sensor().id())
d = pickle.dumps(sensorPlotSettings)
SETTINGS.setValue(id, d)
def restorePlotSettings(self, sensor, index='DEFAULT'):
assert isinstance(sensor, SensorInstrument)
id = 'SPS.{}.{}'.format(index, sensor.id())
sensorPlotSettings = SETTINGS.value(id)
if sensorPlotSettings is not None:
try:
sensorPlotSettings = pickle.loads(sensorPlotSettings)
s = ""
except:
sensorPlotSettings = None
pass
if isinstance(sensorPlotSettings, TemporalProfilePlotStyle):
return sensorPlotSettings
else:
def flags(self, index):
if index.isValid():
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
flags = flags | Qt.ItemIsUserCheckable
if columnName in [self.cnTemporalProfile, self.cnSensor, self.cnExpression, self.cnStyle]: #allow check state
flags = flags | Qt.ItemIsEditable
return flags
#return item.qt_flags(index.column())
return Qt.NoItemFlags
def headerData(self, col, orientation, role):
if Qt is None:
return None
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
elif orientation == Qt.Vertical and role == Qt.DisplayRole:
return col
return None
class ProfileViewDockUI(QgsDockWidget, loadUI('profileviewdock.ui')):
def __init__(self, parent=None):
super(ProfileViewDockUI, self).__init__(parent)
self.setupUi(self)
#self.line.setVisible(False)
#self.listWidget.setVisible(False)
self.stackedWidget.setCurrentWidget(self.page2D)
if OPENGL_AVAILABLE:
l = self.layout3DPlotWidget
from pyqtgraph.opengl import GLViewWidget
self.plotWidget3D.setObjectName('plotWidget3D')
size = self.labelDummy3D.size()
l.removeWidget(self.labelDummy3D)
l.addWidget(self.plotWidget3D)
#self.plotWidget3D.setSize(size)
self.labelDummy3D.setVisible(False)
#pi = self.plotWidget2D.plotItem
#ax = DateAxis(orientation='bottom', showValues=True)
#pi.layout.addItem(ax, 3,2)
self.baseTitle = self.windowTitle()
self.TS = None
self.progressBar.setMinimum(0)
self.progressBar.setMaximum(100)
self.progressBar.setValue(0)
self.progressInfo.setText('')
self.pxViewModel2D = None
self.pxViewModel3D = None
self.tableView2DProfiles.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents)
self.tableView2DProfiles.setSortingEnabled(True)
self.tableViewTemporalProfiles.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents)
self.tableViewTemporalProfiles.setSortingEnabled(True)
def dateDOY(date):
if isinstance(date, np.datetime64):
date = date.astype(datetime.date)
return date.timetuple().tm_yday
def daysPerYear(year):
if isinstance(year, np.datetime64):
year = year.astype(datetime.date)
if isinstance(year, datetime.date):
year = year.timetuple().tm_year
return dateDOY(datetime.date(year=year, month=12, day=31))
def date2num(d):
#kindly taken from https://stackoverflow.com/questions/6451655/python-how-to-convert-datetime-dates-to-decimal-years
if isinstance(d, np.datetime64):
d = d.astype(datetime.datetime)
assert isinstance(d, datetime.date)
yearDuration = daysPerYear(d)
yearElapsed = d.timetuple().tm_yday
fraction = float(yearElapsed) / float(yearDuration)
if fraction == 1.0:
fraction = 0.9999999
return float(d.year) + fraction
def num2date(n, dt64=True):
n = float(n)
if n < 1:
n += 1
year = int(n)
fraction = n - year
yearDuration = daysPerYear(year)
yearElapsed = fraction * yearDuration
import math
doy = round(yearElapsed)
if doy < 1:
doy = 1
try:
date = datetime.date(year, 1, 1) + datetime.timedelta(days=doy-1)
except:
s = ""
if dt64:
return np.datetime64(date)
else:
return date
#return np.datetime64('{:04}-01-01'.format(year), 'D') + np.timedelta64(int(yearElapsed), 'D')
def depr_date2num(d):
d2 = d.astype(datetime.datetime)
o = d2.toordinal()
#assert d == num2date(o)
return o
if n < 1:
n = 1
return np.datetime64('{:04}-{:02}-{:02}'.format(d.year,d.month,d.day), 'D')
class TemporalProfile(QObject):
_mNextID = 0
@staticmethod
def nextID():
n = TemporalProfile._mNextID
TemporalProfile._mNextID += 1
return n
def __init__(self, timeSeries, spatialPoint):
super(TemporalProfile, self).__init__()
assert isinstance(spatialPoint, SpatialPoint)
self.mUpdated = False
self.mName = '#{}'.format(self.mID)
self.mLoaded = self.mLoadedMax = 0
self.initMetadata()
self.updateLoadingStatus()
def initMetadata(self):
for tsd in self.mTimeSeries:
assert isinstance(tsd, TimeSeriesDatum)
meta = {'doy':tsd.doy,
'date':str(tsd.date)}
sigNameChanged = pyqtSignal(str)
def setName(self, name):
if name != self.mName:
self.mName = name
self.sigNameChanged.emit(self.mName)
def name(self):
return self.mName
def plot(self):
import pyqtgraph as pg
for sensor in self.mTimeSeries.sensors():
assert isinstance(sensor, SensorInstrument)
plotStyle = TemporalProfilePlotStyle(self)
plotStyle.setSensor(sensor)
pi = TemporalProfilePlotDataItem(plotStyle)
pi.setClickable(True)
pw = pg.plot(title=self.name())
pw.getPlotItem().addItem(pi)
pi.setColor('green')
pg.QAPP.exec_()
assert isinstance(tsd, TimeSeriesDatum)
assert isinstance(values, dict)
if tsd not in self.mData.keys():
self.mData[tsd] = {}
self.mData[tsd].update(values)
self.updateLoadingStatus()
self.mUpdated = True
def resetUpdated(self):
self.mUpdated = False
def updated(self):
return self.mUpdated
def qgsFieldFromKeyValue(self, key, value):
t = type(value)
if t in [int, float] or np.isreal(value):
fLen = 0
fPrec = 0
fComm = ''
fType = ''
f = QgsField(key, QVariant.Double, 'double', 40, 5)
else:
f = QgsField(key, QVariant.String, 'text', 40, 5)
return f
def dataFromExpression(self, sensor, expression, dateType='date'):
assert dateType in ['date','doy']
x = []
y = []
if not isinstance(expression, QgsExpression):
expression = QgsExpression(expression)
assert isinstance(expression, QgsExpression)
expression = QgsExpression(expression)
fields = QgsFields()
sensorTSDs = sorted([tsd for tsd in self.mData.keys() if tsd.sensor == sensor])
for tsd in sensorTSDs:
data = self.mData[tsd]
for k, v in data.items():
if v is not None and fields.fieldNameIndex(k) == -1:
fields.append(self.qgsFieldFromKeyValue(k, v))
for i, tsd in enumerate(sensorTSDs):
assert isinstance(tsd, TimeSeriesDatum)
data = self.mData[tsd]
context = QgsExpressionContext()
scope = QgsExpressionContextScope()
f = QgsFeature()
f.setFields(fields)
f.setValid(True)
if v is None:
continue
idx = f.fieldNameIndex(k)
field = f.fields().field(idx)
if field.typeName() == 'text':
v = str(v)
else:
v = float(v)
scope.setFeature(f)
context.appendScope(scope)
#value = expression.evaluatePrepared(f)
value = expression.evaluate(context)
if value in [None, NULL]:
s = ""
else:
if dateType == 'date':
x.append(date2num(tsd.date))
elif dateType == 'doy':
x.append(tsd.doy)
y.append(value)
#return np.asarray(x), np.asarray(y)
return x, y
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
def data(self, tsd):
assert isinstance(tsd, TimeSeriesDatum)
if self.hasData(tsd):
return self.mData[tsd]
else:
return {}
def loadingStatus(self):
"""
Returns the loading status in terms of single pixel values.
nLoaded = sum of single band values
nLoadedMax = potential maximum of band values that might be loaded
:return: (nLoaded, nLoadedMax)
"""
return self.mLoaded, self.mLoadedMax
def updateLoadingStatus(self):
"""
Calculates and the loading status in terms of single pixel values.
nMax is the sum of all bands over each TimeSeriesDatum and Sensors
"""
self.mLoaded = self.mLoadedMax
for tsd in self.mTimeSeries:
assert isinstance(tsd, TimeSeriesDatum)
self.mLoadedMax += tsd.sensor.nb
if self.hasData(tsd):
self.mLoaded += len([k for k in self.mData[tsd].keys() if k.startswith('b')])
def hasData(self,tsd):
assert isinstance(tsd, TimeSeriesDatum)
return tsd in self.mData.keys()
def __repr__(self):
return 'TemporalProfile {}'.format(self.mCoordinate)
class SpectralTemporalVisualization(QObject):
sigShowPixel = pyqtSignal(TimeSeriesDatum, QgsPoint, QgsCoordinateReferenceSystem)
"""
Signalizes to move to specific date of interest
"""
sigMoveToDate = pyqtSignal(np.datetime64)
def __init__(self, ui):
super(SpectralTemporalVisualization, self).__init__()
assert isinstance(ui, ProfileViewDockUI), 'arg ui of type: {} {}'.format(type(ui), str(ui))
self.ui = ui
if DEBUG:
import timeseriesviewer.pixelloader
timeseriesviewer.pixelloader.DEBUG = True
self.pixelLoader = PixelLoader()
self.pixelLoader.sigPixelLoaded.connect(self.onPixelLoaded)
self.pixelLoader.sigLoadingStarted.connect(lambda: self.ui.progressInfo.setText('Start loading...'))
self.plot_initialized = False
self.tableView2DProfiles = ui.tableView2DProfiles
self.tableView2DProfiles.setSortingEnabled(False)
# self.mSelectionModel.currentChanged.connect(self.onCurrentSelectionChanged)
self.plot2D = ui.plotWidget2D

benjamin.jakimow@geo.hu-berlin.de
committed
pi = self.plot2D.getPlotItem()
pi.getViewBox().sigMoveToDate.connect(self.sigMoveToDate)
pi.getAxis('bottom').setLabel(LABEL_TIME)
pi.getAxis('left').setLabel(LABEL_DN)
self.plot2Dvline = pg.InfiniteLine(angle=90, movable=False)
self.plot2Dhline = pg.InfiniteLine(angle=0, movable=False)
#self.plot2DLabel = pg.TextItem(text='LABEL')
self.plot2DLabel = pg.LabelItem(justify='right')
#pi.setContentsMargins(0.1, 0.1, 0.1, 0.1)
pi.addItem(self.plot2Dvline, ignoreBounds=True)
pi.addItem(self.plot2Dhline, ignoreBounds=True)
self.plot2D.addItem(self.plot2DLabel, ignoreBounds=True)
self.proxy2D = pg.SignalProxy(self.plot2D.scene().sigMouseMoved, rateLimit=60, slot=self.onMouseMoved2D)
self.plot3D = ui.plotWidget3D
self.plot3D.setCameraPosition(distance=50)
## Add a grid to the view
if OPENGL_AVAILABLE:
import pyqtgraph.opengl as gl
self.glGridItem = gl.GLGridItem()
self.glGridItem.scale(2, 2, 1)
self.glGridItem.setDepthValue(10) # draw grid after surfaces since they may be translucent
self.glPlotDataItem = []
self.plot3D.addItem(self.glGridItem)
self.tpCollection = TemporalProfileCollection()
self.tpCollectionListModel = TemporalProfileCollectionListModel(self.tpCollection)
self.ui.tableViewTemporalProfiles.setModel(self.tpCollection)
self.ui.tableViewTemporalProfiles.selectionModel().selectionChanged.connect(self.onTemporalProfileSelectionChanged)
self.ui.cbTemporalProfile3D.setModel(self.tpCollectionListModel)
#self.pxCollection.sigPixelAdded.connect(self.requestUpdate)
#self.pxCollection.sigPixelRemoved.connect(self.clear)
self.plotSettingsModel = None
self.pixelLoader.sigLoadingStarted.connect(self.clear)
self.pixelLoader.sigLoadingFinished.connect(lambda : self.plot2D.enableAutoRange('x', False))
# self.VIEW.setItemDelegateForColumn(3, PointStyleDelegate(self.VIEW))
self.plotData2D = dict()
self.plotData3D = dict()
self.updateRequested = True
self.updateTimer = QTimer(self)
self.updateTimer.timeout.connect(self.onDataUpdate)
self.sigMoveToDate.connect(self.onMoveToDate)

benjamin.jakimow@geo.hu-berlin.de
committed
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
def onMouseMoved2D(self, evt):
pos = evt[0] ## using signal proxy turns original arguments into a tuple
plotItem = self.plot2D.getPlotItem()
if plotItem.sceneBoundingRect().contains(pos):
mousePoint = plotItem.vb.mapSceneToView(pos)
x = mousePoint.x()
y = mousePoint.y()
index = int(mousePoint.x())
self.plot2DLabel.setText('Refreshed {}'.format(mousePoint.x(), mousePoint.y()))
#if index > 0 and index < len(data1):
# label.setText(
# "<span style='font-size: 12pt'>x=%0.1f, <span style='color: red'>y1=%0.1f</span>, <span style='color: green'>y2=%0.1f</span>" % (
# mousePoint.x(), data1[index], data2[index]))
self.plot2Dvline.setPos(mousePoint.x())
self.plot2Dhline.setPos(mousePoint.y())
return
vb = plotItem.vb
if plotItem.sceneBoundingRect().contains(pos):
mousePoint = vb.mapSceneToView(pos)
self.vLine.setPos(mousePoint.x())
self.hLine.setPos(mousePoint.y())
info = '{:0.2f}, {:0.2f}'.format(mousePoint.x(), mousePoint.y())
self.tbCursorLocationValue.setText(info)
"""
self.mlabel.setText(
"<span style='font-size: 12pt'>{}</span>".format(info)
"""
else:
self.tbCursorLocationValue.setText('')
self.mlabel.setText('')
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
def selected2DPlotStyles(self):
result = []
m = self.ui.tableView2DProfiles.model()
for idx in selectedModelIndices(self.ui.tableView2DProfiles):
result.append(m.idx2plotStyle(idx))
return result
def selectedTemporalProfiles(self):
result = []
m = self.ui.tableViewTemporalProfiles.model()
for idx in selectedModelIndices(self.ui.tableViewTemporalProfiles):
result.append(m.idx2tp(idx))
return result
def removePlotStyles(self, plotStyles):
m = self.ui.tableView2DProfiles.model()
if isinstance(m, PlotSettingsModel):
m.removePlotStyles(plotStyles)
def removeTemporalProfiles(self, temporalProfiles):
m = self.ui.tableViewTemporalProfiles.model()
if isinstance(m, TemporalProfileCollection):
m.removeTemporalProfiles(temporalProfiles)
def createNewPlotStyle(self):
l = len(self.tpCollection)
if l > 0:
temporalProfile = self.tpCollection[0]
plotStyle = TemporalProfilePlotStyle(temporalProfile)
plotStyle.sigExpressionUpdated.connect(self.updatePlot2D)
sensors = self.TS.Sensors.keys()
if len(sensors) > 0:
plotStyle.setSensor(sensors[0])
self.plotSettingsModel.insertPlotStyles([plotStyle])
pdi = plotStyle.createPlotItem(self.plot2D)
assert isinstance(pdi, TemporalProfilePlotDataItem)

benjamin.jakimow@geo.hu-berlin.de
committed
pdi.sigClicked.connect(self.onProfileClicked2D)
pdi.sigPointsClicked.connect(self.onPointsClicked2D)
self.plot2D.getPlotItem().addItem(pdi)
#self.plot2D.getPlotItem().addItem(pg.PlotDataItem(x=[1, 2, 3], y=[1, 2, 3]))
#plotItem.plot().sigPlotChanged.emit(plotItem)
self.updatePlot2D()

benjamin.jakimow@geo.hu-berlin.de
committed
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
def onProfileClicked2D(self, pdi):
if isinstance(pdi, TemporalProfilePlotDataItem):
sensor = pdi.mPlotStyle.sensor()
tp = pdi.mPlotStyle.temporalProfile()
if isinstance(tp, TemporalProfile) and isinstance(sensor, SensorInstrument):
info = ['Sensor:{}'.format(sensor.name()),
'Coordinate:{}, {}'.format(tp.mCoordinate.x(), tp.mCoordinate.y())]
self.ui.tbInfo2D.setPlainText('\n'.join(info))
def onPointsClicked2D(self, pdi, spottedItems):
if isinstance(pdi, TemporalProfilePlotDataItem) and isinstance(spottedItems, list):
sensor = pdi.mPlotStyle.sensor()
tp = pdi.mPlotStyle.temporalProfile()
if isinstance(tp, TemporalProfile) and isinstance(sensor, SensorInstrument):
info = ['Sensor: {}'.format(sensor.name()),
'Coordinate: {}, {}'.format(tp.mCoordinate.x(), tp.mCoordinate.y())]
for item in spottedItems:
pos = item.pos()
x = pos.x()
y = pos.y()
date = num2date(x)
info.append('Date: {}\nValue: {}'.format(date, y))
self.ui.tbInfo2D.setPlainText('\n'.join(info))
def onTemporalProfileSelectionChanged(self, selected, deselected):
self.ui.actionRemoveTemporalProfile.setEnabled(len(selected) > 0)
def onPlot2DSelectionChanged(self, selected, deselected):
self.ui.actionRemoveView.setEnabled(len(selected) > 0)
self.ui.actionRemoveView.setEnabled(False)
self.ui.actionRemoveTemporalProfile.setEnabled(False)
self.ui.btnAddView.setDefaultAction(self.ui.actionAddView)
self.ui.btnRemoveView.setDefaultAction(self.ui.actionRemoveView)
self.ui.btnRefresh2D.setDefaultAction(self.ui.actionRefresh2D)
self.ui.btnRefresh3D.setDefaultAction(self.ui.actionRefresh3D)
self.ui.btnRemoveTemporalProfile.setDefaultAction(self.ui.actionRemoveTemporalProfile)
self.ui.actionRefresh2D.triggered.connect(self.updatePlot2D)
self.ui.actionRefresh3D.triggered.connect(self.updatePlot3D)
self.ui.actionAddView.triggered.connect(self.createNewPlotStyle)
self.ui.actionRemoveView.triggered.connect(lambda:self.removePlotStyles(self.selected2DPlotStyles()))
self.ui.actionRemoveTemporalProfile.triggered.connect(lambda :self.removeTemporalProfiles(self.selectedTemporalProfiles()))
self.tpCollection.sigMaxProfilesChanged.connect(self.ui.sbMaxTP.setValue)
self.ui.sbMaxTP.valueChanged.connect(self.tpCollection.setMaxProfiles)
#todo: self.ui.actionRemoveView.triggered.connect(self.plotSettingsModel.createPlotStyle)
def setTimeSeries(self, TS):
assert isinstance(TS, TimeSeries)
self.TS = TS
self.plotSettingsModel = PlotSettingsModel(self.tpCollection, self.plot2D, parent=self)
self.plotSettingsModel.sigVisibilityChanged.connect(self.setVisibility)
self.plotSettingsModel.sigDataChanged.connect(self.requestUpdate)
self.plotSettingsModel.rowsInserted.connect(self.onRowsInserted)
# self.plotSettingsModel.modelReset.connect(self.updatePersistantWidgets)
self.tableView2DProfiles.setModel(self.plotSettingsModel)
#self.tableView2DProfilesSelectionModel = QItemSelectionModel(self.mModel)
self.tableView2DProfiles.selectionModel().selectionChanged.connect(self.onPlot2DSelectionChanged)
#self.tableView2DProfilesSelectionModel.selectionChanged.connect(self.onPlot2DSelectionChanged)
#self.tableView2DProfilesSelectionModel.setSelectionModel(self.mSelectionModel)
self.delegate = PlotSettingsWidgetDelegate(self.tableView2DProfiles, self.TS, self.tpCollectionListModel)
self.delegate.setItemDelegates(self.tableView2DProfiles)
def onMoveToDate(self, date):
dt = np.asarray([np.abs(tsd.date - date) for tsd in self.TS])
i = np.argmin(dt)
self.sigMoveToTSD.emit(self.TS[i])
def onPixelLoaded(self, nDone, nMax, d):
self.ui.progressBar.setValue(nDone)
self.ui.progressBar.setMaximum(nMax)
if d.success():
t = 'Last loaded from {}.'.format(bn)
else:
t = 'Failed loading from {}.'.format(bn)
if d.info and d.info != '':
t += '({})'.format(d.info)
def requestUpdate(self, *args):
self.updateRequested = True
#next time
def updatePersistentWidgets(self):
model = self.tableView2DProfiles.model()
if isinstance(model, PlotSettingsModel):
colExpression = model.columnIndex(model.cnExpression)
colStyle = model.columnIndex(model.cnStyle)

benjamin.jakimow@geo.hu-berlin.de
committed
for row in range(model.rowCount()):
idxExpr = model.createIndex(row, colExpression)
idxStyle = model.createIndex(row, colStyle)
#self.TV.openPersistentEditor(idxExpr)
#self.TV.openPersistentEditor(idxStyle)

benjamin.jakimow@geo.hu-berlin.de
committed
#self.TV.openPersistentEditor(model.createIndex(start, colStyle))
s = ""
def onRowsInserted(self, parent, start, end):
model = self.tableView2DProfiles.model()
if isinstance(model, PlotSettingsModel):
colExpression = model.columnIndex(model.cnExpression)
colStyle = model.columnIndex(model.cnStyle)
while start <= end:
idxExpr = model.createIndex(start, colExpression)
idxStyle = model.createIndex(start, colStyle)
self.tableView2DProfiles.openPersistentEditor(idxExpr)
self.tableView2DProfiles.openPersistentEditor(idxStyle)
start += 1
#self.TV.openPersistentEditor(model.createIndex(start, colStyle))
s = ""
def onObservationClicked(self, plotDataItem, points):
for p in points:
tsd = p.data()
def clear(self):
#first remove from pixelCollection
self.plotData2D.clear()
self.plotData3D.clear()
pi = self.plot2D.getPlotItem()
plotItems = pi.listDataItems()
for p in plotItems:
p.clear()
p.update()
if len(self.TS) > 0:
rng = [self.TS[0].date, self.TS[-1].date]
rng = [date2num(d) for d in rng]
self.plot2D.getPlotItem().setRange(xRange=rng)
if self.plot3D:
pass
def loadCoordinate(self, spatialPoints=None, LUT_bandIndices=None):
"""
Loads a temporal profile for a single or multiple geometries.
:param spatialPoints: SpatialPoint | [list-of-SpatialPoints]
"""
if not isinstance(self.plotSettingsModel, PlotSettingsModel):
return False
if not self.pixelLoader.isReadyToLoad():
return False
assert isinstance(self.TS, TimeSeries)
#Get or create the TimeSeriesProfiles which will store the loaded values
tasks = []
TPs = []
theGeometries = []
# Define a which (new) bands need to be loaded for each sensor
if LUT_bandIndices is None:
LUT_bandIndices = dict()
for sensor in self.TS.Sensors:
LUT_bandIndices[sensor] = self.plotSettingsModel.requiredBandsIndices(sensor)
assert isinstance(LUT_bandIndices, dict)
for sensor in self.TS.Sensors:
assert sensor in LUT_bandIndices.keys()
#update new / existing points
if isinstance(spatialPoints, SpatialPoint):
spatialPoints = [spatialPoints]
for spatialPoint in spatialPoints:
assert isinstance(spatialPoint, SpatialPoint)
TP = self.tpCollection.fromSpatialPoint(spatialPoint)
if not isinstance(TP, TemporalProfile):
TP = TemporalProfile(self.TS, spatialPoint)
self.tpCollection.insertTemporalProfiles(TP, i=0)
TPs.append(TP)
theGeometries.append(TP.mCoordinate)
TP_ids = [TP.mID for TP in TPs]
#each TSD is a Task
#a Task defines which bands are to be loaded
for tsd in self.TS:
#do not load from invisible TSDs
if not tsd.isVisible():
continue
#which bands do we need to load?
requiredIndices = set(LUT_bandIndices[tsd.sensor])
if len(requiredIndices) == 0:
continue
else:
s = ""
missingIndices = set()
for TP in TPs:
assert isinstance(TP, TemporalProfile)
existingBandKeys = [k for k in TP.data(tsd).keys() if PlotSettingsModel.regBandKeyExact.search(k)]
existingBandIndices = set([bandKey2bandIndex(k) for k in existingBandKeys])
need2load = requiredIndices.difference(existingBandIndices)
missingIndices = missingIndices.union(need2load)
if len(missingIndices) > 0:
task = PixelLoaderTask(tsd.pathImg, theGeometries,
bandIndices=missingIndices,
temporalProfileIDs=TP_ids)
tasks.append(task)
if len(tasks) > 0:
aGoodDefault = 2 if len(self.TS) > 25 else 1
self.pixelLoader.setNumberOfProcesses(SETTINGS.value('profileloader_threads', aGoodDefault))
if DEBUG:
print('Start loading for {} geometries from {} sources...'.format(
len(theGeometries), len(tasks)
))
self.pixelLoader.startLoading(tasks)
else:
if DEBUG:
print('Data for geometries already loaded')
def setVisibility(self, sensorPlotStyle):
assert isinstance(sensorPlotStyle, TemporalProfilePlotStyle)
self.setVisibility2D(sensorPlotStyle)
def setVisibility2D(self, sensorPlotStyle):
self.plot2D.update()
if sensorView is None:
for sv in self.plotSettingsModel.items:
self.setData(sv)
else:
assert isinstance(sensorView, TemporalProfilePlotStyle)
self.setData2D(sensorView)
def onDataUpdate(self):
Loading
Loading full blame...