Skip to content
Snippets Groups Projects
profilevisualization.py 81.3 KiB
Newer Older
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):
        assert isinstance(plotStyle, TemporalProfile2DPlotStyle)
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()):
        return len(self.columnNames)

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

        value = None
        columnName = self.columnNames[index.column()]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        plotStyle = self.idx2plotStyle(index)
        if isinstance(plotStyle, TemporalProfile2DPlotStyle):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            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:
                    tp = plotStyle.temporalProfile()
                    if isinstance(tp, TemporalProfile):
                        value = tp.name()
                    else:
                        value = 'undefined'
Benjamin Jakimow's avatar
Benjamin Jakimow committed

            #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))
        columnName = self.columnNames[index.column()]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        result = False
        plotStyle = self.idx2plotStyle(index)
        if isinstance(plotStyle, TemporalProfile2DPlotStyle):
            if role in [Qt.DisplayRole]:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                if columnName == self.cnExpression:
                    plotStyle.setExpression(value)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                    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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                    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, TemporalProfile2DPlotStyle)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #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
        if isinstance(sensorPlotSettings, TemporalProfile2DPlotStyle):
            return sensorPlotSettings
    def flags(self, index):
        if index.isValid():
            columnName = self.columnNames[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:
            return self.columnNames[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)
        self.addActions(self.findChildren(QAction))

        self.mActions2D = [self.actionAddStyle2D, self.actionRemoveStyle2D, self.actionRefresh2D, self.actionReset2DPlot]
        self.mActions3D = [self.actionAddStyle3D, self.actionRemoveStyle3D, self.actionRefresh3D,
                           self.actionReset3DCamera]
        self.mActionsTP = [self.actionLoadTPFromOgr, self.actionSaveTemporalProfiles, self.actionRemoveTemporalProfile, self.actionLoadMissingValues]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #self.line.setVisible(False)
        #self.listWidget.setVisible(False)
        self.baseTitle = self.windowTitle()
        self.stackedWidget.currentChanged.connect(self.onStackPageChanged)
        self.stackedWidget.setCurrentWidget(self.page2D)
        self.plotWidget3D = None
        self.init3DWidgets('gl')
        #pi = self.plotWidget2D.plotItem
        #ax = DateAxis(orientation='bottom', showValues=True)
        #pi.layout.addItem(ax, 3,2)
        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.Interactive)
    def init3DWidgets(self, mode='gl'):
        assert mode in ['gl']
        if ENABLE_OPENGL and OPENGL_AVAILABLE and mode == 'gl':

            from timeseriesviewer.temporalprofiles3dGL import ViewWidget3D
            self.plotWidget3D = ViewWidget3D(parent=self.labelDummy3D.parent())
            self.plotWidget3D.setObjectName('plotWidget3D')

            size = self.labelDummy3D.size()
            l.addWidget(self.plotWidget3D)
            self.plotWidget3D.setSizePolicy(self.labelDummy3D.sizePolicy())
            self.labelDummy3D.setVisible(False)
            l.removeWidget(self.labelDummy3D)
            #self.plotWidget3D.setBaseSize(size)
            self.splitter3D.setSizes([100, 100])
            self.frameSettings3D.setEnabled(True)
        else:
            self.frameSettings3D.setEnabled(False)
    def onStackPageChanged(self, i):
        w = self.stackedWidget.currentWidget()
        title = self.baseTitle
        if w == self.page2D:
            title = '{} | 2D'.format(title)
            for a in self.mActions2D:
                a.setVisible(True)
            for a in self.mActions3D + self.mActionsTP:
                a.setVisible(False)
            title = '{} | 3D (experimental!)'.format(title)
            for a in self.mActions2D + self.mActionsTP:
                a.setVisible(False)
            for a in self.mActions3D:
                a.setVisible(True)
            title = '{} | Coordinates'.format(title)
            for a in self.mActions2D + self.mActions3D:
                a.setVisible(False)
            for a in self.mActionsTP:
                a.setVisible(True)

        w.update()
