diff --git a/eotimeseriesviewer/temporalprofiles.py b/eotimeseriesviewer/temporalprofiles.py index a242ab87580c59204d50aec84c8fc5b45bd7388c..64b721d8dc9476068433028e5d8a1c53e76c5d1e 100644 --- a/eotimeseriesviewer/temporalprofiles.py +++ b/eotimeseriesviewer/temporalprofiles.py @@ -88,6 +88,7 @@ try: except: pass + def temporalProfileFeatureFields(sensor: SensorInstrument, singleBandOnly=False) -> QgsFields: """ Returns the fields of a single temporal profile @@ -108,6 +109,7 @@ def temporalProfileFeatureFields(sensor: SensorInstrument, singleBandOnly=False) return fields + def sensorExampleQgsFeature(sensor:SensorInstrument, singleBandOnly=False) -> QgsFeature: """ Returns an exemplary QgsFeature with value for a specific sensor @@ -135,6 +137,7 @@ def sensorExampleQgsFeature(sensor:SensorInstrument, singleBandOnly=False) -> Qg f.setAttribute(bandKey, 1.0) return f + def geometryToPixel(ds:gdal.Dataset, geometry: QgsGeometry) -> typing.Tuple[list, list]: """ Returns the pixel-positions of pixels whose pixel center is covered by a geometry @@ -191,12 +194,12 @@ def geometryToPixel(ds:gdal.Dataset, geometry: QgsGeometry) -> typing.Tuple[list return x_indices, y_indices - def dateDOY(date): if isinstance(date, np.datetime64): date = date.astype(datetime.date) return date.timetuple().tm_yday + def daysPerYear(year): if isinstance(year, np.datetime64): @@ -206,6 +209,7 @@ def daysPerYear(year): return dateDOY(datetime.date(year=year, month=12, day=31)) + def date2num(d): #kindly taken from https://stackoverflow.com/questions/6451655/python-how-to-convert-datetime-dates-to-decimal-years if isinstance(d, np.datetime64): @@ -223,6 +227,7 @@ def date2num(d): fraction = 0.9999999 return float(d.year) + fraction + def num2date(n, dt64=True, qDate=False): n = float(n) if n < 1: @@ -732,11 +737,11 @@ class TemporalProfileLayer(QgsVectorLayer): if len(qgsTask.MISSING_DATA) == 0: return - tid = id(qgsTask) - self.mTasks[tid] = qgsTask + #tid = id(qgsTask) + #self.mTasks[tid] = qgsTask - qgsTask.taskCompleted.connect(lambda *args, t=tid: self.onRemoveTask(t)) - qgsTask.taskTerminated.connect(lambda *args, t=tid: self.onRemoveTask(t)) + #qgsTask.taskCompleted.connect(lambda *args, t=tid: self.onRemoveTask(t)) + #qgsTask.taskTerminated.connect(lambda *args, t=tid: self.onRemoveTask(t)) if run_async: tm = QgsApplication.taskManager() @@ -1024,6 +1029,7 @@ class TemporalProfileLayer(QgsVectorLayer): #self.sensorPxLayers.clear() pass + class TemporalProfileLoaderTask(QgsTask): """ A QgsTask to load pixel-band values from different Time Series Source images and @@ -1036,15 +1042,15 @@ class TemporalProfileLoaderTask(QgsTask): required_profiles: typing.List[TemporalProfile] = None, required_sensor_bands: typing.Dict[SensorInstrument, typing.List[int]] = None, callback=None, - block_size: int=10): + progress_interval: int = 10): super().__init__(description='Load Temporal Profiles') - assert isinstance(block_size, int) and block_size > 0 + assert isinstance(progress_interval, int) and progress_interval > 0 assert isinstance(temporalProfileLayer, TemporalProfileLayer) timeSeries: TimeSeries = temporalProfileLayer.timeSeries() self.nTotal = len(timeSeries) - + self.mProgressInterval = datetime.timedelta(seconds=progress_interval) self.mRequiredSensorBands: typing.Dict[SensorInstrument, typing.List[int]] = dict() self.mRequiredSensorBandKeys: typing.Dict[SensorInstrument, typing.List[str]] = dict() self.mTSS2TSD = dict() @@ -1061,7 +1067,7 @@ class TemporalProfileLoaderTask(QgsTask): self.mRequiredSensorBands[sensor] = sorted(band_indices) for band_index in self.mRequiredSensorBands[sensor]: assert isinstance(band_index, int) - assert band_index >= 0 and band_index < sensor.nb + assert 0 <= band_index < sensor.nb for sensor, band_indices in self.mRequiredSensorBands.items(): self.mRequiredSensorBandKeys[sensor] = [bandIndex2bandKey(i) for i in band_indices] @@ -1111,7 +1117,6 @@ class TemporalProfileLoaderTask(QgsTask): self.GEOMETRY_CACHE[tp.id()][crsWKT] = tp.geometry(crs) self.mCallback = callback - self.mBlockSize = block_size def profileIDs(self) -> typing.List[int]: return list(self.MISSING_DATA.keys()) @@ -1126,8 +1131,8 @@ class TemporalProfileLoaderTask(QgsTask): block_results = [] n_total = len(self.timeSeriesSources()) - next_progress = 5 + t0 = datetime.datetime.now() try: for n, tss in enumerate(self.timeSeriesSources()): assert isinstance(tss, TimeSeriesSource) @@ -1143,6 +1148,8 @@ class TemporalProfileLoaderTask(QgsTask): INTERSECTING[tpID] = geom else: print('Missing geometry for crsWKT={}\nTSS={}\n'.format(tss.crsWkt(), tss.uri())) + del geom, GEOM + if len(INTERSECTING) == 0: continue @@ -1156,8 +1163,11 @@ class TemporalProfileLoaderTask(QgsTask): if len(px_x) > 0: PX_INDICES[tpID] = (px_x, px_y) + del tpID, geom + if len(PX_INDICES) == 0: # profiles are out of image + del PX_INDICES continue # create a dictionary that tells us which pixels / TPs positions are to load from which band @@ -1171,6 +1181,7 @@ class TemporalProfileLoaderTask(QgsTask): required_band_profiles[band_key] = set() required_band_profiles[band_key].add(tpID) + del missingTSDValues # load required bands and required pixel positions only for band_key, tpIds in required_band_profiles.items(): @@ -1194,22 +1205,36 @@ class TemporalProfileLoaderTask(QgsTask): if band.GetNoDataValue(): block = block[np.where(block != band.GetNoDataValue())] if len(block) == 0: + del block continue # get mean pixel value value = np.nanmean(block) + del block if np.isfinite(value): self.MISSING_DATA[tpID][tsd][band_key] = value + del band + del required_band_profiles if self.isCanceled(): self.mErrors.append('Canceled') return False - progress = 100 * n / n_total - if progress >= next_progress: + dt = datetime.datetime.now() - t0 + if dt > self.mProgressInterval: + t0 = datetime.datetime.now() + progress = 100 * n / n_total self.setProgress(progress) - next_progress += 5 + + del INTERSECTING + del PX_INDICES + del ds + del ext + del tsd + del tss + + s = "" except Exception as ex: print(traceback.format_exc()) @@ -1227,6 +1252,7 @@ class TemporalProfileLoaderTask(QgsTask): if self.mCallback is not None: self.mCallback(result, self) + class TemporalProfileTableModel(QgsAttributeTableModel): AUTOGENERATES_COLUMNS = [FN_ID, FN_Y, FN_X] @@ -1308,7 +1334,6 @@ class TemporalProfileTableModel(QgsAttributeTableModel): return result - def headerData(self, section:int, orientation:Qt.Orientation, role:int): data = super(TemporalProfileTableModel, self).headerData(section, orientation, role) if role == Qt.ToolTipRole and orientation == Qt.Horizontal: @@ -1327,7 +1352,6 @@ class TemporalProfileTableModel(QgsAttributeTableModel): def supportedDropActions(self): return Qt.CopyAction | Qt.MoveAction - def supportedDragActions(self): return Qt.CopyAction @@ -1349,10 +1373,7 @@ class TemporalProfileTableModel(QgsAttributeTableModel): class TemporalProfileFeatureSelectionManager(QgsIFeatureSelectionManager): - - def __init__(self, layer, parent=None): - s ="" super(TemporalProfileFeatureSelectionManager, self).__init__(parent) assert isinstance(layer, QgsVectorLayer) self.mLayer = layer @@ -1373,9 +1394,8 @@ class TemporalProfileFeatureSelectionManager(QgsIFeatureSelectionManager): self.mLayer.select(ids) def selectFeatures(self, selection, command): + super(TemporalProfileFeatureSelectionManager, self).selectFeatures(selection, command) - super(TemporalProfileFeatureSelectionManager, self).selectF - s = "" def selectedFeatureCount(self): return self.mLayer.selectedFeatureCount() diff --git a/tests/test_main.py b/tests/test_main.py index 553bf9bc3ad58e759aa2ff1c7a975e931d756eab..656dd9fd242ae4c1b390958910b370ceeddeda34 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -104,7 +104,7 @@ class TestMain(EOTSVTestCase): TSV = EOTimeSeriesViewer() TSV.createMapView('True Color') - TSV.loadExampleTimeSeries() + TSV.loadExampleTimeSeries(loadAsync=True) while QgsApplication.taskManager().countActiveTasks() > 0 or len(TSV.timeSeries().mTasks) > 0: QCoreApplication.processEvents()