Skip to content
Snippets Groups Projects
profilevisualization.py 84.4 KiB
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 collections import OrderedDict
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
Benjamin Jakimow's avatar
Benjamin Jakimow committed
from timeseriesviewer.pixelloader import PixelLoader, PixelLoaderTask
from timeseriesviewer.sensorvisualization import SensorListModel

Benjamin Jakimow's avatar
Benjamin Jakimow committed
from pyqtgraph import functions as fn
from pyqtgraph import AxisItem


import datetime

from osgeo import gdal, gdal_array
import numpy as np

DEBUG = False

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):
        super(DateTimeAxis, self).__init__(*args, **kwds)
        self.setRange(1,3000)
        self.enableAutoSIPrefix(False)
        self.labelAngle = 0
    def logTickStrings(self, values, scale, spacing):
        s = ""

Benjamin Jakimow's avatar
Benjamin Jakimow committed

    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]
        ndays = rng.astype(int)

        strns = []

        for v in values:
            if ndays == 0:
                strns.append(v.astype(str))
    def tickValues(self, minVal, maxVal, size):
        d = super(DateTimeAxis, self).tickValues(minVal, maxVal, size)
    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
Benjamin Jakimow's avatar
Benjamin Jakimow committed


class _SensorPoints(pg.PlotDataItem):
    def __init__(self, *args, **kwds):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        super(_SensorPoints, self).__init__(*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()
        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.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


Benjamin Jakimow's avatar
Benjamin Jakimow committed
class TemporalProfilePlotDataItem(pg.PlotDataItem):

    def __init__(self, plotStyle, parent=None):
        assert isinstance(plotStyle, TemporalProfilePlotStyle)


        super(TemporalProfilePlotDataItem, self).__init__([], [], parent=parent)
        self.menu = None
        #self.setFlags(QGraphicsItem.ItemIsSelectable)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mPlotStyle = plotStyle
        self.setAcceptedMouseButtons(Qt.LeftButton | Qt.RightButton)
        self.mPlotStyle.sigUpdated.connect(self.updateDataAndStyle)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.updateStyle()

    # 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):

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        TP = self.mPlotStyle.temporalProfile()
        sensor = self.mPlotStyle.sensor()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            if len(y) > 0:
                self.setData(x=x, y=y)
            else:
                self.setData(x=[], y=[]) #dummy
        self.updateStyle()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    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
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.setVisible(self.mPlotStyle.isVisible())
        self.setSymbol(self.mPlotStyle.markerSymbol)
        self.setSymbolSize(self.mPlotStyle.markerSize)
        self.setSymbolBrush(self.mPlotStyle.markerBrush)
        self.setSymbolPen(self.mPlotStyle.markerPen)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.setPen(self.mPlotStyle.linePen)
        self.update()

        #self.setPen(fn.mkPen(self.mPlotStyle.linePen))
        #self.setFillBrush(fn.mkBrush(self.mPlotStyle.mExpression))
        #self.setSymbolBrush(fn.mkBrush(self.mPlotStyle.markerBrush))
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        # self.setFillBrush(self.mPlotStyle.)

        #self.update()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    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):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    """
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    """
    def __init__(self, tableView, timeSeries, temporalProfileListModel, parent=None):

        super(PlotSettingsWidgetDelegate, self).__init__(parent=parent)
        self._preferedSize = QgsFieldExpressionWidget().sizeHint()
        self.tableView = tableView
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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]:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            i = model.columNames.index(c)
            tableView.setItemDelegateForColumn(i, self)

    def getColumnName(self, index):
        assert index.isValid()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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.')
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                    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()
    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()))
        if isinstance(w, PlotStyleButton):

            self.commitData.emit(w)

    def setEditorData(self, editor, index):
        cname = self.getColumnName(index)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        model = self.tableView.model()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if index.isValid() and isinstance(model, PlotSettingsModel):
            if cname == model.cnExpression:
                assert isinstance(w, QgsFieldExpressionWidget)
                expr = w.asExpression()
                exprLast = model.data(index, Qt.DisplayRole)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

            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()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

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()


Benjamin Jakimow's avatar
Benjamin Jakimow committed
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())

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    A collection to store the TemporalProfile data delivered by a PixelLoader
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    #sigSensorAdded = pyqtSignal(SensorInstrument)
    #sigSensorRemoved = pyqtSignal(SensorInstrument)
    #sigPixelAdded = pyqtSignal()
    #sigPixelRemoved = pyqtSignal()
    sigTemporalProfilesAdded = pyqtSignal(list)
    sigTemporalProfilesRemoved = pyqtSignal(list)
    sigMaxProfilesChanged = pyqtSignal(int)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __init__(self, ):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        super(TemporalProfileCollection, self).__init__()
        #self.sensorPxLayers = dict()
        #self.memLyrCrs = QgsCoordinateReferenceSystem('EPSG:4326')
        self.newDataFlag = False

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mTemporalProfiles = []
        self.mTPLookupSpatialPoint = {}
        self.mTPLookupID = {}
        self.mCurrentTPID = 0
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mMaxProfiles = 10

        self.nextID = 0

    def __len__(self):
        return len(self.mTemporalProfiles)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __iter__(self):
        return iter(self.mTemporalProfiles)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __getitem__(self, slice):
        return self.mTemporalProfiles[slice]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    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) :
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            return self.mTemporalProfiles[index.row()]
        return None

    def tp2idx(self, temporalProfile):
        assert isinstance(temporalProfile, TemporalProfile)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if temporalProfile in self.mTemporalProfiles:
            row = self.mTemporalProfiles.index(temporalProfile)
            return self.createIndex(row, 0)
        else:
            return QModelIndex()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    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)

Benjamin Jakimow's avatar
Benjamin Jakimow committed

    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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

            for temporalProfile in temporalProfiles:
                assert isinstance(temporalProfile, TemporalProfile)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                idx = self.tp2idx(temporalProfile)
                row = idx.row()
                self.beginRemoveRows(QModelIndex(), row, row)
                self.mTemporalProfiles.remove(temporalProfile)

                deleteFromDict(self.mTPLookupID, temporalProfile)
                deleteFromDict(self.mTPLookupSpatialPoint,  temporalProfile)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
                self.endRemoveRows()
            self.sigTemporalProfilesRemoved.emit(temporalProfiles)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def connectTimeSeries(self, timeSeries):
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        if isinstance(timeSeries, TimeSeries):
            self.TS = timeSeries
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            #for sensor in self.TS.Sensors:
            #    self.addSensor(sensor)
            #self.TS.sigSensorAdded.connect(self.addSensor)
            #self.TS.sigSensorRemoved.connect(self.removeSensor)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        else:
            self.TS = None

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    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

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert n >= 1
        if old != n:
            self.mMaxProfiles = n

            self.prune()
            self.sigMaxProfilesChanged.emit(self.mMaxProfiles)
Benjamin Jakimow's avatar
Benjamin Jakimow committed


    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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def sort(self, col, order):
        if self.rowCount() == 0:
            return
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.layoutAboutToBeChanged.emit()
        colName = self.mColumNames[col]
        r = order != Qt.AscendingOrder
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def addPixelLoaderResult(self, d):
        assert isinstance(d, PixelLoaderTask)
        if d.success():
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            for i, TPid in enumerate(d.temporalProfileIDs):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                TP = self.temporalProfileFromID(TPid)
                tsd = self.TS.getTSD(d.sourcePath)
                assert isinstance(tsd, TimeSeriesDatum)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                if isinstance(TP, TemporalProfile):
                    profileData = d.resProfiles[i]
                    if not isinstance(profileData, tuple):
                        s = ""
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                    vMean, vStd = profileData
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                    values = {}
                    validValues = not isinstance(vMean, str)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                    #1. add the pixel values per returned band
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                    for iBand, bandIndex in enumerate(d.bandIndices):
                        key = 'b{}'.format(bandIndex + 1)
                        values[key] = vMean[iBand] if validValues else None
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                        key = 'std{}'.format(bandIndex + 1)
                        values[key] = vStd[iBand] if validValues else None
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                    #indicesY, indicesX = d.imagePixelIndices()
                    #values['px_x'] = indicesX
                    #values['px_y'] = indicesY
                    TP.updateData(tsd, values)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #todo: remove TS Profiles
        #self.mTemporalProfiles.clear()
        #self.sensorPxLayers.clear()
        pass
Benjamin Jakimow's avatar
Benjamin Jakimow committed
class TemporalProfilePlotStyle(PlotStyle):
    sigExpressionUpdated = pyqtSignal()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __init__(self, temporalProfile):
        super(TemporalProfilePlotStyle, self).__init__()
        assert isinstance(temporalProfile, TemporalProfile)
        self.mSensor = None
        self.mTP = temporalProfile
        self.mExpression = u'"b1"'
        self.mPlotItems = []
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if isinstance(temporalProfile, TemporalProfile):
            self.setTemporalProfile(temporalProfile)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def createPlotItem(self, plotWidget):
        pdi = TemporalProfilePlotDataItem(self)
        self.mPlotItems.append(pdi)
        return pdi
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def temporalProfile(self):
        return self.mTP
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def setTemporalProfile(self, temporalPofile):
        assert isinstance(temporalPofile, TemporalProfile)
        b = temporalPofile != self.mTP
        self.mTP = temporalPofile
        if b: self.update()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def setSensor(self, sensor):
        assert isinstance(sensor, SensorInstrument)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        b = sensor != self.mSensor
        if b: self.update()


    def update(self):
        super(TemporalProfilePlotStyle, self).update()

        for pdi in self.mPlotItems:
            assert isinstance(pdi, TemporalProfilePlotDataItem)
            pdi.updateStyle()
            #pdi.updateItems()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def sensor(self):
        return self.mSensor


    def setExpression(self, exp):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(exp, unicode)
        b = self.mExpression != exp
        if b:
            self.update()
            self.sigExpressionUpdated.emit()
    def __reduce_ex__(self, protocol):
        return self.__class__, (), self.__getstate__()

    def __getstate__(self):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        result = super(TemporalProfilePlotStyle, self).__getstate__()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        del result['mTP']
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())
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.plotItem = pg.PlotItem(
            axisItems={'bottom':DateTimeAxis(orientation='bottom')},
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            viewBox=DateTimeViewBox()
        )
        self.setCentralItem(self.plotItem)
        self.xAxisInitialized = False
Benjamin Jakimow's avatar
Benjamin Jakimow committed

class PlotSettingsModel(QAbstractTableModel):

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    #sigSensorAdded = pyqtSignal(SensorPlotSettings)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    sigVisibilityChanged = pyqtSignal(TemporalProfilePlotStyle)
    sigDataChanged = pyqtSignal(TemporalProfilePlotStyle)
    sigPlotStylesAdded = pyqtSignal(list)
    sigPlotStylesRemoved = pyqtSignal(list)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    regBandKey = re.compile("(?<!\w)b\d+(?!\w)", re.IGNORECASE)
    regBandKeyExact = re.compile('^' + regBandKey.pattern + '$', re.IGNORECASE)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __init__(self, temporalProfileCollection, plotWidget, parent=None, *args):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        super(PlotSettingsModel, self).__init__(parent=parent)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(temporalProfileCollection, TemporalProfileCollection)

        self.cnID = 'ID'
        self.cnSensor = 'sensor'
        self.cnExpression = LABEL_DN
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.tpCollection = temporalProfileCollection
        #self.tpCollection.sigTemporalProfilesRemoved.connect(lambda removedTPs : self.removePlotStyles([p for p in self.mPlotSettings if p.temporalProfile() in removedTPs]))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __len__(self):
        return len(self.mPlotSettings)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __iter__(self):
        return iter(self.mPlotSettings)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __getitem__(self, slice):
        return self.mPlotSettings[slice]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __contains__(self, item):
        return item in self.mPlotSettings
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def testSlot(self, *args):
        print(('TESTSLOT', args))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def columnIndex(self, name):
        return self.columNames.index(name)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def signaler(self, idxUL, idxLR):
        if idxUL.isValid():
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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's avatar
Benjamin Jakimow committed
                bandIndices.add(bandIndex)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        return bandIndices
Benjamin Jakimow's avatar
Benjamin Jakimow 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's avatar
Benjamin Jakimow committed
        if i is None:
            i = len(self.mPlotSettings)
        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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    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,

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def rowCount(self, parent = QModelIndex()):
        return len(self.mPlotSettings)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def removeRows(self, row, count , parent = QModelIndex()):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.beginRemoveRows(parent, row, row + count-1)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        toRemove = self.mPlotSettings[row:row + count]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            self.mPlotSettings.remove(tsd)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def plotStyle2idx(self, plotStyle):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(plotStyle, TemporalProfilePlotStyle)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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()):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        return len(self.columNames)

    def data(self, index, role = Qt.DisplayRole):
        if role is None or not index.isValid():
            return None

        value = None
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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's avatar
Benjamin Jakimow committed
        columnName = self.columNames[index.column()]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        result = False
        plotStyle = self.idx2plotStyle(index)
        if isinstance(plotStyle, TemporalProfilePlotStyle):
            if role in [Qt.DisplayRole]:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                if columnName == self.cnExpression:
                    plotStyle.setExpression(value)
                    result = True
                elif columnName == self.cnStyle:
                    if isinstance(value, PlotStyle):
                        plotStyle.copyFrom(value)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                        result = True

            if role == Qt.CheckStateRole:
                if columnName == self.cnTemporalProfile:
                    plotStyle.setVisibility(value == Qt.Checked)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            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
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            self.savePlotSettings(plotStyle, index='DEFAULT')
            self.dataChanged.emit(index, index)

        return result

    def savePlotSettings(self, sensorPlotSettings, index='DEFAULT'):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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'):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        return None

        #todo
        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
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if isinstance(sensorPlotSettings, TemporalProfilePlotStyle):
            return sensorPlotSettings
    def flags(self, index):
        if index.isValid():
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            columnName = self.columNames[index.column()]
            flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            if columnName in [self.cnTemporalProfile]:
                flags = flags | Qt.ItemIsUserCheckable
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            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:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            return self.columNames[col]
        elif orientation == Qt.Vertical and role == Qt.DisplayRole:
            return col
        return None



Benjamin Jakimow's avatar
Benjamin Jakimow committed
class ProfileViewDockUI(QgsDockWidget, loadUI('profileviewdock.ui')):
    def __init__(self, parent=None):
        super(ProfileViewDockUI, self).__init__(parent)
        self.setupUi(self)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #self.line.setVisible(False)
        #self.listWidget.setVisible(False)
        self.stackedWidget.setCurrentWidget(self.page2D)
        self.plotWidget3D = None
            l = self.layout3DPlotWidget

            from pyqtgraph.opengl import GLViewWidget
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            self.plotWidget3D = GLViewWidget(parent=self.page3D)
            self.plotWidget3D.setObjectName('plotWidget3D')
            size = self.labelDummy3D.size()
            l.removeWidget(self.labelDummy3D)
            #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
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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))

    #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)
    return float(d.year) + fraction

def num2date(n, dt64=True):
    n = float(n)

    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 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
def depr_num2date(n):
    n = int(np.round(n))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    d = datetime.datetime.fromordinal(n)
    d = d.date()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    return np.datetime64('{:04}-{:02}-{:02}'.format(d.year,d.month,d.day), 'D')

class TemporalProfile(QObject):

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    _mNextID = 0
    @staticmethod
    def nextID():
        n = TemporalProfile._mNextID
        TemporalProfile._mNextID += 1
        return n

    def __init__(self, timeSeries, spatialPoint):
        super(TemporalProfile, self).__init__()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(timeSeries, TimeSeries)
        assert isinstance(spatialPoint, SpatialPoint)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mTimeSeries = timeSeries
        self.mCoordinate = spatialPoint
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mID = TemporalProfile.nextID()
        self.mData = {}
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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)}
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            self.updateData(tsd, meta)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    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_()

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def updateData(self, tsd, values):
        assert isinstance(tsd, TimeSeriesDatum)
        assert isinstance(values, dict)

        if tsd not in self.mData.keys():
            self.mData[tsd] = {}

        self.mData[tsd].update(values)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.updateLoadingStatus()
        self.mUpdated = True

    def resetUpdated(self):
        self.mUpdated = False

    def updated(self):
        return self.mUpdated
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def qgsFieldFromKeyValue(self, key, value):
        t = type(value)
        if t in [int, float] or np.isreal(value):
Benjamin Jakimow's avatar
Benjamin Jakimow committed

            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 = []
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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))
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            for k, v in data.items():
                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)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
                f.setAttribute(k,v)

            scope.setFeature(f)
            context.appendScope(scope)
            #value = expression.evaluatePrepared(f)
            value = expression.evaluate(context)


            if value in [None, NULL]:
                s = ""
            else:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                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)
        assert len(x) == len(y)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    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)
        super(SpectralTemporalVisualization, self).__init__()

        assert isinstance(ui, ProfileViewDockUI), 'arg ui of type: {} {}'.format(type(ui), str(ui))
        if DEBUG:
            import timeseriesviewer.pixelloader
            timeseriesviewer.pixelloader.DEBUG = True

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.TS = None
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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
        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)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.tpCollection = TemporalProfileCollection()
        self.tpCollectionListModel = TemporalProfileCollectionListModel(self.tpCollection)

        self.ui.tableViewTemporalProfiles.setModel(self.tpCollection)
        self.ui.tableViewTemporalProfiles.selectionModel().selectionChanged.connect(self.onTemporalProfileSelectionChanged)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        self.ui.cbTemporalProfile3D.setModel(self.tpCollectionListModel)
        #self.pxCollection.sigPixelAdded.connect(self.requestUpdate)
        #self.pxCollection.sigPixelRemoved.connect(self.clear)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        self.plotSettingsModel = None
        self.pixelLoader.sigLoadingStarted.connect(self.clear)
        self.pixelLoader.sigLoadingFinished.connect(lambda : self.plot2D.enableAutoRange('x', False))
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed

        # 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.updateTimer.start(5000)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        self.sigMoveToDate.connect(self.onMoveToDate)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.initActions()


    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('')



    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)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def createNewPlotStyle(self):
        l = len(self.tpCollection)
        if l > 0:
            temporalProfile = self.tpCollection[0]
            plotStyle = TemporalProfilePlotStyle(temporalProfile)
            plotStyle.sigExpressionUpdated.connect(self.updatePlot2D)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            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)
            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]))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            #plotItem.addDataItem(pdi)
            #plotItem.plot().sigPlotChanged.emit(plotItem)
            self.updatePlot2D()

Benjamin Jakimow's avatar
Benjamin Jakimow committed

    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))
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def onTemporalProfileSelectionChanged(self, selected, deselected):
        self.ui.actionRemoveTemporalProfile.setEnabled(len(selected) > 0)

    def onPlot2DSelectionChanged(self, selected, deselected):

        self.ui.actionRemoveView.setEnabled(len(selected) > 0)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def initActions(self):

        self.ui.actionRemoveView.setEnabled(False)
        self.ui.actionRemoveTemporalProfile.setEnabled(False)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #todo: self.ui.actionRemoveView.triggered.connect(self.plotSettingsModel.createPlotStyle)


    def setTimeSeries(self, TS):
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        assert isinstance(TS, TimeSeries)
        self.TS = TS

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.tpCollection.connectTimeSeries(self.TS)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        # 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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    sigMoveToTSD = pyqtSignal(TimeSeriesDatum)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    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])
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def onPixelLoaded(self, nDone, nMax, d):
        self.ui.progressBar.setValue(nDone)
        self.ui.progressBar.setMaximum(nMax)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(d, PixelLoaderTask)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        bn = os.path.basename(d.sourcePath)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if d.success():
            t = 'Last loaded from {}.'.format(bn)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            self.tpCollection.addPixelLoaderResult(d)
            self.updateRequested = True
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        else:
            t = 'Failed loading from {}.'.format(bn)
            if d.info and d.info != '':
                t += '({})'.format(d.info)
        # QgsApplication.processEvents()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.ui.progressInfo.setText(t)

    def requestUpdate(self, *args):
        self.updateRequested = True
        #next time

    def updatePersistentWidgets(self):
        model = self.tableView2DProfiles.model()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if isinstance(model, PlotSettingsModel):
            colExpression = model.columnIndex(model.cnExpression)
            colStyle = model.columnIndex(model.cnStyle)

            for row in range(model.rowCount()):
                idxExpr = model.createIndex(row, colExpression)
                idxStyle = model.createIndex(row, colStyle)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

                #self.TV.openPersistentEditor(idxExpr)
                #self.TV.openPersistentEditor(idxStyle)

                #self.TV.openPersistentEditor(model.createIndex(start, colStyle))
            s = ""

    def onRowsInserted(self, parent, start, end):
        model = self.tableView2DProfiles.model()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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()
            #print(tsd)

    def clear(self):
        #first remove from pixelCollection
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.tpCollection.prune()

        return

        self.plotData2D.clear()
        self.plotData3D.clear()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        pi = self.plot2D.getPlotItem()
        plotItems = pi.listDataItems()
        for p in plotItems:
            p.clear()
            p.update()

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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)
        #QApplication.processEvents()
    def loadCoordinate(self, spatialPoints=None, LUT_bandIndices=None):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        """
        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
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        assert isinstance(self.TS, TimeSeries)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #Get or create the TimeSeriesProfiles which will store the loaded values

        tasks = []
        TPs = []
        theGeometries = []
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        # 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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        assert isinstance(LUT_bandIndices, dict)
        for sensor in self.TS.Sensors:
            assert sensor in LUT_bandIndices.keys()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        #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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            TPs.append(TP)
            theGeometries.append(TP.mCoordinate)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        TP_ids = [TP.mID for TP in TPs]
        #each TSD is a Task
        #a Task defines which bands are to be loaded
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            #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()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            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])
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                need2load = requiredIndices.difference(existingBandIndices)
                missingIndices = missingIndices.union(need2load)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            missingIndices = sorted(list(missingIndices))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            if len(missingIndices) > 0:
                task = PixelLoaderTask(tsd.pathImg, theGeometries,
                                       bandIndices=missingIndices,
                                       temporalProfileIDs=TP_ids)
                tasks.append(task)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(sensorPlotStyle, TemporalProfilePlotStyle)
        self.setVisibility2D(sensorPlotStyle)
    def setVisibility2D(self, sensorPlotStyle):
    def addData(self, sensorView = None):

        if sensorView is None:
            for sv in self.plotSettingsModel.items:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            assert isinstance(sensorView, TemporalProfilePlotStyle)
    @QtCore.pyqtSlot()
    def onDataUpdate(self):

Loading
Loading full blame...