NEXT_COLOR_HUE_DELTA_CON = 10
NEXT_COLOR_HUE_DELTA_CAT = 100
def nextColor(color, mode='cat'):
    """
    Returns another color
    :param color: the previous color
    :param mode: 'cat' - for categorical color jump (next color looks pretty different to previous)
                 'con' - for continuous color jump (next color looks similar to previous)
    :return:
    """
    assert mode in ['cat','con']
    assert isinstance(color, QColor)
    hue, sat, value, alpha = color.getHsl()
    if mode == 'cat':
        hue += NEXT_COLOR_HUE_DELTA_CAT
    elif mode == 'con':
        hue += NEXT_COLOR_HUE_DELTA_CON
    if sat == 0:
        sat = 255
        value = 128
        alpha = 255
    return QColor.fromHsl(hue, sat, value, alpha)
class SpectralTemporalVisualization(QObject):

    sigShowPixel = pyqtSignal(TimeSeriesDatum, QgsPoint, QgsCoordinateReferenceSystem)

    """
    Signalizes to move to specific date of interest
    """
    sigMoveToDate = pyqtSignal(np.datetime64)
    def __init__(self, timeSeries, profileDock):
        super(SpectralTemporalVisualization, self).__init__()
        assert isinstance(profileDock, ProfileViewDockUI)
        self.ui = profileDock
        import timeseriesviewer.pixelloader
            timeseriesviewer.pixelloader.DEBUG = True

        #the timeseries. will be set later
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(timeSeries, TimeSeries)
        self.TS = timeSeries
        self.plot_initialized = False
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.plot2D = self.ui.plotWidget2D
        self.plot2D.getViewBox().sigMoveToDate.connect(self.sigMoveToDate)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.plot3D = self.ui.plotWidget3D
        # temporal profile collection to store loaded values
        self.mTemporalProfileLayer = TemporalProfileLayer(self.TS)
        self.mTemporalProfileLayer.sigTemporalProfilesAdded.connect(self.onTemporalProfilesAdded)
        self.mTemporalProfileLayer.startEditing()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mTemporalProfileLayer.selectionChanged.connect(self.onTemporalProfileSelectionChanged)
        #self.tpCollectionListModel = TemporalProfileCollectionListModel(self.tpCollection)
        self.tpModel = TemporalProfileTableModel(self.mTemporalProfileLayer)
        self.tpFilterModel = TemporalProfileTableFilterModel(self.tpModel)

        self.ui.tableViewTemporalProfiles.setModel(self.tpFilterModel)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #self.ui.tableViewTemporalProfiles.selectionModel().selectionChanged.connect(self.onTemporalProfileSelectionChanged)
        self.ui.tableViewTemporalProfiles.horizontalHeader().setResizeMode(QHeaderView.Interactive)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.ui.tableViewTemporalProfiles.setSortingEnabled(False)
        #self.ui.tableViewTemporalProfiles.contextMenuEvent = self.onTemporalProfilesContextMenu
        # pixel loader to load pixel values in parallel
        config = QgsAttributeTableConfig()
        config.update(self.mTemporalProfileLayer.fields())
        config.setActionWidgetVisible(True)

        hidden = [FN_ID]
        for i, columnConfig in enumerate(config.columns()):

            assert isinstance(columnConfig, QgsAttributeTableConfig.ColumnConfig)
            config.setColumnHidden(i, columnConfig.name in hidden)


        self.mTemporalProfilesTableConfig = config
        self.mTemporalProfileLayer.setAttributeTableConfig(self.mTemporalProfilesTableConfig)
        self.tpFilterModel.setAttributeTableConfig(self.mTemporalProfilesTableConfig)
        #self.ui.tableViewTemporalProfiles.setAttributeTable(self.mTemporalProfilesTableConfig)
        self.pixelLoader = PixelLoader()
        self.pixelLoader.sigPixelLoaded.connect(self.onPixelLoaded)
        self.pixelLoader.sigLoadingStarted.connect(lambda: self.ui.progressInfo.setText('Start loading...'))
        #self.pixelLoader.sigLoadingStarted.connect(self.tpCollection.prune)
        self.pixelLoader.sigLoadingFinished.connect(lambda : self.plot2D.enableAutoRange('x', False))

        #set the plot models for 2D
        self.plotSettingsModel2D = PlotSettingsModel2D()
        self.ui.tableView2DProfiles.setModel(self.plotSettingsModel2D)
        self.ui.tableView2DProfiles.setSortingEnabled(False)
        self.ui.tableView2DProfiles.selectionModel().selectionChanged.connect(self.onPlot2DSelectionChanged)
        self.ui.tableView2DProfiles.horizontalHeader().setResizeMode(QHeaderView.Interactive)
        self.plotSettingsModel2D.sigDataChanged.connect(self.requestUpdate)
        self.plotSettingsModel2D.rowsInserted.connect(self.onRowsInserted2D)
        self.delegateTableView2D = PlotSettingsModel2DWidgetDelegate(self.ui.tableView2DProfiles, self.mTemporalProfileLayer)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.delegateTableView2D.setItemDelegates(self.ui.tableView2DProfiles)


        # set the plot models for 3D
        self.plotSettingsModel3D = PlotSettingsModel3D()
        self.ui.tableView3DProfiles.setModel(self.plotSettingsModel3D)
        self.ui.tableView3DProfiles.setSortingEnabled(False)
        self.ui.tableView2DProfiles.selectionModel().selectionChanged.connect(self.onPlot3DSelectionChanged)
        self.ui.tableView3DProfiles.horizontalHeader().setResizeMode(QHeaderView.Interactive)
        self.plotSettingsModel3D.rowsInserted.connect(self.onRowsInserted3D)
        self.delegateTableView3D = PlotSettingsModel3DWidgetDelegate(self.ui.tableView3DProfiles, self.mTemporalProfileLayer)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.delegateTableView3D.setItemDelegates(self.ui.tableView3DProfiles)
        if not ENABLE_OPENGL:
            self.ui.listWidget.item(1).setHidden(True)
            self.ui.page3D.setHidden(True)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        def temporalProfileLayer():
            return self.mTemporalProfileLayer
