Skip to content
Snippets Groups Projects
timeseries.py 25.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    from __future__ import absolute_import
    import six, sys, os, gc, re, collections, site, inspect, time, traceback, copy
    import bisect
    from osgeo import gdal, ogr
    
    from qgis import *
    from qgis.core import *
    from qgis.gui import *
    from PyQt4.QtGui import *
    from PyQt4.QtCore import *
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    import numpy as np
    
    
    from timeseriesviewer import DIR_REPO, DIR_EXAMPLES, dprint, jp
    
    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)
    
    class SensorInstrument(QObject):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        INSTRUMENTS = dict()
        INSTRUMENTS = {(6, 30., 30.): 'Landsat Legacy' \
                    , (7, 30., 30.): 'L8 OLI' \
                    , (4, 10., 10.): 'S2 MSI 10m' \
                    , (6, 20., 20.): 'S2 MSI 20m' \
                    , (3, 30., 30.): 'S2 MSI 60m' \
                    , (3, 30., 30.): 'S2 MSI 60m' \
                    , (5, 5., 5.): 'RE 5m' \
                        }
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        LUT_Wavelenghts = dict({'B':480,
                                '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, refLyr, sensor_name=None):
            super(SensorInstrument, self).__init__()
            assert isinstance(refLyr, QgsRasterLayer)
    
            assert refLyr.isValid()
    
            #QgsMapLayerRegistry.instance().addMapLayer(refLyr)
            self.nb = refLyr.bandCount()
            self.bandDataType = refLyr.dataProvider().dataType(1)
            self.refUri = refLyr.dataProvider().dataSourceUri()
            r = refLyr.renderer()
            self.defaultRGB = [r.redBand(), r.greenBand(), r.blueBand()]
            s = ""
            """
            dom = QDomDocument()
            root = dom.createElement('root')
            refLyr.renderer().writeXML(dom, root)
            dom.appendChild(root)
            self.renderXML = dom.toString()
            """
    
            #todo: better band names
            self.bandNames = [refLyr.bandName(i) for i in range(1, self.nb + 1)]
            #self.refLyr = refLyr
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            self.TS = None
    
            px_size_x = refLyr.rasterUnitsPerPixelX()
            px_size_y = refLyr.rasterUnitsPerPixelY()
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.px_size_x = float(abs(px_size_x))
            self.px_size_y = float(abs(px_size_y))
    
            assert self.px_size_x > 0
            assert self.px_size_y > 0
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            #find wavelength
            wl, wlu = parseWavelength(refLyr)
            self.wavelengths = np.asarray(wl)
            self.wavelengthUnits = wlu
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            if sensor_name is None:
                id = (self.nb, self.px_size_x, self.px_size_y)
                sensor_name = SensorInstrument.INSTRUMENTS.get(
                    id,
                    '{}band@{}m'.format(self.nb, self.px_size_x))
    
            self.sensorName = sensor_name
    
            self.hashvalue = hash(','.join(self.bandNames))
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        def bandClosestToWavelength(self, wl, wl_unit='nm'):
            """
            Returns the band number (>=1) of the band closest to wavelength wl
            :param wl:
            :param wl_unit:
            :return:
            """
            if not self.wavelengthsDefined():
                return None
    
            if wl in SensorInstrument.LUT_Wavelenghts.keys():
                wl_unit = 'nm'
                wl = SensorInstrument.LUT_Wavelenghts[wl]
    
            wl = float(wl)
            if self.wavelengthUnits != wl_unit:
                wl = convertMetricUnit(wl, wl_unit, self.wavelengthUnits)
    
    
            return np.argmin(np.abs(self.wavelengths - wl))+1
    
    
    
    
        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):
            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):
            return self.hashvalue
    
        def __repr__(self):
            return self.sensorName
    
        def getDescription(self):
            info = []
            info.append(self.sensorName)
            info.append('{} Bands'.format(self.nb))
            info.append('Band\tName\tWavelength')
            for b in range(self.nb):
                if self.wavelengths:
                    wl = str(self.wavelengths[b])
                else:
                    wl = 'unknown'
                info.append('{}\t{}\t{}'.format(b + 1, self.bandNames[b], wl))
    
            return '\n'.join(info)
    
    
    class TSDLoaderSignals(QObject):
    
        sigRasterLayerLoaded = pyqtSignal(QgsRasterLayer)
        sigFinished = pyqtSignal()
    
    class TSDLoader(QRunnable):
        """
        Runnable to load QgsRasterLayers from a parallel thread
        """
        def __init__(self, tsd_paths):
            super(TSDLoader, self).__init__()
            self.signals = TSDLoaderSignals()
    
            self.paths = list()
            for p in tsd_paths:
                if not (isinstance(p, tuple) or isinstance(p, list)):
                    p = [p]
                self.paths.append(p)
    
    
        def run(self):
            lyrs = []
            for path in self.paths:
                TSD = TimeSeriesDatum(path)
                lyr = QgsRasterLayer(path)
                if lyr:
                    lyrs.append(lyr)
                    self.signals.sigRasterLayerLoaded.emit(lyr)
                    dprint('{} loaded'.format(path))
                else:
                    dprint('Failed to load {}'.format(path))
            self.signals.sigFinished.emit()
            #return lyrs
    
    
    
    class TimeSeriesDatum(QObject):
        """
        Collects all data sets related to one sensor
        """
    
        def __init__(self, pathImg, pathMsk=None):
            super(TimeSeriesDatum,self).__init__()
    
            assert os.path.exists(pathImg)
    
            self.pathImg = pathImg
            self.pathMsk = None
    
            self.lyrImg = QgsRasterLayer(pathImg, os.path.basename(pathImg), False)
    
            assert self.lyrImg.isValid()
    
            self.uriImg = self.lyrImg.dataProvider().dataSourceUri()
    
            self.crs = self.lyrImg.dataProvider().crs()
            self.sensor = SensorInstrument(self.lyrImg)
    
            self.date = getImageDate2(self.lyrImg)
            assert self.date is not None, 'Unable to find acquisition date of {}'.format(pathImg)
    
            self.ns = self.lyrImg.width()
            self.nl = self.lyrImg.height()
            self.nb = self.lyrImg.bandCount()
            self.srs_wkt = str(self.crs.toWkt())
    
    
            if pathMsk:
                self.setMask(pathMsk)
    
        def getdtype(self):
            return gdal_array.GDALTypeCodeToNumericTypeCode(self.etype)
    
        def getDate(self):
            return np.datetime64(self.date)
    
    
        def getSpatialReference(self):
            return self.crs
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def spatialExtent(self):
            from timeseriesviewer.main import SpatialExtent
            extent = SpatialExtent(self.lyrImg.crs(), self.lyrImg.extent())
            return extent
    
    
        def setMask(self, pathMsk, raise_errors=True, mask_value=0, exclude_mask_value=True):
            dsMsk = gdal.Open(pathMsk)
            mskDate = getImageDate(dsMsk)
    
    
            errors = list()
            if mskDate and mskDate != self.getDate():
                errors.append('Mask date differs from image date')
            if self.ns != dsMsk.RasterXSize or self.nl != dsMsk.RasterYSize:
                errors.append('Spatial size differs')
            if dsMsk.RasterCount != 1:
                errors.append('Mask has > 1 bands')
    
            projImg = self.getSpatialReference()
            projMsk = osr.SpatialReference()
            projMsk.ImportFromWkt(dsMsk.GetProjection())
    
            if not projImg.IsSame(projMsk):
                errors.append('Spatial Reference differs from image')
            if self.gt != list(dsMsk.GetGeoTransform()):
                errors.append('Geotransformation differs from image')
    
            if len(errors) > 0:
                errors.insert(0, 'pathImg:{} \npathMsk:{}'.format(self.pathImg, pathMsk))
                errors = '\n'.join(errors)
                if raise_errors:
                    raise Exception(errors)
                else:
                    six.print_(errors, file=sys.stderr)
                    return False
            else:
                self.pathMsk = pathMsk
                self.mask_value = mask_value
                self.exclude_mask_value = exclude_mask_value
    
                return True
    
        def readSpatialChip(self, geometry, srs=None, bands=[4,5,3]):
    
            srs_img = osr.SpatialReference()
            srs_img.ImportFromWkt(self.srs_wkt)
    
    
            if type(geometry) is ogr.Geometry:
                g_bb = geometry
                srs_bb = g_bb.GetSpatialReference()
            else:
                assert srs is not None and type(srs) in [str, osr.SpatialReference]
                if type(srs) is str:
                    srs_bb = osr.SpatialReference()
                    srs_bb.ImportFromWkt(srs)
                else:
                    srs_bb = srs.Clone()
                g_bb = ogr.CreateGeometryFromWkt(geometry, srs_bb)
    
            assert srs_bb is not None and g_bb is not None
            assert g_bb.GetGeometryName() == 'POLYGON'
    
            if not srs_img.IsSame(srs_bb):
                g_bb = g_bb.Clone()
                g_bb.TransformTo(srs_img)
    
            cx0,cx1,cy0,cy1 = g_bb.GetEnvelope()
    
            ul_px = coordinate2px(self.gt, min([cx0, cx1]), max([cy0, cy1]))
            lr_px = coordinate2px(self.gt, max([cx0, cx1]), min([cy0, cy1]))
            lr_px = [c+1 for c in lr_px] #+1
    
            return self.readImageChip([ul_px[0], lr_px[0]], [ul_px[1], lr_px[1]], bands=bands)
    
        def readImageChip(self, px_x, px_y, bands=[4,5,3]):
    
            ds = gdal.Open(self.pathImg, gdal.GA_ReadOnly)
    
            assert len(px_x) == 2 and px_x[0] <= px_x[1]
            assert len(px_y) == 2 and px_y[0] <= px_y[1]
    
            ns = px_x[1]-px_x[0]+1
            nl = px_y[1]-px_y[0]+1
            assert ns >= 0
            assert nl >= 0
    
            src_ns = ds.RasterXSize
            src_nl = ds.RasterYSize
    
    
            chipdata = dict()
    
    
    
            #pixel indices in source image
            x0 = max([0, px_x[0]])
            y0 = max([0, px_y[0]])
            x1 = min([src_ns, px_x[1]])
            y1 = min([src_nl, px_y[1]])
            win_xsize = x1-x0+1
            win_ysize = y1-y0+1
    
            #pixel indices in image chip (ideally 0 and ns-1 or nl-1)
            i0 = x0 - px_x[0]
            i1 = i0 + win_xsize
    
            j0 = y0 - px_y[0]
            j1 = j0+ win_ysize
    
    
    
    
            templateImg = np.zeros((nl,ns))
            if self.nodata:
                templateImg *= self.nodata
    
            templateImg = templateImg.astype(self.getdtype())
            templateMsk = np.ones((nl,ns), dtype='bool')
    
            if win_xsize < 1 or win_ysize < 1:
                six.print_('Selected image chip is out of raster image {}'.format(self.pathImg), file=sys.stderr)
                for i, b in enumerate(bands):
                    chipdata[b] = np.copy(templateImg)
    
            else:
                for i, b in enumerate(bands):
                    band = ds.GetRasterBand(b)
                    data = np.copy(templateImg)
                    data[j0:j1,i0:i1] = band.ReadAsArray(xoff=x0, yoff=y0, win_xsize=win_xsize,win_ysize=win_ysize)
                    chipdata[b] = data
                    nodatavalue = band.GetNoDataValue()
                    if nodatavalue is not None:
                        templateMsk[j0:j1,i0:i1] = np.logical_and(templateMsk[j0:j1,i0:i1], data[j0:j1,i0:i1] != nodatavalue)
    
                if self.pathMsk:
                    ds = gdal.Open(self.pathMsk)
                    tmp = ds.GetRasterBand(1).ReadAsArray(xoff=x0, yoff=y0, \
                                win_xsize=win_xsize,win_ysize=win_ysize) == 0
    
                    templateMsk[j0:j1,i0:i1] = np.logical_and(templateMsk[j0:j1,i0:i1], tmp)
    
            chipdata['mask'] = templateMsk
            return chipdata
    
        def __repr__(self):
            return 'TS Datum {} {}'.format(self.date, str(self.sensor))
    
        def __cmp__(self, other):
            return cmp(str((self.date, self.sensor)), str((other.date, other.sensor)))
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        #def __eq__(self, other):
        #    return self.date == other.date and self.sensor == other.sensor
    
        def __lt__(self, other):
            if self.date < other.date:
                return True
            else:
                return self.sensor.sensorName < other.sensor.sensorName
    
    
        def __hash__(self):
            return hash((self.date,self.sensor.sensorName))
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    class TimeSeries(QObject):
    
        sigTimeSeriesDatesAdded = pyqtSignal(list)
        sigTimeSeriesDatesRemoved = pyqtSignal(list)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        #fire when a new sensor configuration is added
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        sigSensorAdded = pyqtSignal(SensorInstrument)
        sigSensorRemoved = pyqtSignal(SensorInstrument)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        sigProgress = pyqtSignal(int, int, int, name='progress')
        sigClosed = pyqtSignal()
        sigError = pyqtSignal(object)
    
    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.CHIP_BUFFER=dict()
    
            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 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:
                return
    
            lines = []
            lines.append('#Time series definition file: {}'.format(np.datetime64('now').astype(str)))
            lines.append('#<image path>[;<mask path>]')
            for TSD in self.data.values():
    
                line = TSD.pathImg
                if TSD.pathMsk is not None:
                    line += TimeSeries._sep + TSD.pathMsk
    
                lines.append(line)
    
            lines = [l+'\n' for l in lines]
    
            print('Write {}'.format(path))
            with open(path, 'w') as f:
                f.writelines(lines)
    
    
    
    
        def getMaxSpatialExtent(self, crs=None):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                return None
    
            extent = self.data[0].spatialExtent()
            if len(self.data) > 1:
                for TSD in self.data[1:]:
    
                    extent.combineExtentWith(TSD.spatialExtent())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            return extent
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        def getObservationDates(self):
            return [tsd.getDate() for tsd in self.data]
    
        def getTSDs(self, date_of_interest=None):
            if date_of_interest:
                tsds = [tsd for tsd in self.data if tsd.getDate() == date_of_interest]
            else:
                tsds = self.data
            return tsds
    
        def _callback_error(self, error):
            six.print_(error, file=sys.stderr)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.sigError.emit(error)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self._callback_progress()
    
        def _callback_spatialchips(self, results):
            self.chipLoaded.emit(results)
            self._callback_progress()
    
        def _callback_progress(self):
            self._callback_progress_done += 1
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.sigProgress.emit(0, self._callback_progress_done, self._callback_progress_max)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            if self._callback_progress_done >= self._callback_progress_max:
                self._callback_progress_done = 0
                self._callback_progress_max = 0
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                self.sigProgress.emit(0, 0, 1)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        def getSpatialChips_parallel(self, bbWkt, srsWkt, TSD_band_list, ncpu=1):
            assert type(bbWkt) is str and type(srsWkt) is str
    
            import multiprocessing
            if self.Pool is not None:
                self.Pool.terminate()
    
            self.Pool = multiprocessing.Pool(processes=ncpu)
    
    
            self._callback_progress_max = len(TSD_band_list)
            self._callback_progress_done = 0
    
            for T in TSD_band_list:
    
                TSD = copy.deepcopy(T[0])
                bands = T[1]
                #TSD = pickle.dumps(self.data[date])
                args = (TSD, bbWkt, srsWkt)
                kwds = {'bands':bands}
    
                if six.PY3:
                    self.Pool.apply_async(PFunc_TimeSeries_getSpatialChip, \
                                     args=args, kwds=kwds, \
                                     callback=self._callback_spatialchips, error_callback=self._callback_error)
                else:
                    self.Pool.apply_async(PFunc_TimeSeries_getSpatialChip, \
                                          args, kwds, self._callback_spatialchips)
    
            s = ""
    
            pass
    
        def getImageChips(self, xy, size=50, bands=[4,5,6], dates=None):
            chipCollection = collections.OrderedDict()
    
            if dates is None:
                dates = self.data.keys()
            for date in dates:
                TSD = self.data[date]
                chipCollection[date] = TSD.readImageChip(xy, size=size, bands=bands)
    
            return chipCollection
    
        def addMasks(self, files, raise_errors=True, mask_value=0, exclude_mask_value=True):
            assert isinstance(files, list)
            l = len(files)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.sigProgress.emit(0, 0, l)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for i, file in enumerate(files):
    
                try:
                    self.addMask(file, raise_errors=raise_errors, mask_value=mask_value, exclude_mask_value=exclude_mask_value, _quiet=True)
                except:
                    pass
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                self.sigProgress.emit(0, i + 1, l)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.sigProgress.emit(0, 0, 1)
            self.sigChanged.emit()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        def addMask(self, pathMsk, raise_errors=True, mask_value=0, exclude_mask_value=True, _quiet=False):
            print('Add mask {}...'.format(pathMsk))
            ds = getDS(pathMsk)
            date = getImageDate(ds)
    
            if date in self.data.keys():
                TSD = self.data[date]
    
                if not _quiet:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    self.sigChanged.emit()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
                return TSD.setMask(pathMsk, raise_errors=raise_errors, mask_value=mask_value, exclude_mask_value=exclude_mask_value)
            else:
                info = 'TimeSeries does not contain date {} {}'.format(date, pathMsk)
                if raise_errors:
                    raise Exception(info)
                else:
                    six.print_(info, file=sys.stderr)
                return False
    
        def clear(self):
            self.Sensors.clear()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            del self.data[:]
    
            self.sigTimeSeriesDatesRemoved.emit(dates)
    
    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
                S = TSD.sensor
                self.Sensors[S].remove(TSD)
                if len(self.Sensors[S]) == 0:
                    self.Sensors.pop(S)
                    self.sigSensorRemoved(S)
                removed.append(self.data.pop(TSD, None))
            self.sigTimeSeriesDatesRemoved.emit(removed)
    
    
        def addTimeSeriesDates(self, timeSeriesDates):
            assert isinstance(timeSeriesDates, list)
            added = list()
            for TSD in timeSeriesDates:
                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:
                        six.print_('Time series datum already added: {}'.format(str(TSD)), file=sys.stderr)
                    else:
                        self.Sensors[TSD.sensor].append(TSD)
                        #insert sorted
                        bisect.insort(self.data, 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)
                    six.print_('Unable to add {}'.format(file), file=sys.stderr)
                    pass
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            if len(added) > 0:
                self.sigTimeSeriesDatesAdded.emit(added)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def addFilesAsync(self, files):
            assert isinstance(files, list)
    
    
        def findAbsolutePath(self,file):
            if os.path.exists(file): return file
            possibleRoots = [DIR_EXAMPLES, DIR_REPO, os.getcwd()]
            for root in possibleRoots:
                tmp = jp(root, file)
                if os.path.exists(tmp):
                    return tmp
            return None
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        def addFiles(self, files):
            assert isinstance(files, list)
            l = len(files)
            assert l > 0
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            for i, file in enumerate(files):
    
                tmp = self.findAbsolutePath(file)
                if tmp:
    
                    toadd.append(TimeSeriesDatum(tmp))
                    six.print_('Add image {}...'.format(tmp))
    
                else:
                    dprint('Unable to locate: {}'.format(file), file=sys.stderr)
    
            self.addTimeSeriesDates(toadd)
    
    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, key):
            return self.data[key]
    
    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)
    
    regAcqDate = re.compile(r'acquisition[ ]*(time|date|day)', re.I)
    regLandsatSceneID = re.compile(r"L[EMCT][1234578]{1}[12]\d{12}[a-zA-Z]{3}\d{2}")
    
    def getImageDate2(lyr):
        assert isinstance(lyr, QgsRasterLayer)
        mdLines = str(lyr.metadata()).splitlines()
        date = None
        #find date in metadata
        for line in [l for l in mdLines if regAcqDate.search(l)]:
            date = parseAcquisitionDate(line)
            if date:
                return date
        #find date in filename
        dn, fn = os.path.split(str(lyr.dataProvider().dataSourceUri()))
        date = parseAcquisitionDate(fn)
        if date: return date
    
        #find date in file directory path
        date = parseAcquisitionDate(date)
    
        return date
    
    
    def PFunc_TimeSeries_getSpatialChip(TSD, bbWkt, srsWkt , bands=[4,5,3]):
    
        chipdata = TSD.readSpatialChip(bbWkt, srs=srsWkt, bands=bands)
    
        return TSD, chipdata
    
    def px2Coordinate(gt, pxX, pxY, upper_left=True):
        cx = gt[0] + pxX*gt[1] + pxY*gt[2]
        cy = gt[3] + pxX*gt[4] + pxY*gt[5]
        if not upper_left:
            cx += gt[1]*0.5
            cy += gt[5]*0.5
        return cx, cy
    
    def coordinate2px(gt, cx, cy):
        px = int((cx - gt[0]) / gt[1])
        py = int((cy - gt[3]) / gt[5])
        return px, py
    
    
    regYYYYDOY = re.compile(r'(19|20)\d{5}')
    regYYYYMMDD = re.compile(r'(19|20)\d{2}-\d{2}-\d{2}')
    regYYYY = re.compile(r'(19|20)\d{2}')
    
    
    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('\d*\.\d+|\d+', value) #find floats
                if len(tmp) == 0:
                    tmp = re.findall('\d+', value) #find integers
                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
    
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    def parseAcquisitionDate(text):
        match = regLandsatSceneID.search(text)
        if match:
            id = match.group()
            return getDateTime64FromYYYYDOY(id[9:16])
        match = regYYYYMMDD.search(text)
        if match:
            return np.datetime64(match.group())
        match = regYYYYDOY.search(text)
        if match:
            return getDateTime64FromYYYYDOY(match.group())
        match = regYYYY.search(text)
        if match:
            return np.datetime64(match.group())
        return None
    
    
    
    def getDateTime64FromYYYYDOY(yyyydoy):
        return getDateTime64FromDOY(yyyydoy[0:4], yyyydoy[4:7])
    
    def getDateTime64FromDOY(year, doy):
            if type(year) is str:
                year = int(year)
            if type(doy) is str:
                doy = int(doy)
            return np.datetime64('{:04d}-01-01'.format(year)) + np.timedelta64(doy-1, 'D')
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    if __name__ == '__main__':
    
        print convertMetricUnit(100, 'cm', 'm')
        print convertMetricUnit(1, 'm', 'um')