Skip to content
Snippets Groups Projects
profilevisualization.py 103 KiB
Newer Older
    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 = TemporalProfile2DPlotStyle(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 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(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
        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.tableView2DProfiles.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents)
        self.plotSettingsModel3D = PlotSettingsModel3D()
        #self.plotSettingsModel3D.sigPlotStylesRemoved.connect(self.updatePlot3D)
        #self.plotSettingsModel3D.sigPlotStylesAdded.connect(self.updatePlot3D)
        #self.plotSettingsModel3D.sigPlotStylesAdded.connect(self.updatePlot3D)
        self.plotSettingsModel3D.rowsInserted.connect(self.onRowsInserted3D)
        self.ui.tableView3DProfiles.setModel(self.plotSettingsModel3D)
        self.ui.tableView3DProfiles.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents)
        self.delegateTableView3D = PlotSettingsModel3DWidgetDelegate(self.ui.tableView3DProfiles)
        self.delegateTableView3D.setItemDelegates(self.ui.tableView3DProfiles)
        # 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.reset3DCamera()

        ## Add a grid to the view
        if OPENGL_AVAILABLE:
            import pyqtgraph.opengl as gl
            self.glGridItem = gl.GLGridItem()
            self.glGridItem.setDepthValue(10)  # draw grid after surfaces since they may be translucent
            self.glPlotDataItems = [self.glGridItem]
            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)
        self.ui.tableViewTemporalProfiles.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents)
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)
        self.plotSettingsModel2D = None
        self.pixelLoader.sigLoadingStarted.connect(self.clear)
        self.pixelLoader.sigLoadingFinished.connect(lambda : self.plot2D.enableAutoRange('x', False))
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)

        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, PlotSettingsModel2D):
            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 = TemporalProfile2DPlotStyle(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.plotSettingsModel2D.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.btnReset3DCamera.setDefaultAction(self.ui.actionReset3DCamera)

        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.ui.actionReset3DCamera.triggered.connect(self.reset3DCamera)
        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 reset3DCamera(self, *args):

        if OPENGL_AVAILABLE:
            self.plot3D.setCameraPosition((0,0,0), distance=10, elevation=10)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def setTimeSeries(self, TS):

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

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

        self.plotSettingsModel2D = PlotSettingsModel2D(self.tpCollection, self.plot2D, parent=self)
        self.plotSettingsModel2D.sigVisibilityChanged.connect(self.setVisibility)
        self.plotSettingsModel2D.sigDataChanged.connect(self.requestUpdate)
        self.plotSettingsModel2D.rowsInserted.connect(self.onRowsInserted2D)
        self.plotSettingsModel3D.connectTimeSeries(self.TS)
        # self.plotSettingsModel.modelReset.connect(self.updatePersistantWidgets)
        self.tableView2DProfiles.setModel(self.plotSettingsModel2D)
        #self.tableView2DProfilesSelectionModel = QItemSelectionModel(self.mModel)
        self.tableView2DProfiles.selectionModel().selectionChanged.connect(self.onPlot2DSelectionChanged)
        #self.tableView2DProfilesSelectionModel.selectionChanged.connect(self.onPlot2DSelectionChanged)
        #self.tableView2DProfilesSelectionModel.setSelectionModel(self.mSelectionModel)
        self.delegateTableView2D = PlotSettingsModel2DWidgetDelegate(self.tableView2DProfiles, self.TS, self.tpCollectionListModel)
        self.delegateTableView2D.setItemDelegates(self.tableView2DProfiles)

    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 = 'Last loaded from {}.'.format(bn)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            self.tpCollection.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.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.tableView2DProfiles.openPersistentEditor(idxExpr)
                self.tableView2DProfiles.openPersistentEditor(idxStyle)
                start += 1
                #self.TV.openPersistentEditor(model.createIndex(start, colStyle))
            s = ""

    def onRowsInserted3D(self, parent, start, end):
        model = self.ui.tableView3DProfiles.model()
        if isinstance(model, PlotSettingsModel3D):
            colStyle = model.columnIndex(model.cnStyle)
            while start <= end:
                idxStyle = model.createIndex(start, colStyle)
                self.ui.tableView3DProfiles.openPersistentEditor(idxStyle)
                start += 1

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

        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.plotSettingsModel2D, PlotSettingsModel2D):
            return False

        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 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.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.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 PlotSettingsModel2D.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):
        assert isinstance(sensorPlotStyle, TemporalProfile2DPlotStyle)
        self.setVisibility2D(sensorPlotStyle)
    def setVisibility2D(self, sensorPlotStyle):
    def addData(self, sensorView = None):
            for sv in self.plotSettingsModel2D.items:
            assert isinstance(sensorView, TemporalProfile2DPlotStyle)
    @QtCore.pyqtSlot()
        for plotSetting in self.plotSettingsModel2D:
            assert isinstance(plotSetting, TemporalProfile2DPlotStyle)
            if plotSetting.temporalProfile().updated():
                for pdi in plotSetting.mPlotItems:
                    assert isinstance(pdi, TemporalProfilePlotDataItem)
                    pdi.updateDataAndStyle()
                plotSetting.temporalProfile().resetUpdated()

        for i in self.plot2D.getPlotItem().dataItems:
            i.updateItems()


        if not self.plot2D.xAxisInitialized:
            x0 = x1 = None
            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.getPlotItem().setXRange(x0, x1)
                self.plot2D.xAxisInitialized = True

    @QtCore.pyqtSlot()
    def updatePlot3D(self):
        if OPENGL_AVAILABLE:
            from pyqtgraph.opengl import GLViewWidget
            import pyqtgraph.opengl as gl
            assert isinstance(self.plot3D, GLViewWidget)
            w = self.plot3D

            #we need the data from all bands



            del self.glPlotDataItems[:]
            for i in w.items:
                w.removeItem(i)

            idx = self.ui.cbTemporalProfile3D.currentIndex()
            if idx >= 0:
                tp = self.ui.cbTemporalProfile3D.itemData(idx, role=Qt.UserRole)
                assert isinstance(tp, TemporalProfile)

                #1. ensure that data from all bands will be loaded
                LUT_bandIndices = dict()
                for sensor in self.TS.sensors():
                    assert isinstance(sensor, SensorInstrument)
                    LUT_bandIndices[sensor] = list(range(sensor.nb))

                self.loadCoordinate(tp.mCoordinate, LUT_bandIndices=LUT_bandIndices)

                #2. visualize already loaded data
                LUTStyle = {}
                for style in self.plotSettingsModel3D:
                    assert isinstance(style, TemporalProfile3DPlotStyle)
                    LUTStyle[style.sensor()] = style
                dataPos = []
                x0 = x1 = y0 = y1 = z0 = z1 = 0
                for iDate, tsd in enumerate(tp.mTimeSeries):
                    data = tp.data(tsd)
                    bandKeys = sorted([k for k in data.keys() if k.startswith('b') and data[k] != None], key=lambda k: bandKey2bandIndex(k))
                    if len(bandKeys) < 2:
                        continue
                    for i, k in enumerate(bandKeys):
                        x.append(i)
                        y.append(t)
                        z.append(data[k])
                    x = np.asarray(x)
                    y = np.asarray(y)
                    z = np.asarray(z)
                    if iDate == 0:
                        x0, x1 = (x.min(), x.max())
                        y0, y1 = (y.min(), y.max())
                        z0, z1 = (z.min(), z.max())
                    else:
                        x0, x1 = (min(x.min(), x0), max(x.max(), x1))
                        y0, y1 = (min(y.min(), y0), max(y.max(), y1))
                        z0, z1 = (min(z.min(), z0), max(z.max(), z1))
                    if tsd.sensor in LUTStyle.keys():
                        style = LUTStyle[tsd.sensor]
                    else:
                        style = TemporalProfile3DPlotStyle(tsd.sensor)
                    dataPos.append((x,y,z, style))

                xyz = [(x0,x1),(y0,y1),(z0,z1)]
                l = len(dataPos)
                for iPos, pos in enumerate(dataPos):
                    x,y,z, style = pos
                    assert isinstance(style, TemporalProfile3DPlotStyle)
                    if not style.isVisible():
                        continue
                    arr = np.asarray((x,y,z), dtype=np.float64).transpose()
                    for i, m in enumerate(xyz):
                        arr[:, i] = (arr[:,i] - m0)/(m1-m0)

                    plt = gl.GLLinePlotItem(pos=arr,
                                            #color=pg.glColor((i, n * 1.3)),
                                            #color=pg.glColor(255,123,123,125),
                                            #color=pg.glColor((iPos, l * 1.3)),
                                            color=pg.glColor(style.color()),
                    self.glPlotDataItems.append(plt)

                for i, item in enumerate(self.glPlotDataItems):
                   w.addItem(item)
                #self.glGridItem.scale(0.1,0.1,0.1, local=False)

                #w.setBackgroundColor(QColor('black'))
                #w.setCameraPosition(pos=(0.0, 0.0, 0.0), distance=1.)
                w.addItem(self.glGridItem)
                w.update()
                """
                for sensor, values in data.items():
                    if len(values['z']) > 0:
                        x = values['x']
                        y = values['y']
                        z = values['z']
                        
                        p2 = gl.GLSurfacePlotItem(x=x, y=y, z=z, shader='normalColor')
                        p2.translate(-10, -10, 0)
                        w.addItem(p2)
                """

    @QtCore.pyqtSlot()
    def updatePlot2D(self):
        if isinstance(self.plotSettingsModel2D, PlotSettingsModel2D):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                print('Update plot...')
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            pi = self.plot2D.getPlotItem()
            piDataItems = pi.listDataItems()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            locations = set()
            for plotSetting in self.plotSettingsModel2D:
                assert isinstance(plotSetting, TemporalProfile2DPlotStyle)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                locations.add(plotSetting.temporalProfile().mCoordinate)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                for pdi in plotSetting.mPlotItems:
                    assert isinstance(pdi, TemporalProfilePlotDataItem)
                    pdi.updateDataAndStyle()

            #for i in pi.dataItems:
            #    i.updateItems()

            #self.plot2D.update()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            #2. load pixel data
            self.loadCoordinate(list(locations))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            # https://github.com/pyqtgraph/pyqtgraph/blob/5195d9dd6308caee87e043e859e7e553b9887453/examples/customPlot.py
            return
def examplePixelLoader():

    # prepare QGIS environment
    if sys.platform == 'darwin':
        PATH_QGS = r'/Applications/QGIS.app/Contents/MacOS'
        os.environ['GDAL_DATA'] = r'/usr/local/Cellar/gdal/1.11.3_1/share'
    else:
        # assume OSGeo4W startup
        PATH_QGS = os.environ['QGIS_PREFIX_PATH']
    assert os.path.exists(PATH_QGS)

    qgsApp = QgsApplication([], True)
    QApplication.addLibraryPath(r'/Applications/QGIS.app/Contents/PlugIns')
    QApplication.addLibraryPath(r'/Applications/QGIS.app/Contents/PlugIns/qgis')
    qgsApp.setPrefixPath(PATH_QGS, True)
    qgsApp.initQgis()


    gb = QGroupBox()
    gb.setTitle('Sandbox')

    PL = PixelLoader()
    PL.setNumberOfThreads(2)

    if False:
        files = ['observationcloud/testdata/2014-07-26_LC82270652014207LGN00_BOA.bsq',
                 'observationcloud/testdata/2014-08-03_LE72270652014215CUB00_BOA.bsq'
                 ]
    else:
        from timeseriesviewer.utils import file_search
        searchDir = r'H:\LandsatData\Landsat_NovoProgresso'
        files = file_search(searchDir, '*227065*band4.img', recursive=True)
        #files = files[0:3]

    lyr = QgsRasterLayer(files[0])
    coord = lyr.extent().center()
    crs = lyr.crs()

    l = QVBoxLayout()

    btnStart = QPushButton()
    btnStop = QPushButton()
    prog = QProgressBar()
    tboxResults = QPlainTextEdit()
    tboxResults.setMaximumHeight(300)
    tboxThreads = QPlainTextEdit()
    tboxThreads.setMaximumHeight(200)
    label = QLabel()
    label.setText('Progress')

    def showProgress(n,m,md):
        prog.setMinimum(0)
        prog.setMaximum(m)
        prog.setValue(n)

        info = []
        for k, v in md.items():
            info.append('{} = {}'.format(k,str(v)))
        tboxResults.setPlainText('\n'.join(info))
        #tboxThreads.setPlainText(PL.threadInfo())
        qgsApp.processEvents()

    PL.sigPixelLoaded.connect(showProgress)
    btnStart.setText('Start loading')
    btnStart.clicked.connect(lambda : PL.startLoading(files, coord, crs))
    btnStop.setText('Cancel')
    btnStop.clicked.connect(lambda: PL.cancelLoading())
    lh = QHBoxLayout()
    lh.addWidget(btnStart)
    lh.addWidget(btnStop)
    l.addLayout(lh)
    l.addWidget(prog)
    l.addWidget(tboxThreads)
    l.addWidget(tboxResults)

    gb.setLayout(l)
    gb.show()
    #rs.setBackgroundStyle('background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #222, stop:1 #333);')
    #rs.handle.setStyleSheet('background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #282, stop:1 #393);')
    qgsApp.exec_()
    qgsApp.exitQgis()

if __name__ == '__main__':
    import site, sys
    from timeseriesviewer import utils
    qgsApp = utils.initQgisApplication()
    DEBUG = False
    if False: #the ultimative test for floating point division correctness, at least on a DOY-level
        date1 = np.datetime64('1960-12-31','D')
        assert date1 == num2date(date2num(date1))
        #1960 - 12 - 31
        for year in  range(1960, 2057):
            for doy in range(1, daysPerYear(year)+1):
                dt = datetime.timedelta(days=doy - 1)
                date1 = np.datetime64('{}-01-01'.format(year)) + np.timedelta64(doy-1,'D')
                date2 = datetime.date(year=year, month=1, day=1) + datetime.timedelta(days=doy-1)

                assert date1 == num2date(date2num(date1), dt64=True), 'date1: {}'.format(date1)
                assert date2 == num2date(date2num(date2), dt64=False), 'date2: {}'.format(date1)
    ui = ProfileViewDockUI()
    ui.show()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    if True:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        STVis = SpectralTemporalVisualization(ui)
        STVis.setTimeSeries(TS)
        import example.Images