Newer
Older
"""
/***************************************************************************

Benjamin Jakimow
committed
EO Time Series Viewer
-------------------
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@geo.hu-berlin.de
committed
import os, sys
import pickle
import datetime
from qgis.gui import *
from qgis.core import *
from eotimeseriesviewer.utils import SpatialPoint, SpatialExtent, geo2px, px2geo
from osgeo import gdal, gdal_array, osr

benjamin.jakimow@geo.hu-berlin.de
committed
if DEBUG:
logger = multiprocessing.log_to_stderr()
logger.setLevel(multiprocessing.SUBDEBUG)
def dprint(msg):
if DEBUG:
print('PixelLoader: {}'.format(msg))
def isOutOfImage(ds, px):
"""
Evaluates if a pixel is inside and image or onot
:param ds: gdal.Dataset
:param px: QPoint
:return: True | False
"""
if px.x() < 0 or px.x() >= ds.RasterXSize:
return True
if px.y() < 0 or px.y() >= ds.RasterYSize:
return True
return False
class PixelLoaderTask(object):
An object to store the loading results from a *single* raster source.

benjamin.jakimow@geo.hu-berlin.de
committed
def __init__(self, source:str, temporalProfiles:list, bandIndices=None):

benjamin.jakimow@geo.hu-berlin.de
committed
geometries = [g for g in geometries if isinstance(g, (SpatialExtent, SpatialPoint))]
# assert isinstance(source, str) or isinstance(source, unicode)
self.mSourcePath = source
self.mGeometries = []
self.mTemporalProfileIDs = []
from .temporalprofiles import TemporalProfile
for tp in temporalProfiles:
assert isinstance(tp, TemporalProfile)
self.mTemporalProfileIDs.append(tp.id())
self.mGeometries.append(tp.geometry())
self.mBandIndices = bandIndices

benjamin.jakimow@geo.hu-berlin.de
committed
self.mIsDone = False
self.resCrsWkt = None
self.resGeoTransformation = None
self.resProfiles = None
self.resNoDataValues = None
self.exception = None
self.info = None
def setId(self, idStr:str):
self.mId = idStr
def id(self)->str:
return self.mId

benjamin.jakimow@geo.hu-berlin.de
committed
def toDump(self):
return pickle.dumps(self)
def validPixelValues(self, i):
if not self.success():
return False
if i >= len(self.resProfiles):
return False
profileData = self.resProfiles[i]
if profileData in [INFO_OUT_OF_IMAGE, INFO_NO_DATA]:
return False
else:
return True
def imageCrs(self):
return QgsCoordinateReferenceSystem(self.resCrsWkt)
def pixelResolution(self):
"""
Returns the spatial pixel resolution
:return: QSize
"""
if self.resGeoTransformation is None:
return None
p1 = px2geo(QPoint(0, 0), self.resGeoTransformation)
p2 = px2geo(QPoint(1, 1), self.resGeoTransformation)
xRes = abs(p2.x() - p1.x())
yRes = abs(p2.y() - p1.y())
return QSize(xRes, yRes)
def success(self):

Benjamin Jakimow
committed
"""
Returns True if the PixelLoaderTask has been finished well without exceptions.
:return: True | False
"""
"""
:return:
"""

benjamin.jakimow@geo.hu-berlin.de
committed
result = self.mIsDone and self.exception is None

Benjamin Jakimow
committed
return result
def __repr__(self):
info = ['PixelLoaderTask:']
info.append('\t' + self.mSourcePath)
info.append('\t' + ','.join(str(g) for g in self.mGeometries))

benjamin.jakimow@geo.hu-berlin.de
committed
if not self.mIsDone:
info.append('not started...')
else:
if self.mBandIndices:
info.append('\tBandIndices {}:{}'.format(len(self.mBandIndices), self.mBandIndices))
if self.resProfiles:
info.append('\tProfileData: {}'.format(len(self.resProfiles)))
for i, p in enumerate(self.resProfiles):
d = self.resProfiles[i]

benjamin.jakimow@geo.hu-berlin.de
committed
if d in [INFO_OUT_OF_IMAGE, INFO_NO_DATA]:

Benjamin Jakimow
committed
info.append('\t{}: {}:{}'.format(i + 1, g, d))
else:
vData = d[0]
vStd = d[1]

benjamin.jakimow@geo.hu-berlin.de
committed
try:
info.append('\t{}: {}:{} : {}'.format(i+1, g, vData.shape, vData))
except:
s =""
return '\n'.join(info)
INFO_OUT_OF_IMAGE = 'out of image'

benjamin.jakimow@geo.hu-berlin.de
committed
INFO_NO_DATA = 'no data values'
#def loadProfiles(pathsAndBandIndices, jobid, poolWorker, geom, q, cancelEvent, **kwargs):
def transformPoint2Px(trans, pt, gt):
x, y, _ = trans.TransformPoint(pt.x(), pt.y())
return geo2px(QgsPointXY(x, y), gt)

Benjamin Jakimow
committed
def doLoaderTask(qgsTask:QgsTask, dump):

Benjamin Jakimow
committed
assert isinstance(qgsTask, QgsTask)
tasks = pickle.loads(dump)
results = []

Benjamin Jakimow
committed
nTasks = len(tasks)
qgsTask.setProgress(0)
for iTask, task in enumerate(tasks):
assert isinstance(task, PixelLoaderTask)
nb, ns, nl = ds.RasterCount, ds.RasterXSize, ds.RasterYSize
bandIndices = list(range(nb)) if task.mBandIndices is None else list(task.mBandIndices)
# ensure to load valid indices only
bandIndices = [i for i in bandIndices if i >= 0 and i < nb]

Benjamin Jakimow
committed
gt = ds.GetGeoTransform()
result.resGeoTransformation = gt
result.resCrsWkt = ds.GetProjection()
if result.resCrsWkt == '':
result.resCrsWkt = QgsRasterLayer(ds.GetDescription()).crs().toWkt()
crsSrc = osr.SpatialReference(result.resCrsWkt)

Benjamin Jakimow
committed
# convert Geometries into pixel indices to be extracted
PX_SUBSETS = []

Benjamin Jakimow
committed
crsRequest = osr.SpatialReference()

Benjamin Jakimow
committed
if geom.crs().isValid():
crsRequest.ImportFromWkt(geom.crs().toWkt())
else:
crsRequest.ImportFromWkt(crsSrc.ExportToWkt())
trans = osr.CoordinateTransformation(crsRequest, crsSrc)
trans2 = QgsCoordinateTransform()
trans2.setSourceCrs(QgsCoordinateReferenceSystem(crsRequest.ExportToWkt()))
trans2.setDestinationCrs(QgsCoordinateReferenceSystem(crsSrc.ExportToWkt()))
lyr = QgsRasterLayer(task.mSourcePath)
geom2 = trans2.transform(geom)
if isinstance(geom, QgsPointXY):
ptUL = ptLR = QgsPointXY(geom)
elif isinstance(geom, QgsRectangle):
TYPE = 'RECTANGLE'
ptUL = QgsPointXY(geom.xMinimum(), geom.yMaximum())
ptLR = QgsPointXY(geom.xMaximum(), geom.yMinimum())
else:
raise NotImplementedError('Unsupported geometry {} {}'.format(type(geom), str(geom)))
pxUL = transformPoint2Px(trans, ptUL, gt)
pxLR = transformPoint2Px(trans, ptLR, gt)
bUL = isOutOfImage(ds, pxUL)
bLR = isOutOfImage(ds, pxLR)
PX_SUBSETS.append(INFO_OUT_OF_IMAGE)
def shiftIntoImageBounds(pt, xMax, yMax):
assert isinstance(pt, QPoint)
if pt.x() < 0:
pt.setX(0)
elif pt.x() > xMax:
pt.setX(xMax)
if pt.y() < 0:
pt.setY(0)
elif pt.y() > yMax:
pt.setY(yMax)
shiftIntoImageBounds(pxUL, ds.RasterXSize, ds.RasterYSize)
shiftIntoImageBounds(pxLR, ds.RasterXSize, ds.RasterYSize)
if pxUL == pxLR:
size_x = size_y = 1
size_x = abs(pxUL.x() - pxLR.x())
size_y = abs(pxUL.y() - pxLR.y())
if size_x < 1:
size_x = 1
if size_y < 1:
size_y = 1
PX_SUBSETS.append((pxUL, pxUL, size_x, size_y))
PROFILE_DATA = []
if bandIndices == range(ds.RasterCount):
# we have to extract all bands
# in this case we use gdal.Dataset.ReadAsArray()
noData = ds.GetRasterBand(1).GetNoDataValue()
for px in PX_SUBSETS:
if px == INFO_OUT_OF_IMAGE:
PROFILE_DATA.append(INFO_OUT_OF_IMAGE)
pxUL, pxUL, size_x, size_y = px
bandData = ds.ReadAsArray(pxUL.x(), pxUL.y(), size_x, size_y).reshape((nb, size_x*size_y))

Benjamin Jakimow
committed
if noData:
isValid = np.ones(bandData.shape[1], dtype=np.bool)
for b in range(bandData.shape[0]):
isValid *= bandData[b, :] != ds.GetRasterBand(b+1).GetNoDataValue()
bandData = bandData[:, np.where(isValid)[0]]
PROFILE_DATA.append(bandData)
else:
# access band values band-by-band
# in this case we use gdal.Band.ReadAsArray()
# and need to iterate over the requested band indices
# save the returned band values for each geometry in a separate list
# empty list == invalid geometry
for i in range(len(PX_SUBSETS)):
if PX_SUBSETS[i] == INFO_OUT_OF_IMAGE:
PROFILE_DATA.append(INFO_OUT_OF_IMAGE)

benjamin.jakimow@geo.hu-berlin.de
committed
else:
PROFILE_DATA.append([])
for bandIndex in bandIndices:
band = ds.GetRasterBand(bandIndex+1)
noData = band.GetNoDataValue()
assert isinstance(band, gdal.Band)
for i, px in enumerate(PX_SUBSETS):
if px == INFO_OUT_OF_IMAGE:
continue
pxUL, pxUL, size_x, size_y = px
bandData = band.ReadAsArray(pxUL.x(), pxUL.y(), size_x, size_y).flatten()
if noData:
bandData = bandData[np.where(bandData != noData)[0]]
PROFILE_DATA[i].append(bandData)
for i in range(len(PX_SUBSETS)):
pd = PROFILE_DATA[i]
if isinstance(pd, list):
if len(pd) == 0:
PROFILE_DATA[i] = INFO_OUT_OF_IMAGE
else:
#PROFILE_DATA[i] = np.dstack(pd).transpose(2,0,1)
PROFILE_DATA[i] = np.vstack(pd)
# finally, ensure that there is on 2D array only
for i in range(len(PROFILE_DATA)):
d = PROFILE_DATA[i]
if isinstance(d, np.ndarray):
assert d.ndim == 2
b, yx = d.shape
assert b == len(bandIndices)
_, _, size_x, size_y = PX_SUBSETS[i]

benjamin.jakimow@geo.hu-berlin.de
committed
if yx > 0:
d = d.reshape((b, yx))
vMean = d.mean(axis=1)
vStd = d.std(axis=1)
assert len(vMean) == len(bandIndices)
assert len(vStd) == len(bandIndices)
PROFILE_DATA[i] = (vMean, vStd)
else:
PROFILE_DATA[i] = INFO_NO_DATA

benjamin.jakimow@geo.hu-berlin.de
committed
s = ""
task.resProfiles = PROFILE_DATA
task.mIsDone = True
results.append(task)

Benjamin Jakimow
committed
qgsTask.setProgress(100 * (iTask + 1) / nTasks)
class PixelLoader(QObject):
"""
Loads pixel from raster images
Use QgsTaskManager interface in background

