Skip to content
Snippets Groups Projects
timeseries.py 30.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • # -*- coding: utf-8 -*-
    """
    /***************************************************************************
    
                                  -------------------
            begin                : 2015-08-20
            git sha              : $Format:%H$
            copyright            : (C) 2017 by HU-Berlin
            email                : benjamin.jakimow@geo.hu-berlin.de
     ***************************************************************************/
    
    /***************************************************************************
     *                                                                         *
     *   This program is free software; you can redistribute it and/or modify  *
     *   it under the terms of the GNU General Public License as published by  *
     *   the Free Software Foundation; either version 2 of the License, or     *
     *   (at your option) any later version.                                   *
     *                                                                         *
     ***************************************************************************/
    """
    # noinspection PyPep8Naming
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    from qgis import *
    from qgis.core import *
    from qgis.gui import *
    
    from qgis.PyQt.QtGui import *
    from qgis.PyQt.QtWidgets import *
    from qgis.PyQt.QtCore import *
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    from timeseriesviewer.utils import SpatialExtent, loadUI
    
    gdal.SetConfigOption('VRT_SHARED_SOURCE', '0') #!important. really. do not change this.
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    import numpy as np
    
    
    from timeseriesviewer import SETTINGS, messageLog
    
    from timeseriesviewer.dateparser import parseDateFromDataSet
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    def transformGeometry(geom, crsSrc, crsDst, trans=None):
        if trans is None:
            assert isinstance(crsSrc, QgsCoordinateReferenceSystem)
            assert isinstance(crsDst, QgsCoordinateReferenceSystem)
            return transformGeometry(geom, None, None, trans=QgsCoordinateTransform(crsSrc, crsDst))
        else:
            assert isinstance(trans, QgsCoordinateTransform)
            return trans.transform(geom)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    METRIC_EXPONENTS = {
        "nm":-9,"um": -6, "mm":-3, "cm":-2, "dm":-1, "m": 0,"hm":2, "km":3
    }
    #add synonyms
    METRIC_EXPONENTS['nanometers'] = METRIC_EXPONENTS['nm']
    METRIC_EXPONENTS['micrometers'] = METRIC_EXPONENTS['um']
    METRIC_EXPONENTS['millimeters'] = METRIC_EXPONENTS['mm']
    METRIC_EXPONENTS['centimeters'] = METRIC_EXPONENTS['cm']
    METRIC_EXPONENTS['decimeters'] = METRIC_EXPONENTS['dm']
    METRIC_EXPONENTS['meters'] = METRIC_EXPONENTS['m']
    METRIC_EXPONENTS['hectometers'] = METRIC_EXPONENTS['hm']
    METRIC_EXPONENTS['kilometers'] = METRIC_EXPONENTS['km']
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    def convertMetricUnit(value, u1, u2):
        assert u1 in METRIC_EXPONENTS.keys()
        assert u2 in METRIC_EXPONENTS.keys()
    
        e1 = METRIC_EXPONENTS[u1]
        e2 = METRIC_EXPONENTS[u2]
    
        return value * 10**(e1-e2)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    def getDS(pathOrDataset):
        if isinstance(pathOrDataset, gdal.Dataset):
            return pathOrDataset
        else:
            ds = gdal.Open(pathOrDataset)
            assert isinstance(ds, gdal.Dataset)
            return ds
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    class SensorInstrument(QObject):
    
        SensorNameSettingsPrefix = 'SensorName.'
    
        sigNameChanged = pyqtSignal(str)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        LUT_Wavelengths = dict({'B':480,
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                                'G':570,
                                'R':660,
                                'nIR':850,
                                'swIR':1650,
                                'swIR1':1650,
                                'swIR2':2150
                                })
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        """
        Describes a Sensor Configuration
        """
    
        def __init__(self, pathImg, sensor_name=None):
    
            super(SensorInstrument, self).__init__()
    
            ds = getDS(pathImg)
            self.nb, nl, ns, crs, self.px_size_x, self.px_size_y = getSpatialPropertiesFromDataset(ds)
    
            self.bandDataType = ds.GetRasterBand(1).DataType
            self.pathImg = ds.GetFileList()[0]
    
            self.bandNames = [ds.GetRasterBand(b+1).GetDescription() for b in range(self.nb)]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            self.TS = None
    
            assert self.px_size_x > 0
            assert self.px_size_y > 0
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            #find wavelength
    
            wl, wlu = parseWavelengthFromDataSet(ds)
    
            self.wavelengthUnits = wlu
    
            if wl is None:
                self.wavelengths = None
            else:
                self.wavelengths = np.asarray(wl)
    
            self._id = '{}b{}m'.format(self.nb, self.px_size_x)
            if wl is not None:
    
                self._id += ';'.join([str(w) for w in self.wavelengths])+str(self.wavelengthUnits)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
                sensor_name = '{}bands@{}m'.format(self.nb, self.px_size_x)
    
                sensor_name = SETTINGS.value(self._sensorSettingsKey(), sensor_name)
    
    
            self.setName(sensor_name)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            self.hashvalue = hash(','.join(self.bandNames))
    
    
        def _sensorSettingsKey(self):
            return SensorInstrument.SensorNameSettingsPrefix+self._id
    
        def setName(self, name):
            self._name = name
    
    
            SETTINGS.setValue(self._sensorSettingsKey(), name)
    
            self.sigNameChanged.emit(self.name())
    
        def name(self):
            return self._name
    
    
        def dataType(self, p_int):
            return self.bandDataType
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        def bandClosestToWavelength(self, wl, wl_unit='nm'):
            """
    
            Returns the band index (>=0) of the band closest to wavelength wl
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            :param wl:
            :param wl_unit:
            :return:
            """
            if not self.wavelengthsDefined():
                return None
    
    
            if wl in SensorInstrument.LUT_Wavelengths.keys():
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                wl_unit = 'nm'
    
                wl = SensorInstrument.LUT_Wavelengths[wl]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            wl = float(wl)
            if self.wavelengthUnits != wl_unit:
                wl = convertMetricUnit(wl, wl_unit, self.wavelengthUnits)
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    
    
        def wavelengthsDefined(self):
            return self.wavelengths is not None and \
                    self.wavelengthUnits is not None
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def __eq__(self, other):
    
            if not isinstance(other, SensorInstrument):
                return False
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            return self.nb == other.nb and \
                   self.px_size_x == other.px_size_x and \
                   self.px_size_y == other.px_size_y
    
        def __hash__(self):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        def __repr__(self):
    
            return str(self.__class__) +' ' + self.name()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        def getDescription(self):
            info = []
    
            info.append(self.name())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            info.append('{} Bands'.format(self.nb))
            info.append('Band\tName\tWavelength')
            for b in range(self.nb):
    
                if self.wavelengths is not None:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    wl = str(self.wavelengths[b])
                else:
                    wl = 'unknown'
                info.append('{}\t{}\t{}'.format(b + 1, self.bandNames[b], wl))
    
            return '\n'.join(info)
    
    
    
    def verifyInputImage(path, vrtInspection=''):
    
        if path is None or not isinstance(path, str):
    
        ds = gdal.Open(path)
    
            #logger.error('{}GDAL unable to open: '.format(vrtInspection, path))
    
        if ds.RasterCount == 0 and len(ds.GetSubDatasets()) > 0:
    
            #logger.error('Can not open container {}.\nPlease specify a subdataset'.format(path))
    
        if ds.GetDriver().ShortName == 'VRT':
    
            vrtInspection = 'VRT Inspection {}\n'.format(path)
    
            nextFiles = set(ds.GetFileList()) - set([path])
            validSrc = [verifyInputImage(p, vrtInspection=vrtInspection) for p in nextFiles]
    
            if not all(validSrc):
                return False
    
    
        from timeseriesviewer.dateparser import parseDateFromDataSet
        date = parseDateFromDataSet(ds)
        if date is None:
            return False
    
    
    def pixel2coord(gt, x, y):
        """Returns global coordinates from pixel x, y coords"""
        """https://scriptndebug.wordpress.com/2014/11/24/latitudelongitude-of-each-pixel-using-python-and-gdal/"""
        xoff, a, b, yoff, d, e = gt
        xp = a * x + b * y + xoff
        yp = d * x + e * y + yoff
        return (xp, yp)
    
    class TimeSeriesDatum(QObject):
    
        @staticmethod
        def createFromPath(path):
            """
            Creates a valid TSD or returns None if this is impossible
            :param path:
            :return:
            """
    
        """
        Collects all data sets related to one sensor
        """
    
        sigVisibilityChanged = pyqtSignal(bool)
        sigRemoveMe = pyqtSignal()
    
        def __init__(self, timeSeries, pathImg):
    
            super(TimeSeriesDatum,self).__init__()
    
            self.pathImg = ds.GetFileList()[0] if isinstance(pathImg, gdal.Dataset) else pathImg
    
            self.timeSeries = timeSeries
            self.nb, self.nl, self.ns, self.crs, px_x, px_y = getSpatialPropertiesFromDataset(ds)
    
            self.date = parseDateFromDataSet(ds)
    
            assert self.date is not None, 'Unable to find acquisition date of {}'.format(pathImg)
    
            from timeseriesviewer.dateparser import DOYfromDatetime64
            self.doy = DOYfromDatetime64(self.date)
    
            UL = QgsPointXY(*pixel2coord(gt, 0, 0))
            LR = QgsPointXY(*pixel2coord(gt, self.ns, self.nl))
    
            self._spatialExtent = SpatialExtent(self.crs, UL, LR)
    
    
            self.srs_wkt = str(self.crs.toWkt())
    
    
            self.mVisibility = True
    
    
        def rank(self):
            return self.timeSeries.index(self)
    
        def setVisibility(self, b):
            old = self.mVisibility
            self.mVisibility = b
            if old != self.mVisibility:
                self.sigVisibilityChanged.emit(b)
    
        def isVisible(self):
            return self.mVisibility
    
    
    
        def getDate(self):
            return np.datetime64(self.date)
    
    
        def getSpatialReference(self):
            return self.crs
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def spatialExtent(self):
    
    
        def __repr__(self):
            return 'TS Datum {} {}'.format(self.date, str(self.sensor))
    
    
        def __eq__(self, other):
            return self.date == other.date and self.sensor.id() == other.sensor.id()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        def __lt__(self, other):
            if self.date < other.date:
                return True
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            else:
    
                return self.sensor.id() < other.sensor.id()
    
            return hash((self.date,self.sensor.id()))
    
    class TimeSeriesTableView(QTableView):
    
        def __init__(self, parent=None):
            super(TimeSeriesTableView, self).__init__(parent)
    
        def contextMenuEvent(self, event):
    
            menu = QMenu(self)
    
            a = menu.addAction('Copy value(s)')
            a.triggered.connect(self.onCopyValues)
            a = menu.addAction('Check')
            a.triggered.connect(lambda : self.onSetCheckState(Qt.Checked))
            a = menu.addAction('Uncheck')
            a.triggered.connect(lambda: self.onSetCheckState(Qt.Unchecked))
    
            menu.popup(QCursor.pos())
    
    
        def onSetCheckState(self, checkState):
            indices = self.selectionModel().selectedIndexes()
            rows = sorted(list(set([i.row() for i in indices])))
            model = self.model()
            if isinstance(model, TimeSeriesTableModel):
                for r in rows:
                    idx = model.index(r,0)
                    model.setData(idx, checkState, Qt.CheckStateRole)
    
        def onCopyValues(self):
            indices = self.selectionModel().selectedIndexes()
            model = self.model()
            if isinstance(model, TimeSeriesTableModel):
                from collections import OrderedDict
                R = OrderedDict()
                for idx in indices:
                    if not idx.row() in R.keys():
                        R[idx.row()] = []
                    R[idx.row()].append(model.data(idx, Qt.DisplayRole))
                info = []
                for k, values in R.items():
                    info.append(';'.join([str(v) for v in values]))
                info = '\n'.join(info)
                QApplication.clipboard().setText(info)
            s = ""
    
    
        def selectSelectedObservations(b):
            assert isinstance(b, bool)
    
    
    
    class TimeSeriesDockUI(QgsDockWidget, loadUI('timeseriesdock.ui')):
    
        def __init__(self, parent=None):
            super(TimeSeriesDockUI, self).__init__(parent)
            self.setupUi(self)
            self.btnAddTSD.setDefaultAction(parent.actionAddTSD)
            self.btnRemoveTSD.setDefaultAction(parent.actionRemoveTSD)
            self.btnLoadTS.setDefaultAction(parent.actionLoadTS)
            self.btnSaveTS.setDefaultAction(parent.actionSaveTS)
            self.btnClearTS.setDefaultAction(parent.actionClearTS)
    
            self.progressBar.setMinimum(0)
            self.setProgressInfo(0,100, 'Add images to fill time series')
            self.progressBar.setValue(0)
            self.progressInfo.setText(None)
            self.frameFilters.setVisible(False)
    
    
    
        def setStatus(self):
            from timeseriesviewer.timeseries import TimeSeries
            if isinstance(self.TS, TimeSeries):
                ndates = len(self.TS)
                nsensors = len(set([tsd.sensor for tsd in self.TS]))
                msg = '{} scene(s) from {} sensor(s)'.format(ndates, nsensors)
                if ndates > 1:
                    msg += ', {} to {}'.format(str(self.TS[0].date), str(self.TS[-1].date))
                self.progressInfo.setText(msg)
    
        def setProgressInfo(self, nDone, nMax, message=None):
            if self.progressBar.maximum() != nMax:
                self.progressBar.setMaximum(nMax)
            self.progressBar.setValue(nDone)
            self.progressInfo.setText(message)
            QgsApplication.processEvents()
            if nDone == nMax:
                QTimer.singleShot(3000, lambda: self.setStatus())
    
        def onSelectionChanged(self, *args):
            self.btnRemoveTSD.setEnabled(self.SM is not None and len(self.SM.selectedRows()) > 0)
    
        def selectedTimeSeriesDates(self):
            if self.SM is not None:
                return [self.TSM.data(idx, Qt.UserRole) for idx in self.SM.selectedRows()]
            return []
    
    
            from timeseriesviewer.timeseries import TimeSeries
            self.TS = TS
            self.TSM = None
            self.SM = None
            self.timeSeriesInitialized = False
    
            if isinstance(TS, TimeSeries):
                from timeseriesviewer.timeseries import TimeSeriesTableModel
                self.TSM = TimeSeriesTableModel(self.TS)
                self.tableView_TimeSeries.setModel(self.TSM)
                self.SM = QItemSelectionModel(self.TSM)
                self.tableView_TimeSeries.setSelectionModel(self.SM)
                self.SM.selectionChanged.connect(self.onSelectionChanged)
                self.tableView_TimeSeries.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents)
                TS.sigLoadingProgress.connect(self.setProgressInfo)
    
            self.onSelectionChanged()
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    class TimeSeries(QObject):
    
        sigTimeSeriesDatesAdded = pyqtSignal(list)
        sigTimeSeriesDatesRemoved = pyqtSignal(list)
    
        sigLoadingProgress = pyqtSignal(int, int, str)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        sigSensorAdded = pyqtSignal(SensorInstrument)
        sigSensorRemoved = pyqtSignal(SensorInstrument)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def __init__(self, imageFiles=None, maskFiles=None):
            QObject.__init__(self)
    
            #define signals
    
            #fire when a new TSD is added
    
    
            #self.data = collections.OrderedDict()
            self.data = list()
    
    
            self.shape = None
    
            self.Sensors = collections.OrderedDict()
    
            self.Pool = None
    
            if imageFiles is not None:
                self.addFiles(imageFiles)
            if maskFiles is not None:
                self.addMasks(maskFiles)
    
        _sep = ';'
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def sensors(self):
    
            return list(self.Sensors.keys())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def loadFromFile(self, path, n_max=None):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            images = []
            masks = []
            with open(path, 'r') as f:
                lines = f.readlines()
                for l in lines:
                    if re.match('^[ ]*[;#&]', l):
                        continue
    
                    parts = re.split('[\n'+TimeSeries._sep+']', l)
                    parts = [p for p in parts if p != '']
                    images.append(parts[0])
                    if len(parts) > 1:
                        masks.append(parts[1])
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if n_max:
                n_max = min([len(images), n_max])
                self.addFiles(images[0:n_max])
            else:
                self.addFiles(images)
            #self.addMasks(masks)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def saveToFile(self, path):
            if path is None or len(path) == 0:
    
    benjamin.jakimow@geo.hu-berlin.de's avatar
    benjamin.jakimow@geo.hu-berlin.de committed
                return None
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            lines = []
            lines.append('#Time series definition file: {}'.format(np.datetime64('now').astype(str)))
    
            lines.append('#<image path>')
    
            for TSD in self.data:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
                line = TSD.pathImg
                lines.append(line)
    
            lines = [l+'\n' for l in lines]
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            with open(path, 'w') as f:
                f.writelines(lines)
    
                messageLog('Time series source images written to {}'.format(path))
    
    
    benjamin.jakimow@geo.hu-berlin.de's avatar
    benjamin.jakimow@geo.hu-berlin.de committed
            return path
    
        def getPixelSizes(self):
    
            r = []
            for sensor in self.Sensors.keys():
                r.append((QgsRectangle(sensor.px_size_x, sensor.px_size_y)))
            return r
    
            return None
    
        def getMaxSpatialExtent(self, crs=None):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                return None
    
            extent = self.data[0].spatialExtent()
    
            assert isinstance(extent, SpatialExtent)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if len(self.data) > 1:
                for TSD in self.data[1:]:
    
                    extent.combineExtentWith(TSD.spatialExtent())
    
                    x, y = extent.upperRight()
                    if y > 0:
                        s =""
    
            if isinstance(crs, QgsCoordinateReferenceSystem):
                extent = extent.toCrs(crs)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            return extent
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    
        def tsdFromPath(self, path):
            for tsd in self.data:
                if tsd.pathImg == path:
                    return tsd
            return False
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def getObservationDates(self):
            return [tsd.getDate() for tsd in self.data]
    
    
        def getTSD(self, pathOfInterest):
            for tsd in self.data:
                if tsd.pathImg == pathOfInterest:
                    return tsd
            return None
    
    
        def getTSDs(self, dateOfInterest=None, sensorOfInterest=None):
    
                tsds = [tsd for tsd in tsds if tsd.getDate() == dateOfInterest]
    
            if sensorOfInterest:
                tsds = [tsd for tsd in tsds if tsd.sensor == sensorOfInterest]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            return tsds
    
        def clear(self):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def removeDates(self, TSDs):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for TSD in TSDs:
    
                assert type(TSD) is TimeSeriesDatum
    
                self.data.remove(TSD)
                TSD.timeSeries = None
                removed.append(TSD)
    
    
                if len(self.Sensors[S]) == 0:
                    self.Sensors.pop(S)
    
            self.sigTimeSeriesDatesRemoved.emit(removed)
    
    
        def addTimeSeriesDates(self, timeSeriesDates):
            assert isinstance(timeSeriesDates, list)
            added = list()
            for TSD in timeSeriesDates:
    
                assert isinstance(TSD, TimeSeriesDatum)
    
                try:
                    sensorAdded = False
                    existingSensors = list(self.Sensors.keys())
                    if TSD.sensor not in existingSensors:
                        self.Sensors[TSD.sensor] = list()
                        sensorAdded = True
                    else:
                        TSD.sensor = existingSensors[existingSensors.index(TSD.sensor)]
    
                    if TSD in self.data:
    
                        print('Time series date-time already added ({} {}). \nPlease use VRTs to mosaic images with same acquisition date-time.'.format(str(TSD), TSD.pathImg), file=sys.stderr)
    
                    else:
                        self.Sensors[TSD.sensor].append(TSD)
                        #insert sorted
    
                        bisect.insort(self.data, TSD)
    
                        TSD.timeSeries = self
                        TSD.sigRemoveMe.connect(lambda : self.removeDates([TSD]))
    
                        added.append(TSD)
                    if sensorAdded:
                        self.sigSensorAdded.emit(TSD.sensor)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
                except:
                    exc_type, exc_value, exc_traceback = sys.exc_info()
                    traceback.print_exception(exc_type, exc_value, exc_traceback, limit=2)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            if len(added) > 0:
                self.sigTimeSeriesDatesAdded.emit(added)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def addFiles(self, files):
    
            if isinstance(files, str):
                files = [files]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert isinstance(files, list)
    
            files = [f for f in files if f is not None]
    
    
            nMax = len(files)
            nDone = 0
            self.sigLoadingProgress.emit(0,nMax, 'Start loading {} files...'.format(nMax))
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for i, file in enumerate(files):
    
                tsd = TimeSeriesDatum.createFromPath(file)
                if tsd is None:
    
                    msg = 'Unable to add: {}'.format(os.path.basename(file))
    
                    messageLog(msg)
    
                else:
    
                    self.addTimeSeriesDates([tsd])
    
                    msg = 'Added {}'.format(os.path.basename(file))
                    self.sigRuntimeStats.emit({'dt_addTSD':np.datetime64('now')-t0})
                nDone += 1
                self.sigLoadingProgress.emit(nDone, nMax, msg)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def __len__(self):
            return len(self.data)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def __iter__(self):
            return iter(self.data)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def __getitem__(self, slice):
            return self.data[slice]
    
        def __delitem__(self, slice):
            self.removeDates(slice)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def __contains__(self, item):
            return item in self.data
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        def __repr__(self):
            info = []
            info.append('TimeSeries:')
            l = len(self)
            info.append('  Scenes: {}'.format(l))
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            return '\n'.join(info)
    
    
    
    
    class TimeSeriesTableModel(QAbstractTableModel):
    
        def __init__(self, TS, parent=None, *args):
    
            super(TimeSeriesTableModel, self).__init__()
            assert isinstance(TS, TimeSeries)
    
            self.cnDate = 'Date'
            self.cnSensor = 'Sensor'
            self.cnNS = 'ns'
            self.cnNL = 'nl'
            self.cnNB = 'nb'
            self.cnCRS = 'CRS'
            self.cnImage = 'Image'
            self.mColumnNames = [self.cnDate, self.cnSensor, \
                                self.cnNS, self.cnNL, self.cnNB, \
                                self.cnCRS, self.cnImage]
    
            self.TS = TS
            self.sensors = set()
            self.TS.sigTimeSeriesDatesRemoved.connect(self.removeTSDs)
            self.TS.sigTimeSeriesDatesAdded.connect(self.addTSDs)
    
    
            self.items = []
            self.sortColumnIndex = 0
            self.sortOrder = Qt.AscendingOrder
            self.addTSDs([tsd for tsd in self.TS])
    
        def removeTSDs(self, tsds):
            #self.TS.removeDates(tsds)
            for tsd in tsds:
                if tsd in self.TS:
                    #remove from TimeSeries first.
                    self.TS.removeDates([tsd])
                elif tsd in self.items:
                    idx = self.getIndexFromDate(tsd)
                    self.removeRows(idx.row(), 1)
    
            #self.sort(self.sortColumnIndex, self.sortOrder)
    
    
        def tsdChanged(self, tsd):
            idx = self.getIndexFromDate(tsd)
            self.dataChanged.emit(idx, idx)
    
    
        def sensorsChanged(self, sensor):
    
            i = self.mColumnNames.index(self.cnSensor)
    
            idx0 = self.createIndex(0, i)
            idx1 = self.createIndex(self.rowCount(), i)
            self.dataChanged.emit(idx0, idx1)
    
    
        def addTSDs(self, tsds):
    
    
            for tsd in tsds:
                assert isinstance(tsd, TimeSeriesDatum)
                row = bisect.bisect_left(self.items, tsd)
                self.beginInsertRows(QModelIndex(), row, row)
                self.items.insert(row, tsd)
                self.endInsertRows()
    
                #self.sort(self.sortColumnIndex, self.sortOrder)
    
    
            for tsd in tsds:
                assert isinstance(tsd, TimeSeriesDatum)
                tsd.sigVisibilityChanged.connect(lambda: self.tsdChanged(tsd))
    
            for sensor in set([tsd.sensor for tsd in tsds]):
                if sensor not in self.sensors:
                    self.sensors.add(sensor)
    
                    sensor.sigNameChanged.connect(self.sensorsChanged)
    
    
    
    
        def sort(self, col, order):
            if self.rowCount() == 0:
                return
    
            self.layoutAboutToBeChanged.emit()
    
            colName = self.mColumnNames[col]
    
            r = order != Qt.AscendingOrder
    
            if colName in ['date','ns','nl','sensor']:
                self.items.sort(key = lambda d:d.__dict__[colName], reverse=r)
    
            self.layoutChanged.emit()
            s = ""
    
    
        def rowCount(self, parent = QModelIndex()):
            return len(self.items)
    
    
        def removeRows(self, row, count , parent=QModelIndex()):
            self.beginRemoveRows(parent, row, row+count-1)
            toRemove = self.items[row:row+count]
            for tsd in toRemove:
                self.items.remove(tsd)
            self.endRemoveRows()
    
        def getIndexFromDate(self, tsd):
            return self.createIndex(self.items.index(tsd),0)
    
        def getDateFromIndex(self, index):
            if index.isValid():
                return self.items[index.row()]
            return None
    
        def getTimeSeriesDatumFromIndex(self, index):
            if index.isValid():
                i = index.row()
                if i >= 0 and i < len(self.items):
                    return self.items[i]
    
            return None
    
        def columnCount(self, parent = QModelIndex()):
    
            return len(self.mColumnNames)
    
    
        def data(self, index, role = Qt.DisplayRole):
            if role is None or not index.isValid():
                return None
    
            value = None
    
            columnName = self.mColumnNames[index.column()]
    
    
            TSD = self.getTimeSeriesDatumFromIndex(index)
    
            assert isinstance(TSD, TimeSeriesDatum)
    
            keys = list(TSD.__dict__.keys())
    
    
            if role == Qt.DisplayRole or role == Qt.ToolTipRole:
    
                if columnName == self.cnImage:
    
                    value = os.path.basename(TSD.pathImg)
    
                elif columnName == self.cnSensor:
    
                    if role == Qt.ToolTipRole:
                        value = TSD.sensor.getDescription()
                    else:
                        value = TSD.sensor.name()
    
                elif columnName == self.cnDate:
    
                    value = '{}'.format(TSD.date)
    
                elif columnName == self.cnImage:
    
                    value = TSD.pathImg
    
                elif columnName == self.cnCRS:
                    crs = TSD.crs
                    assert isinstance(crs, QgsCoordinateReferenceSystem)
                    value = crs.description()
    
                elif columnName in keys:
                    value = TSD.__dict__[columnName]
                else:
                    s = ""
            elif role == Qt.CheckStateRole:
    
                if columnName == self.cnDate:
    
                    value = Qt.Checked if TSD.isVisible() else Qt.Unchecked
    
            elif role == Qt.BackgroundColorRole:
                value = None
            elif role == Qt.UserRole:
                value = TSD
    
            return value
    
        def setData(self, index, value, role=None):
            if role is None or not index.isValid():
                return None
    
            if role is Qt.UserRole:
    
                s = ""
    
    
            columnName = self.mColumnNames[index.column()]
    
    
            TSD = self.getTimeSeriesDatumFromIndex(index)
    
            if columnName == self.cnDate and role == Qt.CheckStateRole:
    
                TSD.setVisibility(value != Qt.Unchecked)
                return True
            else:
                return False
    
            return False
    
        def flags(self, index):
            if index.isValid():
    
                columnName = self.mColumnNames[index.column()]
    
                flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
    
                if columnName == self.cnDate: #allow check state
    
                    flags = flags | Qt.ItemIsUserCheckable
    
                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.mColumnNames[col]
    
            elif orientation == Qt.Vertical and role == Qt.DisplayRole:
                return col
            return None
    
    def getSpatialPropertiesFromDataset(ds):
        assert isinstance(ds, gdal.Dataset)
    
        nb = ds.RasterCount
        nl = ds.RasterYSize
        ns = ds.RasterXSize
        proj = ds.GetGeoTransform()
        px_x = float(abs(proj[1]))
        px_y = float(abs(proj[5]))
    
        crs = QgsCoordinateReferenceSystem(ds.GetProjection())
    
        return nb, nl, ns, crs, px_x, px_y
    
    
    
    
    
    
    
    
    
    def parseWavelengthFromDataSet(ds):
        assert isinstance(ds, gdal.Dataset)
        wl = None
        wlu = None
    
        #see http://www.harrisgeospatial.com/docs/ENVIHeaderFiles.html for supported wavelength units
    
        regWLkey = re.compile('.*wavelength[_ ]*$', re.I)
        regWLUkey = re.compile('.*wavelength[_ ]*units?$', re.I)
        regNumeric = re.compile(r"([-+]?\d*\.\d+|[-+]?\d+)", re.I)
        regWLU = re.compile('((micro|nano|centi)meters)|(um|nm|mm|cm|m|GHz|MHz)', re.I)
    
        for domain in ds.GetMetadataDomainList():
            md = ds.GetMetadata_Dict(domain)
            for key, value in md.items():
                if wl is None and regWLkey.search(key):
                    numbers = regNumeric.findall(value)
                    if len(numbers) == ds.RasterCount:
                        wl = [float(n) for n in numbers]
    
                if wlu is None and regWLUkey.search(key):
                    match = regWLU.search(value)
                    if match:
    
                        wlu = match.group().lower()
    
                    names = ['nanometers', 'micrometers', 'millimeters', 'centimeters', 'decimeters']
                    si = ['nm', 'um', 'mm', 'cm', 'dm']
                    if wlu in names:
                        wlu = si[names.index(wlu)]
    
        return wl, wlu
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    
    def parseWavelength(lyr):
        wl = None
        wlu = None
        assert isinstance(lyr, QgsRasterLayer)
        md = [l.split('=') for l in str(lyr.metadata()).splitlines() if 'wavelength' in l.lower()]
        #see http://www.harrisgeospatial.com/docs/ENVIHeaderFiles.html for supported wavelength units
        regWLU = re.compile('((micro|nano|centi)meters)|(um|nm|mm|cm|m|GHz|MHz)')
        for kv in md:
            key, value = kv
            key = key.lower()
            if key == 'center wavelength':
    
                tmp = re.findall(r'\d*\.\d+|\d+', value) #find floats
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                if len(tmp) == 0:
    
                    tmp = re.findall(r'\d+', value) #find integers
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                if len(tmp) == lyr.bandCount():
                    wl = [float(w) for w in tmp]
    
            if key == 'wavelength units':
                match = regWLU.search(value)
                if match:
                    wlu = match.group()
    
                names = ['nanometers','micrometers','millimeters','centimeters','decimenters']
                si   = ['nm','um','mm','cm','dm']
                if wlu in names:
                    wlu = si[names.index(wlu)]
    
        return wl, wlu
    
    if __name__ == '__main__':
    
        q  = QApplication([])
        p = QProgressBar()
        p.setRange(0,0)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        print(convertMetricUnit(100, 'cm', 'm'))
        print(convertMetricUnit(1, 'm', 'um'))