Benjamin Jakimow's avatar
Benjamin Jakimow committed


        def onTemporalProfilesRemoved(removedProfiles):
            #set to valid temporal profile

            affectedStyles2D = [p for p in self.plotSettingsModel2D if p.temporalProfile() in removedProfiles]
            affectedStyles3D = [p for p in self.plotSettingsModel3D if p.temporalProfile() in removedProfiles]
            alternativeProfile = self.mTemporalProfileLayer[-1] if len(self.mTemporalProfileLayer) > 0 else None
            for s in affectedStyles2D:
                assert isinstance(s, TemporalProfile2DPlotStyle)
                m = self.plotSettingsModel2D
                idx = m.plotStyle2idx(s)
                idx = m.createIndex(idx.row(), m.columnNames.index(m.cnTemporalProfile))
                m.setData(idx, alternativeProfile, Qt.EditRole)

            for s in affectedStyles3D:
                assert isinstance(s, TemporalProfile3DPlotStyle)
                m = self.plotSettingsModel3D
                idx = m.plotStyle2idx(s)
                idx = m.createIndex(idx.row(), m.columnNames.index(m.cnTemporalProfile))
                m.setData(idx, alternativeProfile, Qt.EditRole)

        self.mTemporalProfileLayer.sigTemporalProfilesRemoved.connect(onTemporalProfilesRemoved)
        # remove / add plot style
        def on2DPlotStyleRemoved(plotStyles):
            for plotStyle in plotStyles:
                assert isinstance(plotStyle, PlotStyle)
                for pi in plotStyle.mPlotItems:
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        def on3DPlotStyleRemoved(plotStyles):
            toRemove = []
            for plotStyle in plotStyles:
                assert isinstance(plotStyle, TemporalProfile3DPlotStyle)
                toRemove.append(plotStyle.mPlotItems)
            self.plot3D.removeItems(toRemove)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        def onMaxProfilesChanged(n):
            v = self.ui.sbMaxTP.value()
            if v != n:
                self.ui.sbMaxTP.setValue(n)

        self.mTemporalProfileLayer.sigMaxProfilesChanged.connect(onMaxProfilesChanged)
        self.mTemporalProfileLayer.setMaxProfiles(SETTINGS.value('max_temporalprofiles', 64))
        self.plotSettingsModel2D.sigPlotStylesRemoved.connect(on2DPlotStyleRemoved)
        self.plotSettingsModel3D.sigPlotStylesRemoved.connect(on3DPlotStyleRemoved)
        #initialize the update loop
        self.updateRequested = True
        self.updateTimer = QTimer(self)
        self.updateTimer.timeout.connect(self.onDataUpdate)
        self.updateTimer.start(2000)

        self.sigMoveToDate.connect(self.onMoveToDate)

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

    def temporalProfileLayer(self):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        """
        Returns a QgsVectorLayer that is used to store profile coordinates.
        :return:
        """
        return self.mTemporalProfileLayer
    def onTemporalProfilesContextMenu(self, event):
        assert isinstance(event, QContextMenuEvent)
        tableView = self.ui.tableViewTemporalProfiles
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        selectionModel = self.ui.tableViewTemporalProfiles.selectionModel()
        assert isinstance(selectionModel, QItemSelectionModel)

        model = self.ui.tableViewTemporalProfiles.model()
        assert isinstance(model, TemporalProfileLayer)
        temporalProfiles = []

        if len(selectionModel.selectedIndexes()) > 0:
            for idx in selectionModel.selectedIndexes():
                tp = model.idx2tp(idx)
                if isinstance(tp, TemporalProfile) and not tp in temporalProfiles:
                    temporalProfiles.append(tp)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        else:
            temporalProfiles = model[:]

        spatialPoints = [tp.coordinate() for tp in temporalProfiles]


        menu = QMenu()

        a = menu.addAction('Load missing')
        a.setToolTip('Loads missing band-pixels.')
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        a.triggered.connect(lambda : self.loadCoordinate(spatialPoints=spatialPoints, mode='all'))
        s = ""

        a = menu.addAction('Reload')
        a.setToolTip('Reloads all band-pixels.')
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        a.triggered.connect(lambda: self.loadCoordinate(spatialPoints=spatialPoints, mode='reload'))

        menu.popup(tableView.viewport().mapToGlobal(event.pos()))
        self.menu = menu


    def selected2DPlotStyles(self):
        result = []

        m = self.ui.tableView2DProfiles.model()
        for idx in selectedModelIndices(self.ui.tableView2DProfiles):
            result.append(m.idx2plotStyle(idx))
        return result

Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def removePlotStyles2D(self, plotStyles):
        m = self.ui.tableView2DProfiles.model()
        if isinstance(m, PlotSettingsModel2D):
            m.removePlotStyles(plotStyles)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def removeTemporalProfiles(self, fids):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mTemporalProfileLayer.selectByIds(fids)
        b = self.mTemporalProfileLayer.isEditable()
        self.mTemporalProfileLayer.startEditing()
        self.mTemporalProfileLayer.deleteSelectedFeatures()
        self.mTemporalProfileLayer.saveEdits(leaveEditable=b)
    def createNewPlotStyle2D(self):
        l = len(self.mTemporalProfileLayer)
        plotStyle = TemporalProfile2DPlotStyle()
        plotStyle.sigExpressionUpdated.connect(self.updatePlot2D)
        sensors = list(self.TS.Sensors.keys())
        if len(sensors) > 0:
            plotStyle.setSensor(sensors[0])
        if len(self.mTemporalProfileLayer) > 0:
            temporalProfile = self.mTemporalProfileLayer[0]
            plotStyle.setTemporalProfile(temporalProfile)


        if len(self.plotSettingsModel2D) > 0:
            lastStyle = self.plotSettingsModel2D[0] #top style in list is the most-recent
            assert isinstance(lastStyle, TemporalProfile2DPlotStyle)
            markerColor = nextColor(lastStyle.markerBrush.color())
            plotStyle.markerBrush.setColor(markerColor)
        self.plotSettingsModel2D.insertPlotStyles([plotStyle], i=0)
        pdi = plotStyle.createPlotItem(self.plot2D)
        assert isinstance(pdi, TemporalProfilePlotDataItem)
        pdi.sigClicked.connect(self.onProfileClicked2D)
        pdi.sigPointsClicked.connect(self.onPointsClicked2D)
        #self.plot2D.getPlotItem().addItem(pg.PlotDataItem(x=[1, 2, 3], y=[1, 2, 3]))
        #plotItem.addDataItem(pdi)
        #plotItem.plot().sigPlotChanged.emit(plotItem)
        self.updatePlot2D()
    def createNewPlotStyle3D(self):
        if not (ENABLE_OPENGL and OPENGL_AVAILABLE):
        plotStyle = TemporalProfile3DPlotStyle()
        plotStyle.sigExpressionUpdated.connect(self.updatePlot3D)

        if len(self.mTemporalProfileLayer) > 0:
            temporalProfile = self.mTemporalProfileLayer[0]
            plotStyle.setTemporalProfile(temporalProfile)
            if len(self.plotSettingsModel3D) > 0:
                color = self.plotSettingsModel3D[-1].color()
                plotStyle.setColor(nextColor(color))

        sensors = list(self.TS.Sensors.keys())
        if len(sensors) > 0:
            plotStyle.setSensor(sensors[0])

        self.plotSettingsModel3D.insertPlotStyles([plotStyle], i=0) # latest to the top
        plotItems = plotStyle.createPlotItem()
        self.plot3D.addItems(plotItems)
        #self.updatePlot3D()
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(c.x(), c.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(c.x(), c.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 onTemporalProfilesAdded(self, profiles):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #self.mTemporalProfileLayer.prune()
        for plotStyle in self.plotSettingsModel3D:
            assert isinstance(plotStyle, TemporalProfilePlotStyleBase)
            if not isinstance(plotStyle.temporalProfile(), TemporalProfile):
                r = self.plotSettingsModel3D.plotStyle2idx(plotStyle).row()
                c = self.plotSettingsModel3D.columnIndex(self.plotSettingsModel3D.cnTemporalProfile)
                idx = self.plotSettingsModel3D.createIndex(r,c)
                self.plotSettingsModel3D.setData(idx, self.mTemporalProfileLayer[0])
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def onTemporalProfileSelectionChanged(self, selectedFIDs, deselectedFIDs):
        nSelected = len(selectedFIDs)

        self.ui.actionRemoveTemporalProfile.setEnabled(nSelected > 0)
Benjamin Jakimow's avatar
Benjamin Jakimow committed


    def onPlot2DSelectionChanged(self, selected, deselected):

        self.ui.actionRemoveStyle2D.setEnabled(len(selected) > 0)
    def onPlot3DSelectionChanged(self, selected, deselected):

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

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

        self.ui.actionRemoveStyle2D.setEnabled(False)
        self.ui.actionRemoveTemporalProfile.setEnabled(False)
        self.ui.actionAddStyle2D.triggered.connect(self.createNewPlotStyle2D)
        self.ui.actionAddStyle3D.triggered.connect(self.createNewPlotStyle3D)
        self.ui.actionRefresh2D.triggered.connect(self.updatePlot2D)
        self.ui.actionRefresh3D.triggered.connect(self.updatePlot3D)
        self.ui.actionRemoveStyle2D.triggered.connect(lambda:self.removePlotStyles2D(self.selected2DPlotStyles()))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.ui.actionRemoveTemporalProfile.triggered.connect(lambda :self.removeTemporalProfiles(self.mTemporalProfileLayer.selectedFeatureIds()))
        self.ui.actionToggleEditing.triggered.connect(self.onToggleEditing)
        self.ui.actionReset2DPlot.triggered.connect(self.plot2D.resetViewBox)
        self.plot2D.resetTransform()
        self.ui.actionReset3DCamera.triggered.connect(self.reset3DCamera)
        self.ui.actionLoadTPFromOgr.triggered.connect(lambda : self.loadCoordinatesFromOgr(None))
        self.ui.actionLoadMissingValues.triggered.connect(lambda: self.loadMissingData())
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.ui.actionSaveTemporalProfiles.triggered.connect(self.mTemporalProfileLayer.saveTemporalProfiles)
        #set actions to be shown in the TemporalProfileTableView context menu
        ma = [self.ui.actionSaveTemporalProfiles, self.ui.actionLoadMissingValues]
        self.ui.tableViewTemporalProfiles.setContextMenuActions(ma)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.onEditingToggled()

    def onSaveTemporalProfiles(self):

        s = ""

    def onToggleEditing(self, b):

        if self.mTemporalProfileLayer.isEditable():
            self.mTemporalProfileLayer.saveEdits(leaveEditable=False)
        else:
            self.mTemporalProfileLayer.startEditing()
        self.onEditingToggled()

    def onEditingToggled(self):
        lyr = self.mTemporalProfileLayer

        hasSelectedFeatures = lyr.selectedFeatureCount() > 0
        isEditable = lyr.isEditable()
        self.ui.actionToggleEditing.blockSignals(True)
        self.ui.actionToggleEditing.setChecked(isEditable)
        #self.actionSaveTemporalProfiles.setEnabled(isEditable)
        #self.actionReload.setEnabled(not isEditable)
        self.ui.actionToggleEditing.blockSignals(False)

        #self.actionAddAttribute.setEnabled(isEditable)
        #self.actionRemoveAttribute.setEnabled(isEditable)
        self.ui.actionRemoveTemporalProfile.setEnabled(isEditable and hasSelectedFeatures)
        #self.actionPasteFeatures.setEnabled(isEditable)
        self.ui.actionToggleEditing.setEnabled(not lyr.readOnly())

Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def loadCoordinatesFromOgr(self, path):
        if path is None:

            filters = QgsProviderRegistry.instance().fileVectorFilters()
            defDir = None
            path, filter = QFileDialog.getOpenFileName(directory=defDir, filter=filters)

        if isinstance(path, str) and len(path) > 0:
            ds = QgsVectorLayer(path)

            extent = self.TS.getMaxSpatialExtent(ds.crs())
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            ds.removeSelection()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            n = ds.selectedFeatureCount()
            newProfiles = []
            for feature in ds.selectedFeatures():
                assert isinstance(feature, QgsFeature)
                geom = feature.geometry()
                if isinstance(geom, QgsGeometry):
                    point = geom.centroid().constGet()
                    #TP = TemporalProfile(self.TS, SpatialPoint(ds.crs(), point))
                    TP = self.mTemporalProfileLayer.createTemporaProfile(self.TS, SpatialPoint(ds.crs(), point))
                    name = ' '.join([str(a) for a in feature.attributes()])
                    TP.setName(name)
                    if not TP in self.mTemporalProfileLayer:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                        newProfiles.append(TP)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            if True and len(newProfiles) > 0:
                self.mTemporalProfileLayer.setMaxProfiles(len(self.mTemporalProfileLayer) + len(newProfiles))
                self.mTemporalProfileLayer.insertTemporalProfiles(newProfiles)
    def reset3DCamera(self, *args):

        if ENABLE_OPENGL and OPENGL_AVAILABLE:
            self.ui.actionReset3DCamera.trigger()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    sigMoveToTSD = pyqtSignal(TimeSeriesDatum)
    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)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(d, PixelLoaderTask)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        bn = os.path.basename(d.sourcePath)
        if d.success():

            t = 'Loaded {} pixel from {}.'.format(len(d.resProfiles), bn)
            self.mTemporalProfileLayer.addPixelLoaderResult(d)
            self.updateRequested = True
        else:
            t = 'Failed loading from {}.'.format(bn)
            if d.info and d.info != '':
                t += '({})'.format(d.info)
        # QgsApplication.processEvents()
        self.ui.progressInfo.setText(t)

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

    def onRowsInserted2D(self, parent, start, end):
        model = self.ui.tableView2DProfiles.model()
        if isinstance(model, PlotSettingsModel2D):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            colExpression = model.columnIndex(model.cnExpression)
            colStyle = model.columnIndex(model.cnStyle)
            while start <= end:
                idxExpr = model.createIndex(start, colExpression)
                idxStyle = model.createIndex(start, colStyle)
                self.ui.tableView2DProfiles.openPersistentEditor(idxExpr)
                self.ui.tableView2DProfiles.openPersistentEditor(idxStyle)
    def onRowsInserted3D(self, parent, start, end):
        model = self.ui.tableView3DProfiles.model()
        if isinstance(model, PlotSettingsModel3D):
            colExpression = model.columnIndex(model.cnExpression)
            colStyle = model.columnIndex(model.cnStyle)
            while start <= end:
                idxStyle = model.createIndex(start, colStyle)
                idxExpr = model.createIndex(start, colExpression)
                self.ui.tableView3DProfiles.openPersistentEditor(idxStyle)
                self.ui.tableView3DProfiles.openPersistentEditor(idxExpr)
    def onObservationClicked(self, plotDataItem, points):
        for p in points:
            tsd = p.data()
            #print(tsd)
    def loadMissingData(self, backgroundProcess=False):
        """
        Loads all band values of collected locations that have not been loaded until now
        """

        fids = self.mTemporalProfileLayer.selectedFeatureIds()
        if len(fids) == 0:
            fids = [f.id() for f in self.mTemporalProfileLayer.getFeatures()]

        tps = [self.mTemporalProfileLayer.mProfiles.get(fid) for fid in fids]
        spatialPoints = [tp.coordinate() for tp in tps if isinstance(tp, TemporalProfile)]
        self.loadCoordinate(spatialPoints=spatialPoints, mode='all', backgroundProcess=backgroundProcess)
    LOADING_MODES = ['missing','reload','all']
    def loadCoordinate(self, spatialPoints=None, LUT_bandIndices=None, mode='missing', backgroundProcess = False):
        """
        :param spatialPoints: [list-of-geometries] to load pixel values from
        :param LUT_bandIndices: dictionary {sensor:[indices]} with band indices to be loaded per sensor
        :param mode:
        :return:
        """
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        """
        Loads a temporal profile for a single or multiple geometries.
        :param spatialPoints: SpatialPoint | [list-of-SpatialPoints]
        """
        assert mode in SpectralTemporalVisualization.LOADING_MODES

        if not isinstance(self.plotSettingsModel2D, PlotSettingsModel2D):
        #if not self.pixelLoader.isReadyToLoad():
        #    return False

        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 which (new) bands need to be loaded for each sensor
        if LUT_bandIndices is None:
            LUT_bandIndices = dict()
            for sensor in self.TS.Sensors:
                if mode in ['all','reload']:
                    LUT_bandIndices[sensor] = list(range(sensor.nb))
                else:
                    LUT_bandIndices[sensor] = self.plotSettingsModel2D.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.mTemporalProfileLayer.fromSpatialPoint(spatialPoint)
            #if not TP exists for this point, create an empty one
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            if not isinstance(TP, TemporalProfile):
                TP = self.mTemporalProfileLayer.createTemporalProfiles(spatialPoint)[0]
                if len(self.mTemporalProfileLayer) == 1:
                    if len(self.plotSettingsModel2D) == 0:
                        self.createNewPlotStyle2D()
                        self.createNewPlotStyle3D()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            TPs.append(TP)
            theGeometries.append(TP.coordinate())
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        TP_ids = [TP.id() for TP in TPs]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #each TSD is a Task
        s = ""
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #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

            if mode == 'missing':
                missingIndices = set()
                for TP in TPs:
                    assert isinstance(TP, TemporalProfile)
                    need2load = TP.missingBandIndices(tsd, requiredIndices=requiredIndices)
                    missingIndices = missingIndices.union(need2load)
                missingIndices = sorted(list(missingIndices))
            else:
                missingIndices = requiredIndices
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))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            if DEBUG:
                print('Start loading for {} geometries from {} sources...'.format(
                    len(theGeometries), len(tasks)
                ))
            if backgroundProcess:
                self.pixelLoader.startLoading(tasks)
            else:
                import timeseriesviewer.pixelloader
                tasks = [timeseriesviewer.pixelloader.doLoaderTask(task) for task in tasks]
                l = len(tasks)
                for i, task in enumerate(tasks):
                    self.pixelLoader.sigPixelLoaded.emit(i+1, l, task)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        else:
            if DEBUG:
                print('Data for geometries already loaded')
    def addData(self, sensorView = None):
            for sv in self.plotSettingsModel2D.items:
            assert isinstance(sensorView, TemporalProfile2DPlotStyle)
    @QtCore.pyqtSlot()
    def onDataUpdate(self):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #self.mTemporalProfileLayer.prune()
        for plotSetting in self.plotSettingsModel2D:
            assert isinstance(plotSetting, TemporalProfile2DPlotStyle)
            tp = plotSetting.temporalProfile()
            for pdi in plotSetting.mPlotItems:
                assert isinstance(pdi, TemporalProfilePlotDataItem)
                pdi.updateDataAndStyle()
            if isinstance(tp, TemporalProfile) and plotSetting.temporalProfile().updated():
        notInit = [0, 1] == self.plot2D.plotItem.getAxis('bottom').range
            for plotSetting in self.plotSettingsModel2D:
                assert isinstance(plotSetting, TemporalProfile2DPlotStyle)
                for pdi in plotSetting.mPlotItems:
                    assert isinstance(pdi, TemporalProfilePlotDataItem)
                    if pdi.xData.ndim == 0 or pdi.xData.shape[0] == 0:
                        continue
                    if x0 is None:
                        x0 = pdi.xData.min()
                        x1 = pdi.xData.max()
                    else:
                        x0 = min(pdi.xData.min(), x0)
                        x1 = max(pdi.xData.max(), x1)

            if x0 is not None:
                self.plot2D.plotItem.setXRange(x0, x1)
                #self.plot2D.xAxisInitialized = True

    @QtCore.pyqtSlot()
    def updatePlot3D(self):
        if ENABLE_OPENGL and OPENGL_AVAILABLE:
            from pyqtgraph.opengl import GLViewWidget
            from pyqtgraph.opengl.GLGraphicsItem import GLGraphicsItem
            import pyqtgraph.opengl as gl
            from timeseriesviewer.temporalprofiles3dGL import ViewWidget3D
            assert isinstance(self.plot3D, ViewWidget3D)
            # 1. ensure that data from all bands will be loaded
            #    new loaded values will be shown in the next updatePlot3D call
            allPlotItems = []
            for plotStyle3D in self.plotSettingsModel3D:
                assert isinstance(plotStyle3D, TemporalProfile3DPlotStyle)