Benjamin Jakimow
committed
sigProgressChanged = pyqtSignal(float)
def __init__(self, *args, **kwds):
super(PixelLoader, self).__init__(*args, **kwds)
def tasks(self)->list:
"""
Returns the list of QgsTaskWrappers
:return: list
"""
def taskManager(self)->QgsTaskManager:
return QgsApplication.taskManager()
def startLoading(self, tasks):
assert isinstance(tasks, list)

Benjamin Jakimow
committed
self.sigLoadingStarted.emit()

Benjamin Jakimow
committed
qgsTask = QgsTask.fromFunction('Load band values', doLoaderTask, dump, on_finished=self.onLoadingFinished)
assert isinstance(qgsTask, QgsTask)

Benjamin Jakimow
committed
tid = id(qgsTask)

Benjamin Jakimow
committed
qgsTask.taskCompleted.connect(lambda *args, tid=tid: self.onRemoveTask(tid))
qgsTask.taskTerminated.connect(lambda *args, tid=tid: self.onRemoveTask(tid))
qgsTask.progressChanged.connect(self.sigProgressChanged.emit)
self.mTasks[tid] = qgsTask
tm = self.taskManager()
tm.addTask(qgsTask)
def onRemoveTask(self, tid):
if tid in self.mTasks.keys():
error = args[0]
if error is None:
dump = args[1]
tasks = pickle.loads(dump)
for plt in tasks:
assert isinstance(plt, PixelLoaderTask)
#self.mTasks.pop(plt.id())

Benjamin Jakimow
committed
self.sigLoadingFinished.emit()
def cancelLoading(self):
raise NotImplementedError