Newer
Older
from __future__ import absolute_import
import six, sys, os, gc, re, collections, site, inspect, time, traceback, copy
import logging
logger = logging.getLogger(__name__)

benjamin.jakimow@geo.hu-berlin.de
committed
import bisect, datetime
from osgeo import gdal, ogr
from qgis import *
from qgis.core import *
from qgis.gui import *
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4.QtXml import *
from osgeo import gdal, ogr, gdal_array

benjamin.jakimow@geo.hu-berlin.de
committed
gdal.SetConfigOption('VRT_SHARED_SOURCE', '0') #!important. really. do not change this.
from timeseriesviewer import DIR_REPO, DIR_EXAMPLES, jp
from timeseriesviewer.dateparser import parseDateFromDataSet
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)
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']
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)
def getDS(pathOrDataset):
if isinstance(pathOrDataset, gdal.Dataset):
return pathOrDataset
else:
ds = gdal.Open(pathOrDataset)
assert isinstance(ds, gdal.Dataset)
return ds
class SensorInstrument(QObject):
sigNameChanged = pyqtSignal(str)
LUT_Wavelengths = dict({'B':480,
'G':570,
'R':660,
'nIR':850,
'swIR':1650,
'swIR1':1650,
'swIR2':2150
})
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]
#todo: better band names
self.bandNames = [ds.GetRasterBand(b+1).GetDescription() for b in range(self.nb)]
self.TS = None
assert self.px_size_x > 0
assert self.px_size_y > 0
wl, wlu = parseWavelengthFromDataSet(ds)
if wl is None:
self.wavelengths = None
self.wavelengthUnits = None
else:
self.wavelengths = np.asarray(wl)
self.wavelengthUnits = wlu
self._id = '{}b{}m'.format(self.nb, self.px_size_x)
if wlu is None:
wlu = ''
if wl is not None:
self._id += ';'.join([str(w) for w in self.wavelengths])+wlu
sensor_name = '{}bands@{}m'.format(self.nb, self.px_size_x)
self.setName(sensor_name)
def id(self):
return self._id
def setName(self, name):
self._name = name
self.sigNameChanged.emit(self.name())
def name(self):
return self._name
def dataType(self, p_int):
return self.bandDataType
def bandClosestToWavelength(self, wl, wl_unit='nm'):
"""

benjamin.jakimow@geo.hu-berlin.de
committed
Returns the band index (>=0) of the band closest to wavelength wl
:param wl:
:param wl_unit:
:return:
"""
if not self.wavelengthsDefined():
return None
if wl in SensorInstrument.LUT_Wavelengths.keys():
wl = SensorInstrument.LUT_Wavelengths[wl]
wl = float(wl)
if self.wavelengthUnits != wl_unit:
wl = convertMetricUnit(wl, wl_unit, self.wavelengthUnits)

benjamin.jakimow@geo.hu-berlin.de
committed
return np.argmin(np.abs(self.wavelengths - wl))
def wavelengthsDefined(self):
return self.wavelengths is not None and \
self.wavelengthUnits is not None

Benjamin Jakimow
committed
if not isinstance(other, SensorInstrument):
return False
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 hash(self.id())
return str(self.__class__) +' ' + self.name()
info.append(self.name())
info.append('{} Bands'.format(self.nb))
info.append('Band\tName\tWavelength')
for b in range(self.nb):
if self.wavelengths is not None:
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 type(path) in [str, unicode]:
return None

benjamin.jakimow@geo.hu-berlin.de
committed
logger.error('{}GDAL unable to open: '.format(vrtInspection, path))

benjamin.jakimow@geo.hu-berlin.de
committed
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)

benjamin.jakimow@geo.hu-berlin.de
committed
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

