diff --git a/CHANGES.txt b/CHANGES.txt index cfc33cc12086112e1c77b3c49631b9a79f2f1242..cd8b82e757d87aeaabcfdef1da2a9a62874944b1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,9 @@ -2918-06-20 +2018-11-09 + - uses QgsTaskManager for background loading + - own QgsMapLayerStore to not mix-up with (main) QGIS layers + - fixed bugs related to changes in QGIS API + +2018-06-20 - increase version to 0.7 - Visualization of images with stacked temporal information (each band = one observation date) - some bugfixes diff --git a/test/test_pixelloader.py b/test/test_pixelloader.py index 99c9513065b0e1d55a44840f9334863d82fd6ea9..24f2090e183d980afa8320637f6c1037a531c161 100644 --- a/test/test_pixelloader.py +++ b/test/test_pixelloader.py @@ -14,18 +14,30 @@ __author__ = 'benjamin.jakimow@geo.hu-berlin.de' import unittest import os, sys, pickle - +import qgis.testing from timeseriesviewer.utils import initQgisApplication import example.Images -from timeseriesviewer.utils import file_search - -QGIS_APP = initQgisApplication() +from timeseriesviewer.utils import * +QGIS_APP = qgis.testing.start_app(False) +#QGIS_APP = initQgisApplication() +SHOW_GUI = True def onDummy(*args): print(('dummy', args)) +def waitForEnd(pl:PixelLoader): + print('wait for end...') + + while QgsApplication.taskManager().countActiveTasks() > 0: + QCoreApplication.processEvents() + + + QCoreApplication.processEvents() + print('done') + + class PixelLoaderTest(unittest.TestCase): """Test translations work.""" @@ -34,7 +46,7 @@ class PixelLoaderTest(unittest.TestCase): from timeseriesviewer import DIR_EXAMPLES from timeseriesviewer.utils import file_search cls.imgs = file_search(DIR_EXAMPLES, '*.tif', recursive=True) - cls.img1 = cls.imgs[0] + cls.img1 = list(cls.imgs)[0] ds = gdal.Open(cls.img1) assert isinstance(ds, gdal.Dataset) nb, nl, ns = ds.RasterCount, ds.RasterYSize, ds.RasterXSize @@ -55,8 +67,79 @@ class PixelLoaderTest(unittest.TestCase): """Runs after each test.""" pass + def createTestImageSeries(self, n=10)->list: + return TestObjects.createTestImageSeries(n=n) + + def test_QgsTaskManager(self): + + nTasks = 0 + result = None + def countTasks(*args): + nonlocal nTasks + nTasks += 1 + + tm = QgsApplication.taskManager() + self.assertIsInstance(tm, QgsTaskManager) + tm.taskAdded.connect(countTasks) + + def func1(taskWrapper:QgsTaskWrapper): + return 'Hello' + + def onFinished(e, value): + nonlocal result + assert e is None + result = value + + + task = QgsTask.fromFunction('',func1, on_finished = onFinished) + self.assertIsInstance(task, QgsTask) + tm.addTask(task) + + while task.status() not in [QgsTask.Complete, QgsTask.Terminated]: + pass + while QgsApplication.taskManager().countActiveTasks() > 0: + QCoreApplication.processEvents() + self.assertTrue(nTasks == 1) + self.assertTrue(result == 'Hello') + + def test_PixelLoader(self): + + images = self.createTestImageSeries(n=50) + + RESULTS = [] + def onPixelLoaded(taskResult, *args): + nonlocal RESULTS + print('Pixel loaded: {}'.format(taskResult)) + RESULTS.append(taskResult) + + paths = [i.GetFileList()[0] for i in images] + rl = QgsRasterLayer(paths[0]) + self.assertIsInstance(rl, QgsRasterLayer) + self.assertTrue(rl.isValid()) + center = SpatialPoint.fromMapLayerCenter(rl) + cleft = SpatialPoint(center.crs(), center.x()-100, center.y()) + + geometries = [center, cleft] + + pl = PixelLoader() + pl.sigPixelLoaded.connect(onPixelLoaded) + self.assertIsInstance(pl, PixelLoader) + + tasks = [] + for p in paths: + task = PixelLoaderTask(p, geometries) + tasks.append(task) + pl.startLoading(tasks) + + waitForEnd(pl) + QCoreApplication.processEvents() + + import time + time.sleep(5) + + self.assertTrue(len(RESULTS) == len(tasks)) def test_pixelLoader(self): from timeseriesviewer.pixelloader import doLoaderTask, PixelLoaderTask, INFO_OUT_OF_IMAGE, INFO_NO_DATA @@ -79,9 +162,11 @@ class PixelLoaderTest(unittest.TestCase): ptValid2 = SpatialPoint(ext.crs(), x+50, y+50) #test a valid pixels - + plt = PixelLoaderTask(source, [ptValid1, ptValid2]) + task = QgsTask.fromFunction('', doLoaderTask, plt) try: - result = doLoaderTask(PixelLoaderTask(source, [ptValid1, ptValid2])) + result = doLoaderTask(task, plt.toDump()) + result = PixelLoaderTask.fromDump(result) except Exception as ex: self.fail('Failed to return the pixels for two geometries') @@ -111,21 +196,12 @@ class PixelLoaderTest(unittest.TestCase): - #test a out-of-image geometry - result = doLoaderTask(PixelLoaderTask(source, ptOutOfImage)) - self.assertTrue(result.success()) - self.assertEqual(result.resProfiles[0], INFO_OUT_OF_IMAGE) - - result = doLoaderTask(PixelLoaderTask(source, ptNoData)) - self.assertTrue(result.success()) - self.assertEqual(result.resProfiles[0], INFO_NO_DATA) def test_loadProfiles(self): from timeseriesviewer.utils import SpatialPoint, SpatialExtent, px2geo - img1 = self.img1 nb, nl, ns = self.img1Shape gt = self.img1gt @@ -155,25 +231,20 @@ class PixelLoaderTest(unittest.TestCase): geoms2 = [SpatialPoint(ext.crs(), x, y), SpatialPoint(ext.crs(), x + 250, y + 70)] - from multiprocessing import Pool - + loaded_values = [] def onPxLoaded(*args): - n, nmax, task = args - assert isinstance(task, PixelLoaderTask) - print(task) + print('got loaded') + task = args[0] + nonlocal loaded_values + self.assertIsInstance(task, PixelLoaderTask) + loaded_values.append(task) - PL = PixelLoader() - def onTimer(*args): - print(('TIMER', PL)) - pass - + PL = PixelLoader() PL.sigPixelLoaded.connect(onPxLoaded) PL.sigLoadingFinished.connect(lambda: onDummy('finished')) - PL.sigLoadingCanceled.connect(lambda: onDummy('canceled')) PL.sigLoadingStarted.connect(lambda: onDummy('started')) - PL.sigPixelLoaded.connect(lambda: onDummy('px loaded')) tasks1 = [] for i, f in enumerate(files): @@ -185,18 +256,21 @@ class PixelLoaderTest(unittest.TestCase): kwargs = {'myid': 'myID{}'.format(i)} tasks2.append(PixelLoaderTask(f, geoms2, bandIndices=None, **kwargs)) - for t in tasks1: - result = doLoaderTask(t) - s = "" PL.startLoading(tasks1) PL.startLoading(tasks2) + waitForEnd(PL) + + + self.assertTrue(len(loaded_values) == len(tasks2)+len(tasks1)) print('DONE') if __name__ == "__main__": + SHOW_GUI = False + unittest.main() diff --git a/test/test_temporalprofiles.py b/test/test_temporalprofiles.py index 5070ed598ea1ec668f8bee17e3dc8d524397a098..45f057d2ea2ec6ba62b9a1ffb874e8b0ae40c527 100644 --- a/test/test_temporalprofiles.py +++ b/test/test_temporalprofiles.py @@ -23,6 +23,7 @@ from timeseriesviewer.profilevisualization import * from timeseriesviewer.utils import * from osgeo import ogr, osr QGIS_APP = initQgisApplication() +SHOW_GUI = True class testclassUtilityTests(unittest.TestCase): """Test rerources work.""" @@ -49,10 +50,12 @@ class testclassUtilityTests(unittest.TestCase): lyr = TemporalProfileLayer(self.TS) - - tp1 = lyr.createTemporalProfiles(center)[0] - tp2 = lyr.createTemporalProfiles(SpatialPoint(center.crs(), center.x() + 40, center.y() + 50)) - return [tp1, tp2] + results = [] + results.extend(lyr.createTemporalProfiles(center)) + results.extend(lyr.createTemporalProfiles(SpatialPoint(center.crs(), center.x() + 40, center.y() + 50))) + for p in results: + self.assertIsInstance(p, TemporalProfile) + return results def test_createTemporalProfile(self): @@ -213,8 +216,8 @@ class testclassUtilityTests(unittest.TestCase): svis.loadCoordinate(point2) svis.ui.show() - s = "" - QGIS_APP.exec_() + if SHOW_GUI: + QGIS_APP.exec_() if __name__ == "__main__": diff --git a/timeseriesviewer/main.py b/timeseriesviewer/main.py index ca74817e882b5548a035c703af4b54ccb9188ad8..9f8cf02556718324408387af537de3a3aee43840 100644 --- a/timeseriesviewer/main.py +++ b/timeseriesviewer/main.py @@ -210,7 +210,7 @@ class TimeSeriesViewerUI(QMainWindow, self.addActions(self.findChildren(QAction)) from timeseriesviewer import TITLE, icon, __version__ - self.setWindowTitle('{} ()'.format(TITLE, __version__)) + self.setWindowTitle('{} ({})'.format(TITLE, __version__)) self.setWindowIcon(icon()) if sys.platform == 'darwin': self.menuBar().setNativeMenuBar(False) @@ -817,7 +817,7 @@ class TimeSeriesViewer(QgisInterface, QObject): vectorLayers.append(l) except Exception as ex: pass - QgsProject.instance().addMapLayers(vectorLayers) + self.mapLayerStore().addMapLayers(vectorLayers) def addTimeSeriesImages(self, files=None): diff --git a/timeseriesviewer/pixelloader.py b/timeseriesviewer/pixelloader.py index dd828b011dba2f8ee5351423e9072bb076b84b98..dc6f755455dc548bd55bdc721fe60a362ad95a75 100644 --- a/timeseriesviewer/pixelloader.py +++ b/timeseriesviewer/pixelloader.py @@ -68,14 +68,6 @@ class PixelLoaderTask(object): return pickle.loads(byte_object) def __init__(self, source:str, geometries, bandIndices=None, **kwargs): - """ - - :param jobId: jobId number as given by the calling PixelLoader - :param processId: processId, as managed by the calling PixelLoader - :param geometry: SpatialPoint that describes the pixels to be loaded - :param source: file path to raster image. - :param kwargs: additional stuff returned, e.g. to identify somethin - """ if not isinstance(geometries, list): geometries = [geometries] @@ -84,6 +76,8 @@ class PixelLoaderTask(object): assert type(geometry) in [SpatialExtent, SpatialPoint] + self.mId = '' + #assert isinstance(source, str) or isinstance(source, unicode) self.sourcePath = source self.geometries = geometries @@ -107,6 +101,12 @@ class PixelLoaderTask(object): if not k in self.__dict__.keys(): self.__dict__[k] = kwargs[k] + def setId(self, idStr:str): + self.mId = idStr + + def id(self)->str: + return self.mId + def toDump(self): return pickle.dumps(self) @@ -187,16 +187,21 @@ def transformPoint2Px(trans, pt, gt): x, y, _ = trans.TransformPoint(pt.x(), pt.y()) return geo2px(QgsPointXY(x, y), gt) -def doLoaderTask(task): - #assert isinstance(task, PixelLoaderTask), '{}\n{}'.format(type(task), task) - result = task - ds = gdal.Open(task.sourcePath, gdal.GA_ReadOnly) - nb, ns, nl = ds.RasterCount, ds.RasterXSize, ds.RasterYSize +def doLoaderTask(taskWrapper:QgsTask, dump): + #assert isinstance(taskWrapper, QgsTask) + if isinstance(dump, PixelLoaderTask): + task = dump + else: + task = PixelLoaderTask.fromDump(dump) + assert isinstance(task, PixelLoaderTask) + result = task + ds = gdal.Open(task.sourcePath, gdal.GA_ReadOnly) + nb, ns, nl = ds.RasterCount, ds.RasterXSize, ds.RasterYSize bandIndices = list(range(nb)) if task.bandIndices is None else list(task.bandIndices) #ensure to load valid indices only @@ -353,53 +358,7 @@ def doLoaderTask(task): s = "" task.resProfiles = PROFILE_DATA task.mIsDone = True - return task - - - -def pixelLoadingLoop(inputQueue, resultQueue, cancelEvent, finishedEvent): - import time - from multiprocessing.queues import Queue - from multiprocessing.synchronize import Event - assert isinstance(inputQueue, Queue) - assert isinstance(resultQueue, Queue) - assert isinstance(cancelEvent, Event) - assert isinstance(finishedEvent, Event) - - dprint('Pixel Loading Loop Started') - #while not inputQueue.empty(): - while True: - if cancelEvent.is_set(): - dprint('Taskloop put CANCELED') - #resultQueue.put('CANCELED', True) - resultQueue.put('CANCELED') - #if not inputQueue.empty(): - - queueObj = inputQueue.get() - if isinstance(queueObj, bytes): - task = PixelLoaderTask.fromDump(queueObj) - try: - dprint('Taskloop {} doLoaderTask'.format(task.mJobId)) - task = doLoaderTask(task) - dprint('Taskloop {} put task result back to queue'.format(task.mJobId)) - #resultQueue.put(task.toDump(), True, 2) - resultQueue.put(task.toDump()) - except Exception as ex: - dprint('Taskloop {} EXCEPTION {} '.format(task.mJobId, ex)) - #resultQueue.put(ex, True) - resultQueue.put(ex) - elif isinstance(queueObj, str): - if queueObj.startswith('LAST'): - dprint('Taskloop put FINISHED') - #resultQueue.put('FINISHED', True, 2) - resultQueue.put('FINISHED') - #finishedEvent.set() - dprint('Taskloop FINISHED set') - else: - dprint('Taskloop put UNHANDLED') - dprint('Unhandled {} {}'.format(str(queueObj), type(queueObj))) - - + return task.toDump() @@ -438,269 +397,66 @@ class LoadingProgress(object): class PixelLoader(QObject): """ Loads pixel from raster images + Use QgsTaskManager interface in background """ - - sigPixelLoaded = pyqtSignal([int, int, object],[object]) + sigPixelLoaded = pyqtSignal(PixelLoaderTask) sigLoadingStarted = pyqtSignal() - sigLoadingFinished = pyqtSignal(np.timedelta64) - sigLoadingCanceled = pyqtSignal() + sigLoadingFinished = pyqtSignal() + def __init__(self, *args, **kwds): super(PixelLoader, self).__init__(*args, **kwds) - #self.filesList = [] - self.mJobId = -1 - self.mJobProgress = {} - #self.mNumberOfProcesses = 2 - self.mLoadingStartTime = np.datetime64('now','ms') - - #see https://gis.stackexchange.com/questions/35279/multiprocessing-error-in-qgis-with-python-on-windows - #path = os.path.abspath(os.path.join(sys.exec_prefix, '../../bin/pythonw.exe')) - #assert os.path.exists(path) - - multiprocessing.set_executable(os.path.join(sys.exec_prefix, 'pythonw.exe')) - sys.argv = [__file__] - - self.mResultQueue = multiprocessing.Queue(maxsize=0) - self.mTaskQueue = multiprocessing.Queue(maxsize=0) - self.mCancelEvent = multiprocessing.Event() - self.mKillEvent = multiprocessing.Event() - self.mWorkerProcess = None - - self.queueCheckTimer = QTimer() # - #self.queueCheckTimer.setInterval(200) - self.queueCheckTimer.timeout.connect(self.checkTaskResults) - #self.queueCheckTimer.timeout.connect(self.dummySlot) - self.queueCheckTimer.start(250) - - def initWorkerProcess(self, id): - - if not isinstance(self.mWorkerProcess, multiprocessing.Process): - multiprocessing.set_executable(os.path.join(sys.exec_prefix, 'pythonw.exe')) - sys.argv = [__file__] - - self.mWorkerProcess = multiprocessing.Process(name='PixelLoaderWorkingProcess_{}'.format(id), - target=pixelLoadingLoop, - args=(self.mTaskQueue, self.mResultQueue, self.mCancelEvent, self.mKillEvent)) - - self.mWorkerProcess.daemon = True - self.mWorkerProcess.start() - return True - else: - if not self.mWorkerProcess.is_alive(): - dprint('WorkerProcess exit code {}'.format(self.mWorkerProcess.exitcode)) - - #self.mWorkerProcess.join(2) - self.mWorkerProcess = None - - #code = self.mWorkerProcess.exitcode - self.pixelLoadingLoop(self.mTaskQueue, self.mResultQueue, self.mCancelEvent, self.mKillEvent) - #self.mWorkerProcess = None - return False - #self.initWorkerProcess() - #self.mWorkerProcess.run() - else: - return True + self.mTasks = {} + def tasks(self)->list: + """ + Returns the list of QgsTaskWrappers + :return: list + """ + return self.taskManager().tasks() - def onPixelLoaded(self, dataList): - assert isinstance(dataList, list) - for data in dataList: - assert isinstance(data, PixelLoaderTask) - - if data.mJobId not in self.mJobProgress.keys(): - return - else: - progressInfo = self.mJobProgress[data.mJobId] - - assert isinstance(progressInfo, LoadingProgress) - if not data.success(): - s = "" - - progressInfo.addResult(data.success()) - if progressInfo.done() == progressInfo.total(): - self.mJobProgress.pop(data.mJobId) - - self.sigPixelLoaded[int, int, object].emit(progressInfo.done(), progressInfo.total(), data) - self.sigPixelLoaded[object].emit(data) - - #def setNumberOfProcesses(self, nProcesses): - # assert nProcesses >= 1 - # self.mNumberOfProcesses = nProcesses + def taskManager(self)->QgsTaskManager: + return QgsApplication.taskManager() def startLoading(self, tasks): assert isinstance(tasks, list) - self.sigLoadingStarted.emit() - paths = [] - for t in tasks: - assert isinstance(t, PixelLoaderTask) - paths.append(t.sourcePath) - - self.mLoadingStartTime = np.datetime64('now', 'ms') - - self.mJobId += 1 - jobId = self.mJobId - - self.mJobProgress[jobId] = LoadingProgress(jobId, len(tasks)) - - #self.mKillEvent.clear() - for t in tasks: - assert isinstance(t, PixelLoaderTask) - t.mJobId = self.mJobId - self.mTaskQueue.put(t.toDump()) - self.mTaskQueue.put('LAST_{}'.format(jobId)) - - #self.mWorkerProcess = None - t = 0 - while not self.initWorkerProcess('{}.{}'.format(self.mJobId, t)) and t < 10: - t += 1 - s = "" - - - def cancelLoading(self): - self.mCancelEvent.set() - - - def isReadyToLoad(self): - - return self.mTaskQueue is None or (self.mTaskQueue.empty() and self.mResultQueue.empty()) - - - def checkTaskResults(self, *args): - dataList = [] - finished = False - canceled = False - #print('check task results') - if isinstance(self.mWorkerProcess, multiprocessing.Process): - while not self.mResultQueue.empty(): - import queue - try: - #data = self.mResultQueue.get(True, 2) - data = self.mResultQueue.get() - s = "" - except queue.Empty: - break - - if isinstance(data, bytes): - task = PixelLoaderTask.fromDump(data) - dataList.append(task) - dprint('PixelLoader result pulled') - elif isinstance(data, str): - if data == 'FINISHED': - finished = True - elif data == 'CANCELED': - canceled = True - else: - s = "" - else: - raise Exception('Unhandled type returned {}'.format(data)) - if len(dataList) > 0: - self.onPixelLoaded(dataList) - - if finished: - dt = np.datetime64('now', 'ms') - self.mLoadingStartTime - self.sigLoadingFinished.emit(dt) - - - if self.mTaskQueue.empty() and self.mResultQueue.empty(): - pass - #self.mWorkerProcess.terminate() - #self.mWorkerProcess.join() + tm = self.taskManager() + #self.sigLoadingStarted.emit() + #todo: create chuncks + import uuid + for plt in tasks: + assert isinstance(plt, PixelLoaderTask) + taskName = 'pltTask.{}'.format(uuid.uuid4()) + plt.setId(taskName) + dump = plt.toDump() + qgsTask = QgsTask.fromFunction(taskName, doLoaderTask, dump, on_finished=self.onLoadingFinished) + tm.addTask(qgsTask) + self.mTasks[taskName] = qgsTask -if __name__ == '__main__': + def onLoadingFinished(self, *args, **kwds): - from timeseriesviewer.utils import initQgisApplication - import example.Images - qgsApp = initQgisApplication() - from PyQt5.QtGui import * - from PyQt5.QtWidgets import * + error = args[0] + if error is None: + dump = args[1] + plt = PixelLoaderTask.fromDump(dump) + if isinstance(plt, PixelLoaderTask): + self.mTasks.pop(plt.id()) + self.sigPixelLoaded.emit(plt) - from timeseriesviewer.pixelloader import doLoaderTask, PixelLoaderTask + def status(self)->tuple: - gb = QGroupBox() - gb.setTitle('Sandbox') - DEBUG = False - - import example.Images - from timeseriesviewer.utils import file_search - dir = os.path.dirname(example.Images.__file__) - #files = file_search(dir, '*.tif') - files = [example.Images.Img_2014_05_07_LC82270652014127LGN00_BOA] - files.append(example.Images.Img_2014_04_29_LE72270652014119CUB00_BOA) - files.extend(file_search(dir, 're_*.tif')) - for f in files: print(f) - ext = SpatialExtent.fromRasterSource(files[0]) - - from qgis.core import QgsPoint - x,y = ext.center() - - geoms1 = [#SpatialPoint(ext.crs(), 681151.214,-752388.476), #nodata in Img_2014_04_29_LE72270652014119CUB00_BOA - SpatialExtent(ext.crs(),x+10000,y,x+12000, y+70 ), #out of image - SpatialExtent(ext.crs(),x,y,x+10000, y+70 ), - SpatialPoint(ext.crs(), x,y), - SpatialPoint(ext.crs(), x+250, y+70)] - geoms2 = [ # SpatialPoint(ext.crs(), 681151.214,-752388.476), #nodata in Img_2014_04_29_LE72270652014119CUB00_BOA - SpatialPoint(ext.crs(), x - 100, y), - SpatialPoint(ext.crs(), x + 50, y + 70)] - - - def onPxLoaded(*args): - n, nmax, task = args - assert isinstance(task, PixelLoaderTask) - print('Task {} Loaded'.format(task.mJobId)) - print(task) - - PL = PixelLoader() - def onDummy(*args): - print(('dummy',args)) - - def onTimer(*args): - print(('TIMER',PL)) - pass - - PL.sigPixelLoaded.connect(onPxLoaded) - PL.sigLoadingFinished.connect(lambda: onDummy('finished')) - #PL.sigLoadingFinished.connect(qgsApp.quit) - PL.sigLoadingCanceled.connect(lambda: onDummy('canceled')) - PL.sigLoadingStarted.connect(lambda: onDummy('started')) - PL.sigPixelLoaded.connect(lambda : onDummy('px loaded')) - - tasks1 = [] - tasks2 = [] - for i, f in enumerate(files): - kwargs = {'myid':'myID{}'.format(i)} - tasks1.append(PixelLoaderTask(f, geoms1, bandIndices=None, **kwargs)) - tasks2.append(PixelLoaderTask(f, geoms2, bandIndices=None, **kwargs)) - - PL.startLoading(tasks1) - PL.startLoading(tasks2) - - #QTimer.singleShot(2000, lambda : PL.cancelLoading()) - - def addProfile(): - x0, y1 = ext.upperLeftPt() - x1, y0 = ext.lowerRightPt() - - x = x0 + (x1 - x0) * np.random.sample() - y = y0 + (y1 - y0) * np.random.sample() - pt = SpatialPoint(ext.crs(), x, y) - tasks = [] - for i, f in enumerate(files): - tasks.append(PixelLoaderTask(f, [pt], bandIndices=[0,1,3], **kwargs)) - PL.startLoading(tasks) + return None + def cancelLoading(self): + raise NotImplementedError - btn = QPushButton('Add Profile') - btn.clicked.connect(addProfile) - btn.show() - qgsApp.exec_() - s = "" \ No newline at end of file diff --git a/timeseriesviewer/profilevisualization.py b/timeseriesviewer/profilevisualization.py index e238a6498787a53af2d347520a6eda3e92fff455..b1bb6cf7867d8286b0f65189b9ae5ca53f278694 100644 --- a/timeseriesviewer/profilevisualization.py +++ b/timeseriesviewer/profilevisualization.py @@ -1682,25 +1682,23 @@ class SpectralTemporalVisualization(QObject): self.sigMoveToTSD.emit(self.TS[i]) - def onPixelLoaded(self, nDone, nMax, d): - self.ui.progressBar.setValue(nDone) - self.ui.progressBar.setMaximum(nMax) + def onPixelLoaded(self, d): - assert isinstance(d, PixelLoaderTask) + if isinstance(d, PixelLoaderTask): - bn = os.path.basename(d.sourcePath) - if d.success(): + bn = os.path.basename(d.sourcePath) + if d.success(): - t = 'Loaded {} pixel from {}.'.format(len(d.resProfiles), bn) - self.mTemporalProfileLayer.addPixelLoaderResult(d) - self.updateRequested = True - else: - t = 'Failed loading from {}.'.format(bn) - if d.info and d.info != '': - t += '({})'.format(d.info) + t = 'Loaded {} pixel from {}.'.format(len(d.resProfiles), bn) + self.mTemporalProfileLayer.addPixelLoaderResult(d) + self.updateRequested = True + else: + t = 'Failed loading from {}.'.format(bn) + if d.info and d.info != '': + t += '({})'.format(d.info) - # QgsApplication.processEvents() - self.ui.progressInfo.setText(t) + # QgsApplication.processEvents() + self.ui.progressInfo.setText(t) def requestUpdate(self, *args): self.updateRequested = True @@ -1750,7 +1748,7 @@ class SpectralTemporalVisualization(QObject): self.loadCoordinate(spatialPoints=spatialPoints, mode='all', backgroundProcess=backgroundProcess) LOADING_MODES = ['missing','reload','all'] - def loadCoordinate(self, spatialPoints=None, LUT_bandIndices=None, mode='missing', backgroundProcess = False): + def loadCoordinate(self, spatialPoints=None, LUT_bandIndices=None, mode='missing', backgroundProcess = True): """ :param spatialPoints: [list-of-geometries] to load pixel values from :param LUT_bandIndices: dictionary {sensor:[indices]} with band indices to be loaded per sensor @@ -1858,10 +1856,10 @@ class SpectralTemporalVisualization(QObject): self.pixelLoader.startLoading(tasks) else: import timeseriesviewer.pixelloader - tasks = [timeseriesviewer.pixelloader.doLoaderTask(task) for task in tasks] + tasks = [PixelLoaderTask.fromDump(timeseriesviewer.pixelloader.doLoaderTask(None, task.toDump())) for task in tasks] l = len(tasks) for i, task in enumerate(tasks): - self.pixelLoader.sigPixelLoaded.emit(i+1, l, task) + self.pixelLoader.sigPixelLoaded.emit(task) else: if DEBUG: @@ -2003,7 +2001,7 @@ def examplePixelLoader(): gb.setTitle('Sandbox') PL = PixelLoader() - PL.setNumberOfThreads(2) + if False: files = ['observationcloud/testdata/2014-07-26_LC82270652014207LGN00_BOA.bsq', diff --git a/timeseriesviewer/temporalprofiles2d.py b/timeseriesviewer/temporalprofiles2d.py index d89f6f4518a8980e5a6a17b195a15df7deae2704..f8e202f6bff479d6334c065ba484aafe16589e60 100644 --- a/timeseriesviewer/temporalprofiles2d.py +++ b/timeseriesviewer/temporalprofiles2d.py @@ -698,7 +698,7 @@ class TemporalProfile(QObject): for task in tasks: - result = doLoaderTask(task) + result = PixelLoaderTask.fromDump(doLoaderTask(None, task)) assert isinstance(result, PixelLoaderTask) self.pullDataUpdate(result) @@ -1060,7 +1060,7 @@ class TemporalProfileLayer(QgsVectorLayer): LUT_bandIndices[sensor] = list(range(sensor.nb)) PL = PixelLoader() - PL.sigPixelLoaded[object].connect(self.addPixelLoaderResult) + PL.sigPixelLoaded.connect(self.addPixelLoaderResult) # update new / existing points for tsd in self.mTimeSeries: @@ -1097,11 +1097,10 @@ class TemporalProfileLayer(QgsVectorLayer): PL.startLoading(tasks) else: import timeseriesviewer.pixelloader - tasks = [timeseriesviewer.pixelloader.doLoaderTask(task) for task in tasks] + tasks = [PixelLoaderTask.fromDump(timeseriesviewer.pixelloader.doLoaderTask(None, task.toDump())) for task in tasks] l = len(tasks) for i, task in enumerate(tasks): - PL.sigPixelLoaded[int, int, object].emit(i + 1, l, task) - PL.sigPixelLoaded[object].emit(task) + PL.sigPixelLoaded.emit(task) else: @@ -1226,7 +1225,7 @@ class TemporalProfileLayer(QgsVectorLayer): #styles.setRowStyles([red]) - def createTemporalProfiles(self, coordinates): + def createTemporalProfiles(self, coordinates)->list: """ Creates temporal profiles :param coordinates: @@ -1268,7 +1267,7 @@ class TemporalProfileLayer(QgsVectorLayer): p.updateLoadingStatus() return profiles else: - return None + return [] def saveEdits(self, leaveEditable=True, triggerRepaint=True): diff --git a/timeseriesviewer/utils.py b/timeseriesviewer/utils.py index 8a4acde2650413596f15b69131a11091b9352e29..c73f3d3eb9a04e992ff9f00b6f29007c9c9abefd 100644 --- a/timeseriesviewer/utils.py +++ b/timeseriesviewer/utils.py @@ -20,7 +20,7 @@ """ # noinspection PyPep8Naming -import os, sys, math, re, io, fnmatch +import os, sys, math, re, io, fnmatch, uuid from collections import defaultdict @@ -255,6 +255,12 @@ class SpatialPoint(QgsPointXY): crs = mapCanvas.mapSettings().destinationCrs() return SpatialPoint(crs, mapCanvas.center()) + @staticmethod + def fromMapLayerCenter(mapLayer:QgsMapLayer): + assert isinstance(mapLayer, QgsMapLayer) and mapLayer.isValid() + crs = mapLayer.crs() + return SpatialPoint(crs, mapLayer.extent().center()) + @staticmethod def fromSpatialExtent(spatialExtent): assert isinstance(spatialExtent, SpatialExtent) @@ -1092,15 +1098,12 @@ def zipdir(pathDir, pathZip): zip.write(filename, arcname) -def initQgisApplication(pythonPlugins=None, PATH_QGIS=None, qgisDebug=False, qgisResourceDir=None): + +def initQgisApplication(PATH_QGIS=None, qgisDebug=False, qgisResourceDir=None): """ Initializes the QGIS Environment :return: QgsApplication instance of local QGIS installation """ - if pythonPlugins is None: - pythonPlugins = [] - assert isinstance(pythonPlugins, list) - if os.path.exists(os.path.join(DIR_REPO, 'qgisresources')): qgisResourceDir = os.path.join(DIR_REPO, 'qgisresources') if isinstance(qgisResourceDir, str): @@ -1113,24 +1116,11 @@ def initQgisApplication(pythonPlugins=None, PATH_QGIS=None, qgisDebug=False, qgi if "qInitResources" in dir(mod): mod.qInitResources() - envVar = os.environ.get('QGIS_PLUGINPATH', None) - if isinstance(envVar, list): - pythonPlugins.extend(re.split('[;:]', envVar)) - - # make plugin paths available to QGIS and Python - os.environ['QGIS_PLUGINPATH'] = ';'.join(pythonPlugins) - os.environ['QGIS_DEBUG'] = '1' if qgisDebug else '0' - for p in pythonPlugins: - sys.path.append(p) - if isinstance(QgsApplication.instance(), QgsApplication): - return QgsApplication.instance() - else: - if PATH_QGIS is None: - # find QGIS Path + # find QGIS_PREFIX_PATH if sys.platform == 'darwin': # search for the QGIS.app import qgis, re @@ -1148,12 +1138,17 @@ def initQgisApplication(pythonPlugins=None, PATH_QGIS=None, qgisDebug=False, qgi else: # assume OSGeo4W startup PATH_QGIS = os.environ['QGIS_PREFIX_PATH'] - assert os.path.exists(PATH_QGIS) - qgsApp = QgsApplication([], True) - qgsApp.setPrefixPath(PATH_QGIS, True) - qgsApp.initQgis() + try: + import qgis.testing + qgsApp = qgis.testing.start_app() + except Exception as ex: + print(ex) + + qgsApp = QgsApplication([], True) + qgsApp.setPrefixPath(PATH_QGIS, True) + qgsApp.initQgis() def printQgisLog(msg, tag, level): if tag not in ['Processing']: @@ -1175,7 +1170,17 @@ def initQgisApplication(pythonPlugins=None, PATH_QGIS=None, qgisDebug=False, qgi + class TestObjects(): + @staticmethod + def createTestImageSeries(n=1) -> list: + assert n > 0 + + datasets = [] + for i in range(n): + ds = TestObjects.inMemoryImage() + datasets.append(ds) + return datasets @staticmethod def inMemoryImage(nl=10, ns=20, nb=3, crs='EPSG:32632')->gdal.Dataset: @@ -1189,8 +1194,8 @@ class TestObjects(): """ drv = gdal.GetDriverByName('GTiff') assert isinstance(drv, gdal.Driver) - - path = '/vsimem/testimage.tif' + id = uuid.uuid4() + path = '/vsimem/testimage.multiband.{}.tif'.format(id) ds = drv.Create(path, ns, nl, bands=nb, eType=gdal.GDT_Float32) if isinstance(crs, str): @@ -1217,11 +1222,12 @@ class TestObjects(): scheme = ClassificationScheme() scheme.createClasses(n) - drv = gdal.GetDriverByName('MEM') + drv = gdal.GetDriverByName('GTiff') assert isinstance(drv, gdal.Driver) - - ds = drv.Create('', ns, nl, bands=nb, eType=gdal.GDT_Byte) + id = uuid.uuid4() + path = '/vsimem/testimage.class._{}.tif'.format(id) + ds = drv.Create(path, ns, nl, bands=nb, eType=gdal.GDT_Byte) if isinstance(crs, str): c = QgsCoordinateReferenceSystem(crs)