Skip to content
Snippets Groups Projects
main.py 42.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • unknown's avatar
    unknown committed
    # -*- coding: utf-8 -*-
    """
    /***************************************************************************
     EnMAPBox
                                     A QGIS plugin
     EnMAP-Box V3
                                  -------------------
            begin                : 2015-08-20
            git sha              : $Format:%H$
            copyright            : (C) 2015 by HU-Berlin
            email                : bj@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.                                   *
     *                                                                         *
     ***************************************************************************/
    """
    
    unknown's avatar
    unknown committed
    
    
    # Import the code for the dialog
    
    import os, sys, re, fnmatch, collections, copy, traceback, six
    
    from qgis.core import *
    #os.environ['PATH'] += os.pathsep + r'C:\OSGeo4W64\bin'
    
    from osgeo import gdal, ogr, osr, gdal_array
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    DEBUG = True
    
    import qgis.analysis
    
    unknown's avatar
    unknown committed
    try:
        from qgis.gui import *
    
    unknown's avatar
    unknown committed
        import qgis
    
    unknown's avatar
    unknown committed
        import qgis_add_ins
    
    unknown's avatar
    unknown committed
        qgis_available = True
    
        #import console.console_output
        #console.show_console()
        #sys.stdout = console.console_output.writeOut()
        #sys.stderr = console.console_output.writeOut()
    
    unknown's avatar
    unknown committed
    except:
    
        print('Can not find QGIS instance')
    
    unknown's avatar
    unknown committed
        qgis_available = False
    
    import numpy as np
    
    import multiprocessing, site
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    from PyQt4.uic.Compiler.qtproxies import QtGui, QtCore
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    #abbreviations
    
    from timeseriesviewer import jp, mkdir, DIR_SITE_PACKAGES, file_search, dprint
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    site.addsitedir(DIR_SITE_PACKAGES)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    from timeseriesviewer.ui import widgets
    from timeseriesviewer.timeseries import TimeSeries, TimeSeriesDatum, SensorInstrument
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    #I don't know why, but this is required to run this in QGIS
    
    #todo: still required?
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    path = os.path.abspath(jp(sys.exec_prefix, '../../bin/pythonw.exe'))
    
    unknown's avatar
    unknown committed
    if os.path.exists(path):
        multiprocessing.set_executable(path)
        sys.argv = [ None ]
    
    unknown's avatar
    unknown committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    #ensure that required non-standard modules are available
    
    unknown's avatar
    unknown committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    unknown's avatar
    unknown committed
    class TimeSeriesTableModel(QAbstractTableModel):
    
        columnames = ['date','sensor','ns','nl','nb','image','mask']
    
    unknown's avatar
    unknown committed
    
        def __init__(self, TS, parent=None, *args):
            super(QAbstractTableModel, self).__init__()
            assert isinstance(TS, TimeSeries)
            self.TS = TS
    
        def rowCount(self, parent = QModelIndex()):
            return len(self.TS)
    
        def columnCount(self, parent = QModelIndex()):
            return len(self.columnames)
    
        def removeRows(self, row, count , parent=QModelIndex()):
            self.beginRemoveRows(parent, row, row+count-1)
            toRemove = self._data[row:row+count]
            for i in toRemove:
                self._data.remove(i)
    
            self.endRemoveRows()
    
    
    unknown's avatar
    unknown committed
        def getDateFromIndex(self, index):
            if index.isValid():
                i = index.row()
                if i >= 0 and i < len(self.TS):
    
                    return self.TS.getTSDs()[i]
    
    unknown's avatar
    unknown committed
            return None
    
    unknown's avatar
    unknown committed
    
        def getTimeSeriesDatumFromIndex(self, index):
    
            if index.isValid():
                i = index.row()
                if i >= 0 and i < len(self.TS):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    return self.TS.data[i]
    
    unknown's avatar
    unknown committed
    
            return None
    
    
    
        def data(self, index, role = Qt.DisplayRole):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if role is None or not index.isValid():
    
    unknown's avatar
    unknown committed
                return None
    
    
            value = None
            ic_name = self.columnames[index.column()]
            TSD = self.getTimeSeriesDatumFromIndex(index)
            keys = list(TSD.__dict__.keys())
            if role == Qt.DisplayRole or role == Qt.ToolTipRole:
                if ic_name == 'name':
                    value = os.path.basename(TSD.pathImg)
    
                elif ic_name == 'sensor':
                    if role == Qt.ToolTipRole:
                        value = TSD.sensor.getDescription()
                    else:
                        value = str(TSD.sensor)
    
    unknown's avatar
    unknown committed
                elif ic_name == 'date':
                    value = '{}'.format(TSD.date)
                elif ic_name == 'image':
                    value = TSD.pathImg
                elif ic_name == 'mask':
                    value = TSD.pathMsk
                elif ic_name in keys:
                    value = TSD.__dict__[ic_name]
                else:
                    s = ""
            elif role == Qt.BackgroundColorRole:
                value = None
            elif role == Qt.UserRole:
    
    unknown's avatar
    unknown committed
    
            return value
    
        #def flags(self, index):
        #    return Qt.ItemIsEnabled
    
        def flags(self, index):
            if index.isValid():
                item = self.getTimeSeriesDatumFromIndex(index)
                cname = self.columnames[index.column()]
                if cname.startswith('d'): #relative values can be edited
                    flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
                else:
                    flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
                return flags
                #return item.qt_flags(index.column())
            return None
    
        def headerData(self, col, orientation, role):
            if Qt is None:
                return None
            if orientation == Qt.Horizontal and role == Qt.DisplayRole:
                return self.columnames[col]
            elif orientation == Qt.Vertical and role == Qt.DisplayRole:
                return col
            return None
    
    
    unknown's avatar
    unknown committed
    class TimeSeriesItemModel(QAbstractItemModel):
    
    unknown's avatar
    unknown committed
    
    
    unknown's avatar
    unknown committed
        def __init__(self, TS):
    
    unknown's avatar
    unknown committed
            QAbstractItemModel.__init__(self)
            #self.rootItem = TreeItem[]
    
    unknown's avatar
    unknown committed
            assert type(TS) is TimeSeries
            self.TS = TS
    
    unknown's avatar
    unknown committed
    
        def index(self, row, column, parent = QModelIndex()):
            if not parent.isValid():
                parentItem = self.rootItem
            else:
                parentItem = parent.internalPointer()
            childItem = parentItem.child(row)
            if childItem:
                return self.createIndex(row, column, childItem)
            else:
                return QModelIndex()
    
        def setData(self, index, value, role = Qt.EditRole):
            if role == Qt.EditRole:
                row = index.row()
    
                return False
            return False
    
        def data(self, index, role=Qt.DisplayRole):
            data = None
            if role == Qt.DisplayRole or role == Qt.EditRole:
                data = 'sampletext'
    
    
            return data
    
        def flags(self, QModelIndex):
            return Qt.ItemIsSelectable
    
        def rowCount(self, index=QModelIndex()):
    
    unknown's avatar
    unknown committed
            return len(self.TS)
    
    unknown's avatar
    unknown committed
    
        #---------------------------------------------------------------------------
        def columnCount(self, index=QModelIndex()):
            return 1
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        sigAddBandView = pyqtSignal(object)
        sigRemoveBandView = pyqtSignal(object)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def __init__(self, TS, recommended_bands=None, parent=None, showSensorNames=True):
    
            super(BandView, self).__init__()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.ui = widgets.BandViewUI(parent)
            self.ui.create()
    
            #forward actions with reference to this band view
            self.ui.actionAddBandView.triggered.connect(lambda : self.sigAddBandView.emit(self))
            self.ui.actionRemoveBandView.triggered.connect(lambda: self.sigRemoveBandView.emit(self))
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert type(TS) is TimeSeries
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.sensorViews = collections.OrderedDict()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.TS = TS
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.TS.sigSensorAdded.connect(self.addSensor)
            self.TS.sigChanged.connect(self.removeSensor)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mShowSensorNames = showSensorNames
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for sensor in self.TS.Sensors:
                self.addSensor(sensor)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def setTitle(self, title):
            self.ui.labelViewName.setText(title)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def showSensorNames(self, b):
            assert isinstance(b, bool)
            self.mShowSensorNames = b
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for s,w in self.sensorViews.items():
                w.showSensorName(b)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def removeSensor(self, sensor):
            assert type(sensor) is SensorInstrument
            assert sensor in self.sensorViews.keys()
            self.sensorViews[sensor].close()
            self.sensorViews.pop(sensor)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def addSensor(self, sensor):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
            :param sensor:
            :return:
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert type(sensor) is SensorInstrument
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert sensor not in self.sensorViews.keys()
            w = widgets.ImageChipViewSettings(sensor)
            w.showSensorName(self.mShowSensorNames)
            self.sensorViews[sensor] = w
            i = self.ui.mainLayout.count()-1
            self.ui.mainLayout.insertWidget(i, w.ui)
            s = ""
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def getSensorWidget(self, sensor):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert type(sensor) is SensorInstrument
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            return self.sensorViews[sensor]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def __init__(self, TSD, renderer, destinationId=None):
            assert isinstance(TSD, TimeSeriesDatum)
            assert isinstance(renderer, QgsRasterRenderer)
    
            self.TSD = TSD
            self.renderer = renderer
            self.destinationId = destinationId
    
        def __eq__(self, other):
            if not isinstance(other, RenderJob):
                return False
            return self.TSD == other.TSD and \
                   self.renderer == other.renderer and \
                   self.destinationId == other.destinationId
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    class PixmapBuffer(QObject):
        sigProgress = pyqtSignal(int, int)
        sigPixmapCreated = pyqtSignal(RenderJob, QPixmap)
    
        def __init__(self):
            super(PixmapBuffer, self).__init__()
    
            self.extent = None
            self.crs = None
            self.size = None
            self.nWorkersMax = 1
            self.Workers = []
            self.PIXMAPS = dict()
            self.JOBS = []
            self.nTotalJobs = -1
            self.nJobsDone = -1
    
        def addWorker(self):
            w = Worker()
            w.setCrsTransformEnabled(True)
            w.sigPixmapCreated.connect(self.newPixmapCreated)
            if self.isValid():
                self.setWorkerProperties(w)
            self.Workers.append(w)
    
        def setWorkerProperties(self, worker):
            assert isinstance(worker, Worker)
            worker.setFixedSize(self.size)
            worker.setDestinationCrs(self.crs)
            worker.setExtent(self.extent)
            worker.setCenter(self.extent.center())
    
        def newPixmapCreated(self, renderJob, pixmap):
            self.JOBS.remove(renderJob)
            self.nJobsDone += 1
            self.sigPixmapCreated.emit(renderJob, pixmap)
            self.sigProgress.emit(self.nJobsDone, self.nTotalJobs)
    
        def isValid(self):
            return self.extent != None and self.crs != None and self.size != None
    
        def setExtent(self, extent, crs, maxPx):
            self.stopRendering()
            assert isinstance(extent, QgsRectangle)
            assert isinstance(crs, QgsCoordinateReferenceSystem)
            assert isinstance(maxPx, int)
            ratio = extent.width() / extent.height()
            if ratio < 1:  # x is largest side
                size = QSize(maxPx, int(maxPx / ratio))
            else:  # y is largest
                size = QSize(int(maxPx * ratio), maxPx)
    
            self.crs = crs
            self.size = size
            self.extent = extent
            for w in self.Workers:
                self.setWorkerProperties(w)
            return size
    
        def stopRendering(self):
            for w in self.Workers:
                w.stopRendering()
                w.clear()
            while len(self.JOBS) > 0:
                self.JOBS.pop(0)
            self.sigProgress.emit(0, 0)
    
        def loadSubsets(self, jobs):
            for j in jobs:
                assert isinstance(j, RenderJob)
    
            self.stopRendering()
    
            self.JOBS.extend(jobs)
            self.nTotalJobs = len(self.JOBS)
            self.nJobsDone = 0
            self.sigProgress.emit(0, self.nTotalJobs)
    
            if len(self.Workers) == 0:
                self.addWorker()
    
            #split jobs to number of workers
            i = 0
            chunkSize = int(len(self.JOBS) / len(self.Workers))
            assert chunkSize > 0
            for i in range(0, len(self.Workers), chunkSize):
                worker = self.Workers[i]
                j = min(i+chunkSize, len(self.JOBS))
                worker.startLayerRendering(self.JOBS[i:j])
    
    class Worker(QgsMapCanvas):
    
        sigPixmapCreated = pyqtSignal(RenderJob, QPixmap)
    
    
        def __init__(self, *args, **kwds):
            super(Worker,self).__init__(*args, **kwds)
            self.reg = QgsMapLayerRegistry.instance()
            self.painter = QPainter()
            self.renderJobs = list()
            self.mapCanvasRefreshed.connect(self.createPixmap)
    
        def isBusy(self):
            return len(self.renderJobs) != 0
    
        def createPixmap(self, *args):
            if len(self.renderJobs) > 0:
                pixmap = QPixmap(self.size())
                self.painter.begin(pixmap)
                self.map().paint(self.painter)
                self.painter.end()
                assert not pixmap.isNull()
                job = self.renderJobs.pop(0)
                self.sigPixmapCreated.emit(job, pixmap)
                self.startSingleLayerRendering()
    
        def stopLayerRendering(self):
            self.stopRendering()
            del self.renderJobs[:]
            assert self.isBusy() is False
    
        def startLayerRendering(self, renderJobs):
            assert isinstance(renderJobs, list)
            self.renderJobs.extend(renderJobs)
            self.startSingleLayerRendering()
    
    
    
        def startSingleLayerRendering(self):
    
            if len(self.renderJobs) > 0:
                renderJob = self.renderJobs[0]
                assert isinstance(renderJob, RenderJob)
                #mapLayer = QgsRasterLayer(renderJob.TSD.pathImg)
                mapLayer = renderJob.TSD.lyrImg
                mapLayer.setRenderer(renderJob.renderer)
                dprint('QgsMapLayerRegistry count: {}'.format(self.reg.count()))
                self.reg.addMapLayer(mapLayer)
    
                lyrSet = [QgsMapCanvasLayer(mapLayer)]
                self.setLayerSet(lyrSet)
    
                #todo: add crosshair
                self.refreshAllLayers()
    
    unknown's avatar
    unknown committed
    
    
    class ImageChipLabel(QLabel):
    
        clicked = pyqtSignal(object, object)
    
    
        def __init__(self, time_series_viewer, TSD, renderer):
            assert isinstance(time_series_viewer, TimeSeriesViewer)
            assert isinstance(TSD, TimeSeriesDatum)
            assert isinstance(renderer, QgsRasterRenderer)
    
            super(ImageChipLabel, self).__init__(time_series_viewer.ui)
    
            self.TSV = time_series_viewer
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.TSD = TSD
            self.bn = os.path.basename(self.TSD.pathImg)
    
            self.setContextMenuPolicy(Qt.DefaultContextMenu)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.setFrameShape(QFrame.StyledPanel)
            self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
    
            tt = ['Date: {}'.format(TSD.date) \
                 ,'Name: {}'.format(self.bn) \
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            self.setToolTip(list2str(tt))
    
    
        def mouseReleaseEvent(self, event):
    
    	    self.clicked.emit(self, event)
    
    
        def contextMenuEvent(self, event):
            menu = QMenu()
            #add general options
    
            action = menu.addAction('Copy to clipboard')
            action.triggered.connect(lambda : QApplication.clipboard().setPixmap(self.pixmap()))
    
    
            #add QGIS specific options
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                action = menu.addAction('Add {} to QGIS layers'.format(self.bn))
                action.triggered.connect(lambda : qgis_add_ins.add_QgsRasterLayer(self.iface, self.TSD.pathImg, self.bands))
    
    unknown's avatar
    unknown committed
    
    
    
    def getBoundingBoxPolygon(points, srs=None):
        ring = ogr.Geometry(ogr.wkbLinearRing)
        for point in points:
            ring.AddPoint(point[0], point[1])
        bb = ogr.Geometry(ogr.wkbPolygon)
        bb.AddGeometry(ring)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        if isinstance(srs, QgsCoordinateReferenceSystem):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            _crs = osr.SpatialReference()
            _crs.ImportFromWkt(srs.toWkt())
            bb.AssignSpatialReference(_crs)
        return bb
    
    unknown's avatar
    unknown committed
    def getDS(ds):
        if type(ds) is not gdal.Dataset:
            ds = gdal.Open(ds)
        return ds
    
    
    benjamin.jakimow@geo.hu-berlin.de's avatar
    benjamin.jakimow@geo.hu-berlin.de committed
    
    
    def getBandNames(lyr):
        assert isinstance(lyr, QgsRasterLayer)
        dp = lyr.dataProvider()
        assert isinstance(dp, QgsRasterDataProvider)
        if str(dp.name()) == 'gdal':
            s = ""
        else:
            return lyr
    
    
    unknown's avatar
    unknown committed
    def getImageDate(ds):
        if type(ds) is str:
            ds = gdal.Open(ds)
    
        path = ds.GetFileList()[0]
        to_check = [os.path.basename(path), os.path.dirname(path)]
    
        regAcqDate = re.compile(r'acquisition (time|date|day)', re.I)
        for key, value in ds.GetMetadata_Dict().items():
            if regAcqDate.search(key):
                to_check.insert(0, value)
    
        for text in to_check:
            date = parseAcquisitionDate(text)
            if date:
                return date
    
        raise Exception('Can not identify acquisition date of {}'.format(path))
    
    
    
    def getChip3d(chips, rgb_idx, ranges):
        assert len(rgb_idx) == 3 and len(rgb_idx) == len(ranges)
        for i in rgb_idx:
            assert i in chips.keys()
    
        nl, ns = chips[rgb_idx[0]].shape
        a3d = np.ndarray((3,nl,ns), dtype='float')
    
        for i, rgb_i in enumerate(rgb_idx):
            range = ranges[i]
            data = chips[rgb_i].astype('float')
            data -= range[0]
            data *= 255./range[1]
            a3d[i,:] = data
    
        np.clip(a3d, 0, 255, out=a3d)
    
        return a3d.astype('uint8')
    
    def Array2Image(d3d):
        nb, nl, ns = d3d.shape
        byteperline = nb
        d3d = d3d.transpose([1,2,0]).copy()
    
        return QImage(d3d.data, ns, nl, QImage.Format_RGB888)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    unknown's avatar
    unknown committed
    
    
    unknown's avatar
    unknown committed
    class ImageChipBuffer(object):
    
    
        def __init__(self):
    
            self.data = dict()
            self.BBox = None
            self.SRS = None
    
    unknown's avatar
    unknown committed
            pass
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def hasDataCube(self, TSD):
            return TSD in self.data.keys()
    
    unknown's avatar
    unknown committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def getMissingBands(self, TSD, bands):
    
    unknown's avatar
    unknown committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            missing = set(bands)
            if TSD in self.data.keys():
    
                missing = missing - set(self.data[TSD].keys())
    
    unknown's avatar
    unknown committed
            return missing
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def addDataCube(self, TSD, chipData):
    
    unknown's avatar
    unknown committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert self.BBox is not None, 'Please initialize the bounding box first.'
    
    unknown's avatar
    unknown committed
            assert isinstance(chipData, dict)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if TSD not in self.data.keys():
                self.data[TSD] = dict()
            self.data[TSD].update(chipData)
    
        def getDataCube(self, TSD):
            return self.data.get(TSD)
    
    unknown's avatar
    unknown committed
    
    
        def getChipArray(self, TSD, band_view, mode='rgb'):
            assert mode in ['rgb', 'bgr']
            bands = band_view.getBands(TSD.sensor)
            band_ranges = band_view.getRanges(TSD.sensor)
            nb = len(bands)
            assert nb == 3 and nb == len(band_ranges)
            assert TSD in self.data.keys(), 'Time Series Datum {} is not in buffer'.format(TSD.getDate())
            chipData = self.data[TSD]
            for b in bands:
                assert b in chipData.keys()
    
    
    
            nl, ns = chipData[bands[0]].shape
    
            dtype= 'uint8'
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            array_data = np.ndarray((nl,ns, nb), dtype=dtype)
    
    
            if mode == 'rgb':
                ch_dst = [0,1,2]
            elif mode == 'bgr':
                # r -> dst channel 2
                # g -> dst channel 1
                # b -> dst channel 0
                ch_dst = [2,1,0]
            for i, i_dst in enumerate(ch_dst):
    
                offset = band_ranges[i][0]
                scale = 255./band_ranges[i][1]
    
                res = pg.rescaleData(chipData[bands[i]], scale, offset, dtype='float')
                np.clip(res, 0, 255, out=res)
                array_data[:,:,i_dst] = res
    
            return array_data
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def getChipRGB(self, TSD, band_view):
    
            bands = band_view.getBands(TSD.sensor)
            band_ranges = band_view.getRanges(TSD.sensor)
    
    unknown's avatar
    unknown committed
            assert len(bands) == 3 and len(bands) == len(band_ranges)
    
            assert TSD in self.data.keys(), 'Time Series Datum {} is not in buffer'.format(TSD.getDate())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            chipData = self.data[TSD]
    
    unknown's avatar
    unknown committed
            for b in bands:
                assert b in chipData.keys()
    
            nl, ns = chipData[bands[0]].shape
            rgb_data = np.ndarray((3,nl,ns), dtype='float')
    
            for i, b in enumerate(bands):
                range = band_ranges[i]
                data = chipData[b].astype('float')
                data -= range[0]
                data *= 255./range[1]
                rgb_data[i,:] = data
    
            np.clip(rgb_data, 0, 255, out=rgb_data)
            rgb_data = rgb_data.astype('uint8')
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if band_view.useMaskValues():
                rgb = band_view.getMaskColor()
    
    unknown's avatar
    unknown committed
                is_masked = np.where(np.logical_not(chipData['mask']))
                for i, c in enumerate(rgb):
                    rgb_data[i, is_masked[0], is_masked[1]] = c
    
    
    unknown's avatar
    unknown committed
    
    
        def getChipImage(self, date, view):
            rgb = self.getChipRGB(date, view)
            nb, nl, ns = rgb.shape
            rgb = rgb.transpose([1,2,0]).copy('C')
            return QImage(rgb.data, ns, nl, QImage.Format_RGB888)
    
    unknown's avatar
    unknown committed
    
        def clear(self):
            self.data.clear()
    
        def setBoundingBox(self, BBox):
            assert type(BBox) is ogr.Geometry
            SRS = BBox.GetSpatialReference()
            assert SRS is not None
            if self.BBox is None or not self.BBox.Equals(BBox) or not self.SRS.IsSame(SRS):
    
    unknown's avatar
    unknown committed
                self.BBox = BBox
                self.SRS = SRS
    
        def __repr__(self):
            info = ['Chipbuffer']
            info.append('Bounding Box: {}'.format(self.bbBoxWkt))
            info.append('Chips: {}'.format(len(self.data)))
            return '\n'.join(info)
    
    
    
    list2str = lambda ll : '\n'.join([str(l) for l in ll])
    
    
    class TimeSeriesViewer:
    
    unknown's avatar
    unknown committed
        """QGIS Plugin Implementation."""
    
        def __init__(self, iface):
            """Constructor.
    
            :param iface: An interface instance that will be passed to this class
                which provides the hook by which you can manipulate the QGIS
                application at run time.
            :type iface: QgsInterface
            """
            # Save reference to the QGIS interface
            self.iface = iface
    
            if isinstance(self.iface, qgis.gui.QgisInterface):
                import console
                console.show_console()
    
    unknown's avatar
    unknown committed
            # Create the dialog (after translation) and keep reference
    
            from timeseriesviewer.ui.widgets import TimeSeriesViewerUI
    
            self.ui = TimeSeriesViewerUI()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    
            #init empty time series
            self.TS = TimeSeries()
            self.hasInitialCenterPoint = False
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.TS.sigTimeSeriesDatumAdded.connect(self.ua_datumAdded)
            self.TS.sigChanged.connect(self.timeseriesChanged)
            self.TS.sigProgress.connect(self.ua_TSprogress)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            #init TS model
            TSM = TimeSeriesTableModel(self.TS)
            D = self.ui
            D.tableView_TimeSeries.setModel(TSM)
            D.tableView_TimeSeries.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents)
    
            self.BAND_VIEWS = list()
    
    unknown's avatar
    unknown committed
            self.ImageChipBuffer = ImageChipBuffer()
    
            self.PIXMAPS = PixmapBuffer()
            self.PIXMAPS.sigPixmapCreated.connect(self.showSubset)
    
    unknown's avatar
    unknown committed
            self.CHIPWIDGETS = collections.OrderedDict()
    
            self.ValidatorPxX = QIntValidator(0,99999)
            self.ValidatorPxY = QIntValidator(0,99999)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            #connect actions with logic
    
            #D.btn_showPxCoordinate.clicked.connect(lambda: self.showSubsetsStart())
            D.actionSelectCenter.triggered.connect(self.ua_selectByCoordinate)
            D.actionSelectArea.triggered.connect(self.ua_selectByRectangle)
    
            D.actionAddBandView.triggered.connect(self.ua_addBandView)
            #D.actionRemoveBandView.triggered.connect(self.ua_removeBandView)
    
            D.actionAddTSD.triggered.connect(self.ua_addTSImages)
            D.actionRemoveTSD.triggered.connect(self.ua_removeTSD)
    
            D.actionLoadTS.triggered.connect(self.ua_loadTSFile)
            D.actionClearTS.triggered.connect(self.ua_clear_TS)
            D.actionSaveTS.triggered.connect(self.ua_saveTSFile)
            D.actionAddTSExample.triggered.connect(self.ua_loadExampleTS)
    
    
            D.btn_labeling_clear.clicked.connect(D.tb_labeling_text.clear)
    
            D.actionAbout.triggered.connect( \
    
                lambda: QMessageBox.about(self.ui, 'SenseCarbon TimeSeriesViewer', 'A viewer to visualize raster time series data'))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            D.actionFirstTSD.triggered.connect(lambda: self.setDOISliderValue('first'))
            D.actionLastTSD.triggered.connect(lambda: self.setDOISliderValue('last'))
            D.actionNextTSD.triggered.connect(lambda: self.setDOISliderValue('next'))
            D.actionPreviousTSD.triggered.connect(lambda: self.setDOISliderValue('previous'))
    
    
            D.sliderDOI.valueChanged.connect(self.setDOI)
    
    unknown's avatar
    unknown committed
            D.spinBox_ncpu.setRange(0, multiprocessing.cpu_count())
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.RectangleMapTool = None
            self.PointMapTool = None
    
            self.canvasCrs = QgsCoordinateReferenceSystem()
    
    unknown's avatar
    unknown committed
            if self.iface:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                self.canvas = self.iface.mapCanvas()
    
    unknown's avatar
    unknown committed
                self.RectangleMapTool = qgis_add_ins.RectangleMapTool(self.canvas)
    
                self.RectangleMapTool.rectangleDrawed.connect(self.setSpatialSubset)
    
    unknown's avatar
    unknown committed
                self.PointMapTool = qgis_add_ins.PointMapTool(self.canvas)
    
                self.PointMapTool.coordinateSelected.connect(self.setSpatialSubset)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            else:
                D.btnSelectCenterCoordinate.setEnabled(False)
                D.btnSelectArea.setEnabled(False)
    
                #self.RectangleMapTool.connect(self.ua_selectByRectangle_Done)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.ICP = D.scrollAreaSubsetContent.layout()
            D.scrollAreaBandViewsContent.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
            self.BVP = self.ui.scrollAreaBandViewsContent.layout()
    
    unknown's avatar
    unknown committed
            self.check_enabled()
            s = ""
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        def setDOISliderValue(self, key):
            ui = self.ui
            v = ui.sliderDOI.value()
            if key == 'first':
                v = ui.sliderDOI.minimum()
            elif key == 'last':
                v = ui.sliderDOI.maximum()
            elif key =='next':
                v = min([v+1,ui.sliderDOI.maximum()])
            elif key =='previous':
                v = max([v - 1, ui.sliderDOI.minimum()])
            ui.sliderDOI.setValue(v)
    
        def setDOI(self, i):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            TSD = self.TS.data[i-1]
    
            self.ui.labelDOI.setText(str(TSD.date))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            s = ""
    
    
        @staticmethod
        def icon():
            return QIcon(':/plugins/SenseCarbon/icon.png')
    
        def icon(self):
            return TimeSeriesViewer.icon()
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def timeseriesChanged(self):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            D.sliderDOI.setMinimum(1)
            D.sliderDOI.setMaximum(len(self.TS.data))
    
            if len(self.TS.data)>0 and not self.hasInitialCenterPoint:
                extent = self.TS.getMaxExtent(self.canvasCrs)
                self.setSpatialSubset(extent.center(), self.canvasCrs)
                self.hasInitialCenterPoint = True
            if len(self.TS.data) == 0:
                self.hasInitialCenterPoint = False
    
    
        def ua_loadTSFile(self, path=None):
            if path is None or path is False:
    
                path = QFileDialog.getOpenFileName(self.ui, 'Open Time Series file', '')
    
                M = self.ui.tableView_TimeSeries.model()
    
                M.beginResetModel()
                self.ua_clear_TS()
                self.TS.loadFromFile(path)
                M.endResetModel()
    
    
            self.check_enabled()
    
        def ua_saveTSFile(self):
    
            path = QFileDialog.getSaveFileName(self.ui, caption='Save Time Series file')
    
            if path is not None:
                self.TS.saveToFile(path)
    
    
        def ua_loadExampleTS(self):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            from timeseriesviewer import PATH_EXAMPLE_TIMESERIES
            if not os.path.exists(PATH_EXAMPLE_TIMESERIES):
    
                QMessageBox.information(self.ui, 'File not found', '{} - this file describes an exemplary time series.'.format(path_example))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                self.ua_loadTSFile(path=PATH_EXAMPLE_TIMESERIES)
    
    unknown's avatar
    unknown committed
        def ua_selectByRectangle(self):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if self.RectangleMapTool is not None:
                self.canvas.setMapTool(self.RectangleMapTool)
    
    unknown's avatar
    unknown committed
    
        def ua_selectByCoordinate(self):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if self.PointMapTool is not None:
                self.canvas.setMapTool(self.PointMapTool)
    
    
        def setCanvasSRS(self,srs):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert isinstance(srs, QgsCoordinateReferenceSystem)
    
            #self.canvas_srs = srs
            self.canvasCrs = srs
            self.ui.tb_bb_srs.setPlainText(self.canvasCrs.toWkt())
    
        def setSpatialSubset(self, geometry, crs):
            assert isinstance(crs, QgsCoordinateReferenceSystem)
            assert isinstance(geometry, QgsRectangle) or isinstance(geometry, QgsPoint)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            x = D.spinBox_coordinate_x.value()
            y = D.spinBox_coordinate_x.value()
            dx = D.doubleSpinBox_subset_size_x.value()
            dy = D.doubleSpinBox_subset_size_y.value()
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            if type(geometry) is QgsRectangle:
                center = geometry.center()
                x = center.x()
                y = center.y()
    
                dx = geometry.xMaximum() - geometry.xMinimum()
                dy = geometry.yMaximum() - geometry.yMinimum()
    
            if type(geometry) is QgsPoint:
                x = geometry.x()
                y = geometry.y()
    
            D.doubleSpinBox_subset_size_x.setValue(dx)
            D.doubleSpinBox_subset_size_y.setValue(dy)
            D.spinBox_coordinate_x.setValue(x)
            D.spinBox_coordinate_y.setValue(y)
    
    
            if D.cb_loadSubsetDirectly.isChecked():
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def qgs_handleMouseDown(self, pt, btn):
    
    unknown's avatar
    unknown committed
    
    
        def ua_TSprogress(self, v_min, v, v_max):
            assert v_min <= v and v <= v_max
    
            if v_min < v_max:
    
                if P.minimum() != v_min or P.maximum() != v_max:
                    P.setRange(v_min, v_max)
                else:
                    s = ""
    
                P.setValue(v)
    
    unknown's avatar
    unknown committed
    
    
        def ua_datumAdded(self, TSD):
    
            if len(self.TS) == 1:
                self.setCanvasSRS(TSD.lyrImg.crs())
                if self.ui.spinBox_coordinate_x.value() == 0.0 and \
                   self.ui.spinBox_coordinate_y.value() == 0.0:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    bbox = self.TS.getMaxExtent(srs=self.canvas_srs)
    
    
                    self.ui.spinBox_coordinate_x.setRange(bbox.xMinimum(), bbox.xMaximum())
                    self.ui.spinBox_coordinate_y.setRange(bbox.yMinimum(), bbox.yMaximum())
    
                    #x, y = self.TS.getSceneCenter()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    c = bbox.center()
    
                    self.ui.spinBox_coordinate_x.setValue(c.x())
                    self.ui.spinBox_coordinate_y.setValue(c.y())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            #self.dlg.sliderDOI
    
    
            self.ui.tableView_TimeSeries.resizeColumnsToContents()
    
    unknown's avatar
    unknown committed
    
        def check_enabled(self):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            hasTS = len(self.TS) > 0 or DEBUG
    
            hasTSV = len(self.BAND_VIEWS) > 0
    
    unknown's avatar
    unknown committed
            hasQGIS = qgis_available
    
    
            #D.tabWidget_viewsettings.setEnabled(hasTS)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            #D.btn_showPxCoordinate.setEnabled(hasTS and hasTSV)
            D.btnSelectCenterCoordinate.setEnabled(hasQGIS)
            D.btnSelectArea.setEnabled(hasQGIS)
    
    unknown's avatar
    unknown committed
    
    
    
    unknown's avatar
    unknown committed
    
    
        # noinspection PyMethodMayBeStatic
        def tr(self, message):
            """Get the translation for a string using Qt translation API.
    
            We implement this ourselves since we do not inherit QObject.
    
            :param message: String for translation.
            :type message: str, QString
    
            :returns: Translated version of message.
            :rtype: QString
            """
            # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
            return QCoreApplication.translate('EnMAPBox', message)
    
    
    
    
    
    
        def ua_addTSD_to_QGIS(self, TSD, bands):
    
    unknown's avatar
    unknown committed
        def unload(self):
    
            """Removes the plugin menu item and icon """
            self.iface.removeToolBarIcon(self.action)
    
    unknown's avatar
    unknown committed