benjamin.jakimow@geo.hu-berlin.de
committed
return True
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:
"""
if verifyInputImage(path):
tsd = TimeSeriesDatum(None, path)
except :
pass
return tsd
"""
Collects all data sets related to one sensor
"""
sigVisibilityChanged = pyqtSignal(bool)
sigRemoveMe = pyqtSignal()
def __init__(self, timeSeries, pathImg):
super(TimeSeriesDatum,self).__init__()
ds = getDS(pathImg)
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.sensor = SensorInstrument(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)
gt = ds.GetGeoTransform()
UL = QgsPoint(*pixel2coord(gt, 0, 0))
LR = QgsPoint(*pixel2coord(gt, self.ns, self.nl))
from timeseriesviewer.main import SpatialExtent
self._spatialExtent = SpatialExtent(self.crs, UL, LR)
self.srs_wkt = str(self.crs.toWkt())
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
return self._spatialExtent
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()
def __lt__(self, other):
if self.date < other.date:
return True

benjamin.jakimow@geo.hu-berlin.de
committed
elif self.date > other.date:
return False
return self.sensor.id() < other.sensor.id()
def __hash__(self):
return hash((self.date,self.sensor.id()))
sigTimeSeriesDatesAdded = pyqtSignal(list)
sigTimeSeriesDatesRemoved = pyqtSignal(list)
sigLoadingProgress = pyqtSignal(int, int, str)
sigSensorAdded = pyqtSignal(SensorInstrument)
sigSensorRemoved = pyqtSignal(SensorInstrument)
sigRuntimeStats = pyqtSignal(dict)
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
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 = ';'
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])
if n_max:
n_max = min([len(images), n_max])
self.addFiles(images[0:n_max])
else:
self.addFiles(images)
#self.addMasks(masks)
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>]')
line = TSD.pathImg
lines.append(line)
lines = [l+'\n' for l in lines]
print('Write {}'.format(path))
with open(path, 'w') as f:
f.writelines(lines)

benjamin.jakimow@geo.hu-berlin.de
committed
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):
if len(self.data) == 0:
return None
extent = self.data[0].spatialExtent()
if len(self.data) > 1:
for TSD in self.data[1:]:
extent.combineExtentWith(TSD.spatialExtent())

benjamin.jakimow@geo.hu-berlin.de
committed
def tsdFromPath(self, path):
for tsd in self.data:
if tsd.pathImg == path:
return tsd
return False
def getObservationDates(self):
return [tsd.getDate() for tsd in self.data]

benjamin.jakimow@geo.hu-berlin.de
committed
def getTSD(self, pathOfInterest):
for tsd in self.data:
if tsd.pathImg == pathOfInterest:
return tsd
return None
def getTSDs(self, dateOfInterest=None, sensorOfInterest=None):

benjamin.jakimow@geo.hu-berlin.de
committed
tsds = self.data[:]
if dateOfInterest:

benjamin.jakimow@geo.hu-berlin.de
committed
tsds = [tsd for tsd in tsds if tsd.getDate() == dateOfInterest]
if sensorOfInterest:
tsds = [tsd for tsd in tsds if tsd.sensor == sensorOfInterest]
self.removeDates(self[:])
assert type(TSD) is TimeSeriesDatum
self.data.remove(TSD)
TSD.timeSeries = None
removed.append(TSD)
self.Sensors[S].remove(TSD)
if len(self.Sensors[S]) == 0:
self.Sensors.pop(S)
self.sigSensorRemoved.emit(S)
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 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)
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
if len(added) > 0:
self.sigTimeSeriesDatesAdded.emit(added)
def addFiles(self, files):
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))
t0 = np.datetime64('now')
tsd = TimeSeriesDatum.createFromPath(file)
if tsd is None:
msg = 'Unable to add: {}'.format(os.path.basename(file))
logger.error(msg)
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)
def __len__(self):
return len(self.data)
def __iter__(self):
return iter(self.data)
def __getitem__(self, slice):
return self.data[slice]
def __delitem__(self, slice):
self.removeDates(slice)
def __contains__(self, item):
return item in self.data
def __repr__(self):
info = []
info.append('TimeSeries:')
l = len(self)
info.append(' Scenes: {}'.format(l))
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:
names = ['nanometers', 'micrometers', 'millimeters', 'centimeters', 'decimeters']
si = ['nm', 'um', 'mm', 'cm', 'dm']
if wlu in names:
wlu = si[names.index(wlu)]
return wl, wlu
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
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
if __name__ == '__main__':

benjamin.jakimow@geo.hu-berlin.de
committed
print convertMetricUnit(100, 'cm', 'm')
print convertMetricUnit(1, 'm', 'um')