diff --git a/sandbox/__init__.py b/sandbox/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/sandbox/sandbox.py b/sandbox/sandbox.py new file mode 100644 index 0000000000000000000000000000000000000000..0c35882cce5b2ab0bef628ea2c4a9d58fb0630de --- /dev/null +++ b/sandbox/sandbox.py @@ -0,0 +1,194 @@ +from __future__ import absolute_import +import six, sys, os, gc, re, collections, site, inspect, time +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 timeseriesviewer import DIR_EXAMPLES, jp, dprint +DIR_SANDBOX = os.path.dirname(__file__) + +from itertools import izip_longest + +def grouper(iterable, n, fillvalue=None): + args = [iter(iterable)] * n + return izip_longest(*args, fillvalue=fillvalue) + + + +class HiddenMapCanvas(QgsMapCanvas): + + sigPixmapCreated = pyqtSignal(QgsRasterLayer, QPixmap) + + + def __init__(self, *args, **kwds): + super(HiddenMapCanvas,self).__init__(*args, **kwds) + self.reg = QgsMapLayerRegistry.instance() + self.painter = QPainter() + self.layerQueue = list() + self.mapCanvasRefreshed.connect(self.createPixmap) + + def isBusy(self): + return len(self.layerQueue) != 0 + + def createPixmap(self, *args): + assert len(self.layerQueue) > 0 + + pixmap = QPixmap(self.size()) + self.painter.begin(pixmap) + self.map().paint(self.painter) + self.painter.end() + assert not pixmap.isNull() + lyr = self.layerQueue.pop(0) + + assert lyr.extent().intersects(self.extent()) + self.sigPixmapCreated.emit(lyr, pixmap) + self.startSingleLayerRendering() + + + def startLayerRendering(self, layers): + assert isinstance(layers, list) + self.layerQueue.extend(layers) + self.startSingleLayerRendering() + + + + def startSingleLayerRendering(self): + + if len(self.layerQueue) > 0: + mapLayer = self.layerQueue[0] + self.reg.addMapLayer(mapLayer) + lyrSet = [QgsMapCanvasLayer(mapLayer)] + self.setLayerSet(lyrSet) + + #todo: add crosshair + self.refreshAllLayers() + + + +n_PX = 0 +def newPixmap(layer, pixmap): + global n_PX + pathPNG = jp(DIR_SANDBOX, 'mapimage{}.png'.format(n_PX)) + n_PX += 1 + # .saveAsImage(pathPNG) + # pm = C.layerToPixmap(lyrRef, QSize(600,600)) + print('Write ' + pathPNG) + pixmap.toImage().save(pathPNG) + + s = "" + + +if __name__ == '__main__': + import site, sys + #add site-packages to sys.path as done by enmapboxplugin.py + + from timeseriesviewer import DIR_SITE_PACKAGES + site.addsitedir(DIR_SITE_PACKAGES) + + #prepare QGIS environment + if sys.platform == 'darwin': + PATH_QGS = r'/Applications/QGIS.app/Contents/MacOS' + #os.environ['GDAL_DATA'] = r'/usr/local/Cellar/gdal/1.11.3_1/share' + else: + # assume OSGeo4W startup + PATH_QGS = os.environ['QGIS_PREFIX_PATH'] + assert os.path.exists(PATH_QGS) + + qgsApp = QgsApplication([], True) + QApplication.addLibraryPath(r'/Applications/QGIS.app/Contents/PlugIns') + QApplication.addLibraryPath(r'/Applications/QGIS.app/Contents/PlugIns/qgis') + qgsApp.setPrefixPath(PATH_QGS, True) + qgsApp.initQgis() + + from timeseriesviewer import file_search, PATH_EXAMPLE_TIMESERIES + from timeseriesviewer.timeseries import TimeSeries, TimeSeriesDatum + pathTestTS = jp(DIR_EXAMPLES, 'ExampleTimeSeries.csv') + TS = TimeSeries() + if False or not os.path.exists(pathTestTS): + paths = file_search(jp(DIR_EXAMPLES, 'Images'), '*.bsq') + + TS.addFiles(paths) + TS.saveToFile(PATH_EXAMPLE_TIMESERIES) + else: + TS.loadFromFile(PATH_EXAMPLE_TIMESERIES) + + + + + + C = HiddenMapCanvas() + C.setAutoFillBackground(True) + C.setCanvasColor(Qt.green) + #C.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + + #l = QHBoxLayout() + #l.addWidget(C) + #w.layout().addWidget(C) + #w.show() + + + #C.setFixedSize(QSize(300,400)) + lyrRef = TS.data[24].lyrImg + layers = [TS.data[i].lyrImg for i in [23,12,16]] + QgsMapLayerRegistry.instance().addMapLayer(lyrRef, False) + C.setDestinationCrs(lyrRef.crs()) + C.setExtent(lyrRef.extent()) + C.setFixedSize(QSize(600, 600)) + C.sigPixmapCreated.connect(newPixmap) + + + C.startLayerRendering(layers) + #w.show() + qgsApp.exec_() + + pathPNG = jp(DIR_SANDBOX, 'mapimage.png') + #.saveAsImage(pathPNG) + #pm = C.layerToPixmap(lyrRef, QSize(600,600)) + #pm.toImage().save(pathPNG) + + + #qgsApp.exitQgis() + s = "" + + if False: + drvMEM = gdal.GetDriverByName('MEM') + ds = gdal.Open(TS.data[0].pathImg) + ds = drvMEM.CreateCopy('',ds) + + lyr = QgsRasterLayer(paths[0]) + finalLayerList = [] + def callback(result): + assert isinstance(result, QgsRasterLayer) + print(result) + finalLayerList.append(result) + s = "" + cnt = 0 + def callbackFin(): + cnt-=1 + + #run + #LL = LayerLoaderR(paths) + #LL.signales.sigLayerLoaded.connect(callback) + #r = LL.run() + + import numpy as np + #pool = QThreadPool() + pool = QThreadPool.globalInstance() + pool.setMaxThreadCount(4) + for files in grouper(paths, 3): + + LL = TSDLoader([f for f in files if f is not None]) + cnt += 1 + LL.signals.sigRasterLayerLoaded.connect(callback) + LL.signals.sigFinished.connect(callbackFin) + pool.start(LL) + + t0 = np.datetime64('now') + pool.waitForDone() + + + s = "" \ No newline at end of file diff --git a/timeseriesviewer/main.py b/timeseriesviewer/main.py index c009cfbc10cee498884fee771346f70e54c7af63..190315b1a732d719b2f036f9837bbcf95d172903 100644 --- a/timeseriesviewer/main.py +++ b/timeseriesviewer/main.py @@ -56,7 +56,7 @@ import code import codecs #abbreviations -from timeseriesviewer import jp, mkdir, DIR_SITE_PACKAGES, file_search +from timeseriesviewer import jp, mkdir, DIR_SITE_PACKAGES, file_search, dprint site.addsitedir(DIR_SITE_PACKAGES) from timeseriesviewer.ui import widgets @@ -224,6 +224,7 @@ class BandView(QObject): removeView = pyqtSignal(object) def __init__(self, TS, recommended_bands=None): + super(BandView, self).__init__() assert type(TS) is TimeSeries self.representation = collections.OrderedDict() self.TS = TS @@ -245,60 +246,190 @@ class BandView(QObject): to_add = ts_sensors - represented_sensors to_remove = represented_sensors - ts_sensors for S in to_remove: - self.representation[S].close() + self.representation[S].getWidget().close() self.representation.pop(S) for S in to_add: self.initSensor(S) - def initSensor(self, sensor, recommended_bands=None): + def initSensor(self, sensor): """ :param sensor: - :param recommended_bands: :return: """ - assert type(sensor) is SensorInstrument if sensor not in self.representation.keys(): x = widgets.ImageChipViewSettings(sensor) - x.create() self.representation[sensor] = x - def getSensorStats(self, sensor, bands): - """ - - :param sensor: - :param bands: - :return: - """ - assert type(sensor) is SensorInstrument - dsRef = gdal.Open(self.Sensors[sensor][0]) - return [dsRef.GetRasterBand(b).ComputeRasterMinMax() for b in bands] - - - def getRanges(self, sensor): - return self.getWidget(sensor).getRGBSettings()[1] - - def getBands(self, sensor): - return self.getWidget(sensor).getRGBSettings()[0] - - - def getRGBSettings(self, sensor): - return self.getWidget(sensor).getRGBSettings() - def getWidget(self, sensor): assert type(sensor) is SensorInstrument return self.representation[sensor] +class RenderJob(object): + def __init__(self, TSD, renderer, destinationId=None): + assert isinstance(TSD, TimeSeriesDatum) + assert isinstance(renderer, QgsRasterRenderer) - def useMaskValues(self): + self.TSD = TSD + self.renderer = renderer + self.destinationId = destinationId + + def __eq__(self, other): + if not isinstance(other, RenderJob): + return False + return self.TSD == other.TSD and \ + self.renderer == other.renderer and \ + self.destinationId == other.destinationId - #todo: - return False +class PixmapBuffer(QObject): + sigProgress = pyqtSignal(int, int) + sigPixmapCreated = pyqtSignal(RenderJob, QPixmap) + + def __init__(self): + super(PixmapBuffer, self).__init__() + + self.extent = None + self.crs = None + self.size = None + self.nWorkersMax = 1 + self.Workers = [] + self.PIXMAPS = dict() + self.JOBS = [] + self.nTotalJobs = -1 + self.nJobsDone = -1 + + def addWorker(self): + w = Worker() + w.setCrsTransformEnabled(True) + w.sigPixmapCreated.connect(self.newPixmapCreated) + if self.isValid(): + self.setWorkerProperties(w) + self.Workers.append(w) + + def setWorkerProperties(self, worker): + assert isinstance(worker, Worker) + worker.setFixedSize(self.size) + worker.setDestinationCrs(self.crs) + worker.setExtent(self.extent) + worker.setCenter(self.extent.center()) + + def newPixmapCreated(self, renderJob, pixmap): + self.JOBS.remove(renderJob) + self.nJobsDone += 1 + self.sigPixmapCreated.emit(renderJob, pixmap) + self.sigProgress.emit(self.nJobsDone, self.nTotalJobs) + + def isValid(self): + return self.extent != None and self.crs != None and self.size != None + + def setExtent(self, extent, crs, maxPx): + self.stopRendering() + assert isinstance(extent, QgsRectangle) + assert isinstance(crs, QgsCoordinateReferenceSystem) + assert isinstance(maxPx, int) + ratio = extent.width() / extent.height() + if ratio < 1: # x is largest side + size = QSize(maxPx, int(maxPx / ratio)) + else: # y is largest + size = QSize(int(maxPx * ratio), maxPx) + + self.crs = crs + self.size = size + self.extent = extent + for w in self.Workers: + self.setWorkerProperties(w) + return size + + def stopRendering(self): + for w in self.Workers: + w.stopRendering() + w.clear() + while len(self.JOBS) > 0: + self.JOBS.pop(0) + self.sigProgress.emit(0, 0) + + def loadSubsets(self, jobs): + for j in jobs: + assert isinstance(j, RenderJob) + + self.stopRendering() + + self.JOBS.extend(jobs) + self.nTotalJobs = len(self.JOBS) + self.nJobsDone = 0 + self.sigProgress.emit(0, self.nTotalJobs) + + if len(self.Workers) == 0: + self.addWorker() + + #split jobs to number of workers + i = 0 + chunkSize = int(len(self.JOBS) / len(self.Workers)) + assert chunkSize > 0 + for i in range(0, len(self.Workers), chunkSize): + worker = self.Workers[i] + j = min(i+chunkSize, len(self.JOBS)) + worker.startLayerRendering(self.JOBS[i:j]) + +class Worker(QgsMapCanvas): + + sigPixmapCreated = pyqtSignal(RenderJob, QPixmap) + + + def __init__(self, *args, **kwds): + super(Worker,self).__init__(*args, **kwds) + self.reg = QgsMapLayerRegistry.instance() + self.painter = QPainter() + self.renderJobs = list() + self.mapCanvasRefreshed.connect(self.createPixmap) + + def isBusy(self): + return len(self.renderJobs) != 0 + + def createPixmap(self, *args): + if len(self.renderJobs) > 0: + pixmap = QPixmap(self.size()) + self.painter.begin(pixmap) + self.map().paint(self.painter) + self.painter.end() + assert not pixmap.isNull() + job = self.renderJobs.pop(0) + self.sigPixmapCreated.emit(job, pixmap) + self.startSingleLayerRendering() + + def stopLayerRendering(self): + self.stopRendering() + del self.renderJobs[:] + assert self.isBusy() is False + + def startLayerRendering(self, renderJobs): + assert isinstance(renderJobs, list) + self.renderJobs.extend(renderJobs) + self.startSingleLayerRendering() + + + + def startSingleLayerRendering(self): + + if len(self.renderJobs) > 0: + renderJob = self.renderJobs[0] + assert isinstance(renderJob, RenderJob) + #mapLayer = QgsRasterLayer(renderJob.TSD.pathImg) + mapLayer = renderJob.TSD.lyrImg + mapLayer.setRenderer(renderJob.renderer) + dprint('QgsMapLayerRegistry count: {}'.format(self.reg.count())) + self.reg.addMapLayer(mapLayer) + + lyrSet = [QgsMapCanvasLayer(mapLayer)] + self.setLayerSet(lyrSet) + + #todo: add crosshair + self.refreshAllLayers() class ImageChipLabel(QLabel): @@ -306,21 +437,24 @@ class ImageChipLabel(QLabel): clicked = pyqtSignal(object, object) - def __init__(self, time_series_viewer=None, iface=None, TSD=None, bands=None): - super(ImageChipLabel, self).__init__(time_series_viewer) + def __init__(self, time_series_viewer, TSD, renderer): + assert isinstance(time_series_viewer, TimeSeriesViewer) + assert isinstance(TSD, TimeSeriesDatum) + assert isinstance(renderer, QgsRasterRenderer) + + super(ImageChipLabel, self).__init__(time_series_viewer.ui) self.TSV = time_series_viewer self.TSD = TSD self.bn = os.path.basename(self.TSD.pathImg) - self.iface=iface - self.bands=bands + self.renderer = renderer self.setContextMenuPolicy(Qt.DefaultContextMenu) self.setFrameShape(QFrame.StyledPanel) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) tt = ['Date: {}'.format(TSD.date) \ ,'Name: {}'.format(self.bn) \ - ,'RGB: {}'.format(','.join([str(b) for b in bands]))] + ] self.setToolTip(list2str(tt)) @@ -338,7 +472,7 @@ class ImageChipLabel(QLabel): #add QGIS specific options - if self.iface: + if self.TSV.iface: action = menu.addAction('Add {} to QGIS layers'.format(self.bn)) action.triggered.connect(lambda : qgis_add_ins.add_QgsRasterLayer(self.iface, self.TSD.pathImg, self.bands)) @@ -430,47 +564,32 @@ def Array2Image(d3d): return QImage(d3d.data, ns, nl, QImage.Format_RGB888) -class LayerLoader(QRunnable): - - def __init__(self, paths): - super(LayerLoader, self).__init__() - self.signals = LoaderSignals() +class VerticalLabel(QLabel): + def __init__(self, text): + super(VerticalLabel, self).__init__(text) + self.update() + self.updateGeometry() - self.paths = paths - def run(self): - lyrs = [] - for path in self.paths: - lyr = QgsRasterLayer(path) - if lyr: - lyrs.append(lyr) - self.signals.sigRasterLayerLoaded.emit(lyr) - print('{} loaded'.format(path)) - else: - print('Failed to load {}'.format(path)) - self.signals.sigFinished.emit() + def paintEvent(self, ev): + p = QPainter(self) + p.rotate(-90) + rgn = QRect(-self.height(), 0, self.height(), self.width()) + align = self.alignment() + self.hint = p.drawText(rgn, align, self.text()) + p.end() -class VerticalLabel(QLabel): - def __init__(self, text): - super(self.__class__, self).__init__() - self.text = text - - def paintEvent(self, event): - painter = QPainter(self) - painter.setPen(Qt.black) - painter.translate(20, 100) - painter.rotate(-90) - if self.text: - painter.drawText(0, 0, self.text) - painter.end() - - def minimumSizeHint(self): - size = QLabel.minimumSizeHint(self) - return QSize(size.height(), size.width()) + self.setMaximumWidth(self.hint.height()) + self.setMinimumWidth(0) + self.setMaximumHeight(16777215) + self.setMinimumHeight(self.hint.width()) def sizeHint(self): - size = QLabel.sizeHint(self) - return QSize(size.height(), size.width()) + if hasattr(self, 'hint'): + return QSize(self.hint.height(), self.hint.width()) + else: + return QSize(19, 50) + class ImageChipBuffer(object): @@ -598,6 +717,9 @@ class ImageChipBuffer(object): list2str = lambda ll : '\n'.join([str(l) for l in ll]) + + + class TimeSeriesViewer: """QGIS Plugin Implementation.""" @@ -618,20 +740,31 @@ class TimeSeriesViewer: # Create the dialog (after translation) and keep reference from timeseriesviewer.ui.widgets import TimeSeriesViewerUI - self.dlg = TimeSeriesViewerUI() - D = self.dlg + self.ui = TimeSeriesViewerUI() + D = self.ui + + #init empty time series + self.TS = TimeSeries() + self.hasInitialCenterPoint = False + self.TS.datumAdded.connect(self.ua_datumAdded) + self.TS.changed.connect(self.timeseriesChanged) + self.TS.progress.connect(self.ua_TSprogress) - #init on empty time series - self.TS = None - self.init_TimeSeries() + #init TS model + TSM = TimeSeriesTableModel(self.TS) + D = self.ui + D.tableView_TimeSeries.setModel(TSM) + D.tableView_TimeSeries.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents) self.BAND_VIEWS = list() self.ImageChipBuffer = ImageChipBuffer() + self.PIXMAPS = PixmapBuffer() + self.PIXMAPS.sigPixmapCreated.connect(self.showSubset) self.CHIPWIDGETS = collections.OrderedDict() self.ValidatorPxX = QIntValidator(0,99999) self.ValidatorPxY = QIntValidator(0,99999) - D.btn_showPxCoordinate.clicked.connect(lambda: self.ua_showPxCoordinate_start()) + D.btn_showPxCoordinate.clicked.connect(lambda: self.showSubsetsStart()) D.btn_selectByCoordinate.clicked.connect(self.ua_selectByCoordinate) D.btn_selectByRectangle.clicked.connect(self.ua_selectByRectangle) D.btn_addBandView.clicked.connect(lambda :self.ua_addBandView()) @@ -648,7 +781,7 @@ class TimeSeriesViewer: D.actionSave_Time_Series.triggered.connect(self.ua_saveTSFile) D.actionLoad_Example_Time_Series.triggered.connect(self.ua_loadExampleTS) D.actionAbout.triggered.connect( \ - lambda: QMessageBox.about(self.dlg, 'SenseCarbon TimeSeriesViewer', 'A viewer to visualize raster time series data')) + lambda: QMessageBox.about(self.ui, 'SenseCarbon TimeSeriesViewer', 'A viewer to visualize raster time series data')) D.btn_removeTSD.clicked.connect(lambda : self.ua_removeTSD(None)) D.btn_removeTS.clicked.connect(self.ua_clear_TS) @@ -659,28 +792,28 @@ class TimeSeriesViewer: self.RectangleMapTool = None self.PointMapTool = None - self.canvas_srs = osr.SpatialReference() + self.canvasCrs = QgsCoordinateReferenceSystem() if self.iface: self.canvas = self.iface.mapCanvas() self.RectangleMapTool = qgis_add_ins.RectangleMapTool(self.canvas) - self.RectangleMapTool.rectangleDrawed.connect(self.ua_selectBy_Response) + self.RectangleMapTool.rectangleDrawed.connect(self.setSpatialSubset) self.PointMapTool = qgis_add_ins.PointMapTool(self.canvas) - self.PointMapTool.coordinateSelected.connect(self.ua_selectBy_Response) + self.PointMapTool.coordinateSelected.connect(self.setSpatialSubset) #self.RectangleMapTool.connect(self.ua_selectByRectangle_Done) - self.ICP = self.dlg.scrollArea_imageChip_content.layout() - self.dlg.scrollArea_bandViews_content.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) - self.BVP = self.dlg.scrollArea_bandViews_content.layout() + self.ICP = self.ui.scrollArea_imageChip_content.layout() + self.ui.scrollArea_bandViews_content.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) + self.BVP = self.ui.scrollArea_bandViews_content.layout() self.check_enabled() s = "" def setDOILabel(self, i): TSD = self.TS.data[i-1] - self.dlg.labelDOI.setText(str(TSD.date)) + self.ui.labelDOI.setText(str(TSD.date)) s = "" @staticmethod @@ -690,46 +823,25 @@ class TimeSeriesViewer: def icon(self): return TimeSeriesViewer.icon() - def init_TimeSeries(self, TS=None): - - if TS is None: - TS = TimeSeries() - assert type(TS) is TimeSeries - - if self.TS is not None: - disconnect_signal(self.TS.datumAdded) - disconnect_signal(self.TS.progress) - disconnect_signal(self.TS.chipLoaded) - disconnect_signal(self.TS.changed) - - self.TS = TS - self.TS.datumAdded.connect(self.ua_datumAdded) - self.TS.changed.connect(self.timeseriesChanged) - self.TS.progress.connect(self.ua_TSprogress) - self.TS.chipLoaded.connect(self.ua_showPxCoordinate_addChips) - - TSM = TimeSeriesTableModel(self.TS) - D = self.dlg - D.tableView_TimeSeries.setModel(TSM) - D.tableView_TimeSeries.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents) - #D.cb_doi.setModel(TSM) - #D.cb_doi.setModelColumn(0) - #D.cb_doi.currentIndexChanged.connect(self.scrollToDate) - def timeseriesChanged(self): - D = self.dlg + D = self.ui D.sliderDOI.setMinimum(1) D.sliderDOI.setMaximum(len(self.TS.data)) - s = "" + if len(self.TS.data)>0 and not self.hasInitialCenterPoint: + extent = self.TS.getMaxExtent(self.canvasCrs) + self.setSpatialSubset(extent.center(), self.canvasCrs) + self.hasInitialCenterPoint = True + if len(self.TS.data) == 0: + self.hasInitialCenterPoint = False def ua_loadTSFile(self, path=None): if path is None or path is False: - path = QFileDialog.getOpenFileName(self.dlg, 'Open Time Series file', '') + path = QFileDialog.getOpenFileName(self.ui, 'Open Time Series file', '') if os.path.exists(path): - M = self.dlg.tableView_TimeSeries.model() + M = self.ui.tableView_TimeSeries.model() M.beginResetModel() self.ua_clear_TS() self.TS.loadFromFile(path) @@ -740,7 +852,7 @@ class TimeSeriesViewer: self.check_enabled() def ua_saveTSFile(self): - path = QFileDialog.getSaveFileName(self.dlg, caption='Save Time Series file') + path = QFileDialog.getSaveFileName(self.ui, caption='Save Time Series file') if path is not None: self.TS.saveToFile(path) @@ -748,7 +860,7 @@ class TimeSeriesViewer: def ua_loadExampleTS(self): from timeseriesviewer import PATH_EXAMPLE_TIMESERIES if not os.path.exists(PATH_EXAMPLE_TIMESERIES): - QMessageBox.information(self.dlg, 'File not found', '{} - this file describes an exemplary time series.'.format(path_example)) + QMessageBox.information(self.ui, 'File not found', '{} - this file describes an exemplary time series.'.format(path_example)) else: self.ua_loadTSFile(path=PATH_EXAMPLE_TIMESERIES) @@ -764,18 +876,21 @@ class TimeSeriesViewer: def setCanvasSRS(self,srs): assert isinstance(srs, QgsCoordinateReferenceSystem) - self.canvas_srs = srs + #self.canvas_srs = srs + self.canvasCrs = srs + self.ui.tb_bb_srs.setPlainText(self.canvasCrs.toWkt()) - self.dlg.tb_bb_srs.setPlainText(self.canvas_srs.toWkt()) + def setSpatialSubset(self, geometry, crs): + assert isinstance(crs, QgsCoordinateReferenceSystem) + assert isinstance(geometry, QgsRectangle) or isinstance(geometry, QgsPoint) - def ua_selectBy_Response(self, geometry, srs_wkt): - D = self.dlg + D = self.ui x = D.spinBox_coordinate_x.value() y = D.spinBox_coordinate_x.value() dx = D.doubleSpinBox_subset_size_x.value() dy = D.doubleSpinBox_subset_size_y.value() - self.setCanvasSRS(osr.GetUserInputAsWKT(str(srs_wkt))) + self.setCanvasSRS(crs) if type(geometry) is QgsRectangle: @@ -790,26 +905,13 @@ class TimeSeriesViewer: x = geometry.x() y = geometry.y() - """ - ref_srs = self.TS.getSRS() - if ref_srs is not None and not ref_srs.IsSame(canvas_srs): - print('Convert canvas coordinates to time series SRS') - g = ogr.Geometry(ogr.wkbPoint) - g.AddPoint(x,y) - g.AssignSpatialReference(canvas_srs) - g.TransformTo(ref_srs) - x = g.GetX() - y = g.GetY() - """ - - D.doubleSpinBox_subset_size_x.setValue(dx) D.doubleSpinBox_subset_size_y.setValue(dy) D.spinBox_coordinate_x.setValue(x) D.spinBox_coordinate_y.setValue(y) if D.cb_loadSubsetDirectly.isChecked(): - self.ua_showPxCoordinate_start() + self.showSubsetsStart() def qgs_handleMouseDown(self, pt, btn): pass @@ -819,7 +921,7 @@ class TimeSeriesViewer: def ua_TSprogress(self, v_min, v, v_max): assert v_min <= v and v <= v_max if v_min < v_max: - P = self.dlg.progressBar + P = self.ui.progressBar if P.minimum() != v_min or P.maximum() != v_max: P.setRange(v_min, v_max) else: @@ -827,27 +929,27 @@ class TimeSeriesViewer: P.setValue(v) - def ua_datumAdded(self): + def ua_datumAdded(self, TSD): - if len(self.TS) > 0: - self.setCanvasSRS(self.TS.getSRS()) - if self.dlg.spinBox_coordinate_x.value() == 0.0 and \ - self.dlg.spinBox_coordinate_y.value() == 0.0: + if len(self.TS) == 1: + self.setCanvasSRS(TSD.lyrImg.crs()) + if self.ui.spinBox_coordinate_x.value() == 0.0 and \ + self.ui.spinBox_coordinate_y.value() == 0.0: bbox = self.TS.getMaxExtent(srs=self.canvas_srs) - self.dlg.spinBox_coordinate_x.setRange(bbox.xMinimum(), bbox.xMaximum()) - self.dlg.spinBox_coordinate_y.setRange(bbox.yMinimum(), bbox.yMaximum()) + self.ui.spinBox_coordinate_x.setRange(bbox.xMinimum(), bbox.xMaximum()) + self.ui.spinBox_coordinate_y.setRange(bbox.yMinimum(), bbox.yMaximum()) #x, y = self.TS.getSceneCenter() c = bbox.center() - self.dlg.spinBox_coordinate_x.setValue(c.x()) - self.dlg.spinBox_coordinate_y.setValue(c.y()) + self.ui.spinBox_coordinate_x.setValue(c.x()) + self.ui.spinBox_coordinate_y.setValue(c.y()) s = "" #self.dlg.sliderDOI - self.dlg.tableView_TimeSeries.resizeColumnsToContents() + self.ui.tableView_TimeSeries.resizeColumnsToContents() def check_enabled(self): - D = self.dlg + D = self.ui hasTS = len(self.TS) > 0 or DEBUG hasTSV = len(self.BAND_VIEWS) > 0 hasQGIS = qgis_available @@ -891,13 +993,13 @@ class TimeSeriesViewer: self.iface.removeToolBarIcon(self.action) def run(self): - self.dlg.show() + self.ui.show() def scrollToDate(self, date_of_interest): QApplication.processEvents() - HBar = self.dlg.scrollArea_imageChips.horizontalScrollBar() + HBar = self.ui.scrollArea_imageChips.horizontalScrollBar() TSDs = list(self.CHIPWIDGETS.keys()) if len(TSDs) == 0: return @@ -918,37 +1020,21 @@ class TimeSeriesViewer: HBar.setValue(i_doi * step) - def ua_showPxCoordinate_start(self): + def showSubsetsStart(self): if len(self.TS) == 0: return - D = self.dlg - dx = D.doubleSpinBox_subset_size_x.value() * 0.5 - dy = D.doubleSpinBox_subset_size_y.value() * 0.5 - - cx = D.spinBox_coordinate_x.value() - cy = D.spinBox_coordinate_y.value() - - pts = [(cx - dx, cy + dy), \ - (cx + dx, cy + dy), \ - (cx + dx, cy - dy), \ - (cx - dx, cy - dy)] - - bb = getBoundingBoxPolygon(pts, srs=self.canvas_srs) - bbWkt = bb.ExportToWkt() - srsWkt = bb.GetSpatialReference().ExportToWkt() - self.ImageChipBuffer.setBoundingBox(bb) - - D = self.dlg - ratio = dx / dy - size_px = D.spinBox_chipsize_max.value() - if ratio > 1: #x is largest side - size_x = size_px - size_y = int(size_px / ratio) - else: #y is largest - size_y = size_px - size_x = int(size_px * ratio) + D = self.ui + easting = QgsVector(D.doubleSpinBox_subset_size_x.value(), 0.0) + northing = QgsVector(0.0, D.doubleSpinBox_subset_size_y.value()) + + Center = QgsPoint(D.spinBox_coordinate_x.value(), D.spinBox_coordinate_y.value()) + UL = Center - (easting * 0.5) + (northing * 0.5) + LR = Center + (easting * 0.5) - (northing * 0.5) + extent = QgsRectangle(UL,LR) + maxPx = int(D.spinBoxMaxPixmapSize.value()) + pxSize = self.PIXMAPS.setExtent(extent, self.canvasCrs, maxPx) #get the dates of interes dates_of_interest = list() @@ -987,7 +1073,7 @@ class TimeSeriesViewer: for TSD in self.TS.getTSDs(date_of_interest=date): TSDs_of_interest.append(TSD) - info_label_text = '{}\n{}'.format(TSD.date, TSD.sensor.sensor_name) + info_label_text = '{}\n{}'.format(TSD.date, TSD.sensor.sensorName) textLabel = QLabel(info_label_text) tt = [TSD.date,TSD.pathImg, TSD.pathMsk] textLabel.setToolTip(list2str(tt)) @@ -995,16 +1081,18 @@ class TimeSeriesViewer: viewList = list() j = 1 for view in self.BAND_VIEWS: - bands = view.getBands(TSD.sensor) + viewWidget = view.getWidget(TSD.sensor) + layerRenderer = viewWidget.layerRenderer() + #imageLabel = QLabel() #imv = pg.GraphicsView() #imv = QGraphicsView(self.dlg.scrollArea_imageChip_content) #imv = MyGraphicsView(self.dlg.scrollArea_imageChip_content, iface=self.iface, path=TSD.pathImg, bands=bands) #imv = pg.ImageView(view=None) - imgLabel = ImageChipLabel(time_series_viewer=self.dlg, iface=self.iface, TSD=TSD, bands=bands) + imgLabel = ImageChipLabel(self, TSD, layerRenderer) - imgLabel.setMinimumSize(size_x, size_y) - imgLabel.setMaximumSize(size_x, size_y) + imgLabel.setMinimumSize(pxSize) + imgLabel.setMaximumSize(pxSize) imgLabel.clicked.connect(self.ua_collect_date) @@ -1020,48 +1108,47 @@ class TimeSeriesViewer: cnt_chips += 1 - self.dlg.scrollArea_imageChip_content.update() + self.ui.scrollArea_imageChip_content.update() self.scrollToDate(centerDate) - s = "" - #ScrollArea.show() - #ScrollArea.horizontalScrollBar().setValue() - + #todo: start pixmap loading + #define render jobs + #(TSD, [renderers] in order of views) + LUT_RENDERER = {} + for view in self.BAND_VIEWS: + for sensor in view.Sensors.keys(): + if sensor not in LUT_RENDERER.keys(): + LUT_RENDERER[sensor] = [] + LUT_RENDERER[sensor].append( + view.getWidget(sensor).layerRenderer() + ) - #fill image labels - required_bands = dict() - for j, view in enumerate(self.BAND_VIEWS): - for S in view.Sensors.keys(): - bands = set() - bands.update(view.getBands(S)) - if len(bands) != 3: - s = "" - assert len(bands) == 3 - if S not in required_bands.keys(): - required_bands[S] = set() - required_bands[S] = required_bands[S].union(bands) - missing = set() + jobs = [] for TSD in TSDs_of_interest: - missing_bands = self.ImageChipBuffer.getMissingBands(TSD, required_bands[TSD.sensor]) - if len(missing_bands) == 0: - self.ua_showPxCoordinate_addChips(None, TSD=TSD) - else: - missing.add((TSD, tuple(missing_bands))) + for i, r in enumerate(LUT_RENDERER[TSD.sensor]): + jobs.append(RenderJob(TSD, r.clone(), destinationId=i)) + #oder jobs by distance to DOI + jobs = sorted(jobs, key = lambda j: abs(j.TSD.date - doiTSD.date)) - missing =list(missing) - if len(missing) > 0: - missing = sorted(missing, key=lambda d: abs(centerDate - d[0].getDate())) + #todo: recycling to save loading time + self.PIXMAPS.loadSubsets(jobs) - self.TS.getSpatialChips_parallel(bbWkt, srsWkt, TSD_band_list=missing) + def showSubset(self, renderJob, pixmap): + assert isinstance(renderJob, RenderJob) + chipLabel = self.CHIPWIDGETS[renderJob.TSD][renderJob.destinationId] + chipLabel.setPixmap(pixmap) + chipLabel.setFixedSize(pixmap.size()) + chipLabel.update() + s = "" def ua_collect_date(self, ICL, event): - if self.dlg.rb_labeling_activate.isChecked(): - txt = self.dlg.tb_labeling_text.toPlainText() + if self.ui.rb_labeling_activate.isChecked(): + txt = self.ui.tb_labeling_text.toPlainText() reg = re.compile('\d{4}-\d{2}-\d{2}', re.I | re.MULTILINE) dates = set([np.datetime64(m) for m in reg.findall(txt)]) doi = ICL.TSD.getDate() @@ -1073,64 +1160,15 @@ class TimeSeriesViewer: dates = sorted(list(dates)) txt = ' '.join([d.astype(str) for d in dates]) - self.dlg.tb_labeling_text.setText(txt) - - - def ua_showPxCoordinate_addChips(self, results, TSD=None): - - if results is not None: - TSD, chipData = results - self.ImageChipBuffer.addDataCube(TSD, chipData) - - if TSD not in self.CHIPWIDGETS.keys(): - six.print_('TSD {} does not exist in CHIPBUFFER'.format(TSD), file=sys.stderr) - else: - for imgChipLabel, bandView in zip(self.CHIPWIDGETS[TSD], self.BAND_VIEWS): - #imgView.clear() - #imageLabel.setScaledContents(True) - - #rgb = self.ImageChipBuffer.getChipRGB(TSD, bandView) - array = self.ImageChipBuffer.getChipArray(TSD, bandView, mode = 'bgr') - qimg = pg.makeQImage(array, copy=True, transpose=False) - - #rgb2 = rgb.transpose([1,2,0]).copy('C') - #qImg = qimage2ndarray.array2qimage(rgb2) - #img = QImage(rgb2.data, nl, ns, QImage.Format_RGB888) - - pxMap = QPixmap.fromImage(qimg).scaled(imgChipLabel.size(), Qt.KeepAspectRatio) - imgChipLabel.setPixmap(pxMap) - imgChipLabel.update() - #imgView.setPixmap(pxMap) - #imageLabel.update() - #imgView.adjustSize() - #pxmap = QPixmap.fromImage(qimg) - # - - """ - pxmapitem = QGraphicsPixmapItem(pxmap) - if imgChipLabel.scene() is None: - imgChipLabel.setScene(QGraphicsScene()) - else: - imgChipLabel.scene().clear() + self.ui.tb_labeling_text.setText(txt) - scene = imgChipLabel.scene() - scene.addItem(pxmapitem) - - imgChipLabel.fitInView(scene.sceneRect(), Qt.KeepAspectRatio) - """ - - pass - self.ICP.layout().update() - self.dlg.scrollArea_imageChip_content.update() - s = "" - - pass def clearLayoutWidgets(self, L): if L is not None: while L.count(): w = L.takeAt(0) - w.widget().deleteLater() + if w.widget(): + w.widget().deleteLater() #if w is not None: # w.widget().deleteLater() QApplication.processEvents() @@ -1140,7 +1178,7 @@ class TimeSeriesViewer: files = QFileDialog.getOpenFileNames() if files: - M = self.dlg.tableView_TimeSeries.model() + M = self.ui.tableView_TimeSeries.model() M.beginResetModel() self.TS.addFiles(files) M.endResetModel() @@ -1156,7 +1194,7 @@ class TimeSeriesViewer: l = len(files) if l > 0: - M = self.dlg.tableView_TimeSeries.model() + M = self.ui.tableView_TimeSeries.model() M.beginResetModel() self.TS.addMasks(files, raise_errors=False) M.endResetModel() @@ -1165,34 +1203,38 @@ class TimeSeriesViewer: - def ua_addBandView(self, band_recommendation = [3, 2, 1]): - bandView = BandView(self.TS, recommended_bands=band_recommendation) + def ua_addBandView(self): + bandView = BandView(self.TS) #bandView.removeView.connect(self.ua_removeBandView) self.BAND_VIEWS.append(bandView) self.refreshBandViews() def refreshBandViews(self): - if len(self.BAND_VIEWS) == 0 and len(self.TS) > 0: - self.ua_addBandView(band_recommendation=[3, 2, 1]) - self.ua_addBandView(band_recommendation=[4, 5, 3]) - + self.ua_addBandView() # add two bandviews by default + self.ua_addBandView() self.clearLayoutWidgets(self.BVP) for i, BV in enumerate(self.BAND_VIEWS): W = QWidget() + hl = QHBoxLayout() + hl.setSpacing(2) + hl.setMargin(0) + + textLabel = VerticalLabel('View {}'.format(i+1)) - textLabel = QLabel('View {}'.format(i+1)) + #textLabel = QLabel('View {}'.format(i+1)) textLabel.setToolTip('') textLabel.setSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed) hl.addWidget(textLabel) for S in self.TS.Sensors.keys(): - w = BV.getWidget(S) - + w = BV.getWidget(S).ui + if i > 0: + w.setTitle(None) #show sensor name only on top w.setMaximumSize(w.size()) #w.setMinimumSize(w.size()) w.setSizePolicy(QSizePolicy.Fixed,QSizePolicy.MinimumExpanding) @@ -1200,9 +1242,10 @@ class TimeSeriesViewer: hl.addWidget(w) s = "" - hl.addItem(QSpacerItem(20,20,QSizePolicy.Expanding,QSizePolicy.Minimum)) + hl.addItem(QSpacerItem(1,1)) W.setLayout(hl) self.BVP.addWidget(W) + self.BVP.addItem(QSpacerItem(1, 1)) self.check_enabled() @@ -1214,7 +1257,7 @@ class TimeSeriesViewer: def ua_clear_TS(self): #remove views - M = self.dlg.tableView_TimeSeries.model() + M = self.ui.tableView_TimeSeries.model() M.beginResetModel() self.TS.clear() M.endResetModel() @@ -1225,7 +1268,7 @@ class TimeSeriesViewer: TSDs = self.getSelectedTSDs() assert isinstance(TSDs,list) - M = self.dlg.tableView_TimeSeries.model() + M = self.ui.tableView_TimeSeries.model() M.beginResetModel() self.TS.removeDates(TSDs) M.endResetModel() @@ -1234,7 +1277,7 @@ class TimeSeriesViewer: def getSelectedTSDs(self): - TV = self.dlg.tableView_TimeSeries + TV = self.ui.tableView_TimeSeries TVM = TV.model() return [TVM.getTimeSeriesDatumFromIndex(idx) for idx in TV.selectionModel().selectedRows()] diff --git a/timeseriesviewer/sandbox.py b/timeseriesviewer/sandbox.py deleted file mode 100644 index ce923bae5373e9bdf83655146439742a3ee20435..0000000000000000000000000000000000000000 --- a/timeseriesviewer/sandbox.py +++ /dev/null @@ -1,117 +0,0 @@ -from __future__ import absolute_import -import six, sys, os, gc, re, collections, site, inspect, time -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 timeseriesviewer import DIR_EXAMPLES, jp, dprint - -class HiddenCanvas(QgsMapCanvas): - - - def __init__(self): - super(HiddenCanvas,self).__init__(None, None) - - - -def getLoadedLayer(layer): - print('LAYER READY') - print(layer) - -def getLoadedLayers(layers): - for lyr in layers: - getLoadedLayer(lyr) - - -def loadingDone(): - print('DONE') - - -from itertools import izip_longest - -def grouper(iterable, n, fillvalue=None): - args = [iter(iterable)] * n - return izip_longest(*args, fillvalue=fillvalue) - -def waitForThreads(): - print "Waiting for thread pool" - pool.waitForDone() -if __name__ == '__main__': - import site, sys - #add site-packages to sys.path as done by enmapboxplugin.py - - from timeseriesviewer import DIR_SITE_PACKAGES - site.addsitedir(DIR_SITE_PACKAGES) - - #prepare QGIS environment - if sys.platform == 'darwin': - PATH_QGS = r'/Applications/QGIS.app/Contents/MacOS' - #os.environ['GDAL_DATA'] = r'/usr/local/Cellar/gdal/1.11.3_1/share' - else: - # assume OSGeo4W startup - PATH_QGS = os.environ['QGIS_PREFIX_PATH'] - assert os.path.exists(PATH_QGS) - - qgsApp = QgsApplication([], True) - QApplication.addLibraryPath(r'/Applications/QGIS.app/Contents/PlugIns') - QApplication.addLibraryPath(r'/Applications/QGIS.app/Contents/PlugIns/qgis') - qgsApp.setPrefixPath(PATH_QGS, True) - qgsApp.initQgis() - - from timeseriesviewer import file_search, PATH_EXAMPLE_TIMESERIES - from timeseriesviewer.timeseries import TimeSeries, TimeSeriesDatum - pathTestTS = jp(DIR_EXAMPLES, 'ExampleTimeSeries.csv') - TS = TimeSeries() - if False or not os.path.exists(pathTestTS): - paths = file_search(jp(DIR_EXAMPLES, 'Images'), '*.bsq') - - TS.addFiles(paths) - TS.saveToFile(PATH_EXAMPLE_TIMESERIES) - else: - TS.loadFromFile(PATH_EXAMPLE_TIMESERIES) - - drvMEM = gdal.GetDriverByName('MEM') - ds = gdal.Open(TS.data[0].pathImg) - ds = drvMEM.CreateCopy('',ds) - - lyr = QgsRasterLayer(paths[0]) - finalLayerList = [] - def callback(result): - assert isinstance(result, QgsRasterLayer) - print(result) - finalLayerList.append(result) - s = "" - cnt = 0 - def callbackFin(): - cnt-=1 - - #run - #LL = LayerLoaderR(paths) - #LL.signales.sigLayerLoaded.connect(callback) - #r = LL.run() - - import numpy as np - #pool = QThreadPool() - pool = QThreadPool.globalInstance() - pool.setMaxThreadCount(4) - for files in grouper(paths, 3): - - LL = TSDLoader([f for f in files if f is not None]) - cnt += 1 - LL.signals.sigRasterLayerLoaded.connect(callback) - LL.signals.sigFinished.connect(callbackFin) - pool.start(LL) - - t0 = np.datetime64('now') - pool.waitForDone() - - - #close QGIS - - qgsApp.aboutToQuit.connect(waitForThreads) - qgsApp.exec_() - qgsApp.exitQgis() diff --git a/timeseriesviewer/timeseries.py b/timeseriesviewer/timeseries.py index ff640acdcdbf080905464e4e690ba6ca95be507a..a2f56c9960df7f3c2a3f88429f59d58bc5c2b9a5 100644 --- a/timeseriesviewer/timeseries.py +++ b/timeseriesviewer/timeseries.py @@ -8,6 +8,7 @@ from qgis.core import * from qgis.gui import * from PyQt4.QtGui import * from PyQt4.QtCore import * +from PyQt4.QtXml import * import numpy as np @@ -24,7 +25,7 @@ def transformGeometry(geom, crsSrc, crsDst, trans=None): -class SensorInstrument(object): +class SensorInstrument(QObject): INSTRUMENTS = dict() INSTRUMENTS = {(6, 30., 30.): 'Landsat Legacy' \ @@ -36,49 +37,51 @@ class SensorInstrument(object): , (5, 5., 5.): 'RE 5m' \ } - @staticmethod - def fromRasterLayer(lyr): - - assert isinstance(lyr, QgsRasterLayer) - nb = lyr.bandCount() - sx = lyr.rasterUnitsPerPixelX() - sy = lyr.rasterUnitsPerPixelY() - - bandNames = [lyr.bandName(i) for i in range(1, nb+1)] - - return SensorInstrument(nb, sx, sy, - band_names=bandNames, - wavelengths=None, - sensor_name=None) - + """ def fromGDALDataSet(self, ds): assert isinstance(ds, gdal.Dataset) nb = ds.RasterCount - + """ """ Describes a Sensor Configuration """ - def __init__(self,nb, px_size_x,px_size_y, band_names=None, wavelengths=None, sensor_name=None): - assert nb >= 1 + def __init__(self, refLyr, sensor_name=None): + super(SensorInstrument, self).__init__() + assert isinstance(refLyr, QgsRasterLayer) + #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 self.TS = None - self.nb = nb + px_size_x = refLyr.rasterUnitsPerPixelX() + px_size_y = refLyr.rasterUnitsPerPixelY() + 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 - if band_names is not None: - assert len(band_names) == nb - else: - band_names = ['Band {}'.format(b+1) for b in range(nb)] - - self.bandNames = band_names - + wavelengths = None + #todo: find wavelength if wavelengths is not None: - assert len(wavelengths) == nb + assert len(wavelengths) == self.nb self.wavelengths = wavelengths @@ -153,16 +156,215 @@ class TSDLoader(QRunnable): #return lyrs +class TimeSeriesDatum(QObject): + """ + Collects all data sets related to one sensor + """ + + def __init__(self, pathImg, pathMsk=None): + super(TimeSeriesDatum,self).__init__() + self.pathImg = pathImg + self.pathMsk = None + + self.lyrImg = QgsRasterLayer(pathImg, os.path.basename(pathImg), False) + 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 + + + def getBoundingBox(self, srs=None): + + bbox = self.lyrImg.extent() + if srs: + assert isinstance(srs, QgsCoordinateReferenceSystem) + bbox = transformGeometry(bbox, self.crs, srs) + return bbox + + + 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))) + + def __eq__(self, other): + return self.date == other.date and self.sensor == other.sensor + + def __hash__(self): + return hash((self.date,self.sensor.sensorName)) + + class TimeSeries(QObject): - datumAdded = pyqtSignal(name='datumAdded') + datumAdded = pyqtSignal(TimeSeriesDatum) #fire when a new sensor configuration is added sensorAdded = pyqtSignal(SensorInstrument, name='sensorAdded') changed = pyqtSignal() - progress = pyqtSignal(int,int,int, name='progress') - chipLoaded = pyqtSignal(object, name='chiploaded') closed = pyqtSignal() error = pyqtSignal(object) @@ -235,45 +437,19 @@ class TimeSeries(QObject): f.writelines(lines) - def getSRS(self): - if len(self.data) == 0: - return 0 - else: - TSD = self.data[0] - return TSD.getSpatialReference() - - def getWKT(self): - srs = self.getSRS() - return srs.ExportToWkt() - - def getSceneCenter(self, srs=None): - - if srs is None: - srs = self.getSRS() - - bbs = list() - for S, TSDs in self.Sensors.items(): - x = [] - y = [] - for TSD in TSDs: - bb = TSD.getBoundingBox(srs) - x.extend([c[0] for c in bb]) - y.extend([c[1] for c in bb]) - - return None - pass def getMaxExtent(self, srs=None): - + if len(self.data) == 0: + return None if srs is None: - srs = self.getSRS() + srs = self.data[0].crs() bb = None for TSD in self.data: if bb is None: - bb = TSD.getBoundingBox(srs) + bb = TSD.getBoundingBox(srs=srs) else: - bb.unionRect(TSD.getBoundingBox(srs)) + bb.unionRect(TSD.getBoundingBox(srs=srs)) return bb @@ -438,7 +614,7 @@ class TimeSeries(QObject): #insert sorted bisect.insort(self.data, TSD) #self.data[TSD] = TSD - + self.datumAdded.emit(TSD) if sensorAdded: self.sensorAdded.emit(TSD.sensor) @@ -466,15 +642,20 @@ class TimeSeries(QObject): self.progress.emit(0,i+1,l) self.progress.emit(0,0,1) - self.datumAdded.emit() self.changed.emit() + def __len__(self): + return len(self.data) + def __iter__(self): + return iter(self.data) + def __getitem__(self, key): + return self.data[key] - def __len__(self): - return len(self.data) + def __contains__(self, item): + return item in self.data def __repr__(self): info = [] @@ -509,206 +690,6 @@ def getImageDate2(lyr): return date -class TimeSeriesDatum(QObject): - """ - Collects all data sets related to one sensor - """ - - def __init__(self, pathImg, pathMsk=None): - self.pathImg = pathImg - self.pathMsk = None - - self.lyrImg = QgsRasterLayer(pathImg, os.path.basename(pathImg), False) - self.crs = self.lyrImg.dataProvider().crs() - self.sensor = SensorInstrument.fromRasterLayer(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 - srs = osr.SpatialReference() - srs.ImportFromWkt(self.srs_wkt) - return srs - - - def getBoundingBox(self, srs=None): - - bbox = self.lyrImg.extent() - if srs: - assert isinstance(srs, QgsCoordinateReferenceSystem) - bbox = transformGeometry(bbox, self.crs, srs) - return bbox - - - 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))) - - def __eq__(self, other): - return self.date == other.date and self.sensor == other.sensor - - def __hash__(self): - return hash((self.date,self.sensor.sensorName)) - def PFunc_TimeSeries_getSpatialChip(TSD, bbWkt, srsWkt , bands=[4,5,3]): diff --git a/timeseriesviewer/ui/imagechipviewsettings.ui b/timeseriesviewer/ui/imagechipviewsettings.ui index d61a51e1bc972fd518bcf8dcf8f7316a7e0dc21c..eec25cf836bf359d53334e1fba7ac9e275b598fd 100644 --- a/timeseriesviewer/ui/imagechipviewsettings.ui +++ b/timeseriesviewer/ui/imagechipviewsettings.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>410</width> - <height>170</height> + <width>332</width> + <height>135</height> </rect> </property> <property name="sizePolicy"> @@ -16,11 +16,16 @@ <verstretch>0</verstretch> </sizepolicy> </property> + <property name="font"> + <font> + <pointsize>13</pointsize> + </font> + </property> <property name="windowTitle"> <string>GroupBox</string> </property> <property name="title"> - <string>Sensor</string> + <string/> </property> <property name="flat"> <bool>false</bool> @@ -29,40 +34,35 @@ <bool>false</bool> </property> <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>0</number> + </property> <property name="margin"> <number>0</number> </property> <item> - <layout class="QGridLayout" name="bandSelectionGridLayout"> - <item row="0" column="2"> - <widget class="QLabel" name="labelMax"> - <property name="text"> - <string>Max</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QLabel" name="labelBand"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>Band</string> + <layout class="QGridLayout" name="bandSelectionGridLayout" rowstretch="0,0,0,0,0" columnstretch="0,0,0,0"> + <property name="horizontalSpacing"> + <number>5</number> + </property> + <property name="verticalSpacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <item row="2" column="1"> + <widget class="QSlider" name="sliderGreen"> + <property name="styleSheet"> + <string notr="true"/> </property> - </widget> - </item> - <item row="2" column="2"> - <widget class="QLineEdit" name="tbGreenMin"> - <property name="text"> - <string>0</string> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> </widget> </item> - <item row="2" column="1"> - <widget class="QSlider" name="sliderGreen"> + <item row="3" column="1"> + <widget class="QSlider" name="sliderBlue"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> @@ -100,37 +100,40 @@ </property> </widget> </item> - <item row="3" column="0"> - <widget class="QLabel" name="labelBlue"> + <item row="0" column="1"> + <widget class="QLabel" name="labelBands"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="text"> - <string>B</string> + <string/> </property> </widget> </item> - <item row="3" column="2"> - <widget class="QLineEdit" name="tbBlueMin"> + <item row="2" column="2"> + <widget class="QLineEdit" name="tbGreenMin"> <property name="text"> <string>0</string> </property> </widget> </item> - <item row="0" column="3"> - <widget class="QLabel" name="labelMin"> + <item row="1" column="0"> + <widget class="QLabel" name="labelRed"> + <property name="styleSheet"> + <string notr="true"/> + </property> <property name="text"> - <string>Min</string> + <string>R</string> </property> </widget> </item> - <item row="3" column="3"> - <widget class="QLineEdit" name="tbBlueMax"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> + <item row="0" column="3"> + <widget class="QLabel" name="labelMin"> <property name="text"> - <string>5000</string> + <string>Max</string> </property> </widget> </item> @@ -144,15 +147,62 @@ </property> <property name="minimumSize"> <size> - <width>100</width> + <width>130</width> <height>0</height> </size> </property> + <property name="styleSheet"> + <string notr="true"/> + </property> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> + <item row="2" column="0"> + <widget class="QLabel" name="labelGreen"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="text"> + <string>G</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="labelMax"> + <property name="text"> + <string>Min</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="labelBlue"> + <property name="text"> + <string>B</string> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QLineEdit" name="tbBlueMin"> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item row="3" column="3"> + <widget class="QLineEdit" name="tbBlueMax"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>5000</string> + </property> + </widget> + </item> <item row="1" column="2"> <widget class="QLineEdit" name="tbRedMin"> <property name="minimumSize"> @@ -166,27 +216,19 @@ </property> </widget> </item> - <item row="3" column="1"> - <widget class="QSlider" name="sliderBlue"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="labelGreen"> + <item row="4" column="0" colspan="2"> + <widget class="QLabel" name="label"> <property name="text"> - <string>G</string> + <string>Stretch</string> </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="labelRed"> - <property name="text"> - <string>R</string> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> </property> </widget> </item> + <item row="4" column="2" colspan="2"> + <widget class="QComboBox" name="comboBoxContrastEnhancement"/> + </item> </layout> </item> </layout> diff --git a/timeseriesviewer/ui/timseriesviewer.ui b/timeseriesviewer/ui/timseriesviewer.ui index d1895962f7c78c7bc6d1a8f4d8871a01709fda37..23b2ff663e4733bdfc977a29831b0748f4f7d005 100644 --- a/timeseriesviewer/ui/timseriesviewer.ui +++ b/timeseriesviewer/ui/timseriesviewer.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>1137</width> - <height>993</height> + <height>979</height> </rect> </property> <property name="windowTitle"> @@ -288,6 +288,21 @@ <string>Viewer</string> </attribute> <layout class="QVBoxLayout" name="verticalLayout_9"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>1</number> + </property> + <property name="topMargin"> + <number>1</number> + </property> + <property name="rightMargin"> + <number>1</number> + </property> + <property name="bottomMargin"> + <number>5</number> + </property> <item> <widget class="QPushButton" name="btn_showPxCoordinate"> <property name="toolTip"> @@ -310,7 +325,7 @@ </size> </property> <property name="title"> - <string>Image subset</string> + <string>Subset</string> </property> <layout class="QFormLayout" name="formLayout_3"> <property name="fieldGrowthPolicy"> @@ -629,7 +644,7 @@ </size> </property> <property name="title"> - <string>Chip Rendering</string> + <string>Rendering</string> </property> <layout class="QFormLayout" name="formLayout"> <property name="fieldGrowthPolicy"> @@ -641,7 +656,7 @@ <item row="0" column="0" colspan="2"> <widget class="QRadioButton" name="rb_showEntireTS"> <property name="text"> - <string>Time Series</string> + <string>Entire Time Series</string> </property> <property name="checked"> <bool>true</bool> @@ -760,24 +775,7 @@ </layout> </widget> </item> - <item row="3" column="0"> - <widget class="QLabel" name="labelDOI"> - <property name="text"> - <string>DOI</string> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QSlider" name="sliderDOI"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="tickPosition"> - <enum>QSlider::TicksAbove</enum> - </property> - </widget> - </item> - <item row="4" column="0" colspan="2"> + <item row="3" column="0" colspan="2"> <widget class="QFrame" name="frame_5"> <property name="frameShape"> <enum>QFrame::NoFrame</enum> @@ -797,7 +795,7 @@ </widget> </item> <item> - <widget class="QSpinBox" name="spinBox_chipsize_max"> + <widget class="QSpinBox" name="spinBoxMaxPixmapSize"> <property name="minimumSize"> <size> <width>50</width> @@ -857,6 +855,47 @@ </layout> </widget> </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>50</height> + </size> + </property> + <property name="title"> + <string>Navigation</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <item> + <widget class="QLabel" name="labelDOI"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>DOI</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QSlider" name="sliderDOI"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksAbove</enum> + </property> + </widget> + </item> + </layout> + </widget> + </item> </layout> </widget> <widget class="QWidget" name="tab_4"> diff --git a/timeseriesviewer/ui/widgets.py b/timeseriesviewer/ui/widgets.py index 60f4b4135e9248e3f6f1dbba43f9cabc0f9160bf..98e8bf173b7dcb100be98ef5f4baabb009d6b18b 100644 --- a/timeseriesviewer/ui/widgets.py +++ b/timeseriesviewer/ui/widgets.py @@ -19,7 +19,8 @@ ''' import os - +from qgis.core import * +from qgis.gui import * from PyQt4 import uic from PyQt4.QtCore import * from PyQt4.QtGui import * @@ -46,118 +47,122 @@ class TimeSeriesViewerUI(QMainWindow, # #widgets-and-dialogs-with-auto-connect self.setupUi(self) -class ImageChipViewSettings(QGroupBox, - loadUIFormClass(PATH_IMAGECHIPVIEWSETTINGS_UI)): - - #define signals - removeView = pyqtSignal() +class ImageChipViewSettingsUI(QGroupBox, + loadUIFormClass(PATH_IMAGECHIPVIEWSETTINGS_UI)): def __init__(self, sensor, parent=None): """Constructor.""" - super(ImageChipViewSettings, self).__init__(parent) + super(ImageChipViewSettingsUI, self).__init__(parent) # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-auto-connect + self.setupUi(self) - from timeseriesviewer.timeseries import SensorInstrument - assert isinstance(sensor, SensorInstrument) - self.sensor = sensor - self.setTitle(sensor.sensorName) +class ImageChipViewSettings(QObject): + + #define signals + + removeView = pyqtSignal() + + def __init__(self, sensor, parent=None): + """Constructor.""" + super(ImageChipViewSettings, self).__init__(parent) + + self.ui = ImageChipViewSettingsUI(sensor, parent) + self.ui.create() + + + self.ui.setTitle(sensor.sensorName) + self.ui.bandNames = sensor.bandNames + self.minValues = [self.ui.tbRedMin, self.ui.tbGreenMin, self.ui.tbBlueMin] + self.maxValues = [self.ui.tbRedMax, self.ui.tbGreenMax, self.ui.tbBlueMax] + self.sliders = [self.ui.sliderRed, self.ui.sliderGreen, self.ui.sliderBlue] - self.minValues = [self.tbRedMin, self.tbGreenMin, self.tbBlueMin] - self.maxValues = [self.tbRedMax, self.tbGreenMax, self.tbBlueMax] - self.sliders = [self.sliderRed, self.sliderGreen, self.sliderBlue] for tb in self.minValues + self.maxValues: tb.setValidator(QDoubleValidator()) for sl in self.sliders: sl.setMinimum(1) - sl.setMaximum(self.sensor.nb) + sl.setMaximum(sensor.nb) + sl.valueChanged.connect(self.bandSelectionChanged) - def ua_setMask(self, state): + self.ceAlgs = [("No enhancement", QgsContrastEnhancement.NoEnhancement), + ("Stretch to MinMax", QgsContrastEnhancement.StretchToMinimumMaximum), + ("Stretch and clip to MinMax",QgsContrastEnhancement.StretchAndClipToMinimumMaximum), + ("Clip to MinMax", QgsContrastEnhancement.ClipToMinimumMaximum)] + for item in self.ceAlgs: + self.ui.comboBoxContrastEnhancement.addItem(item[0], item[1]) - useMask = state != 0 - for w in [self.bt_color, self.label_maskexpression, self.tb_maskexpression]: - w.setEnabled(useMask) - def ua_setMaskColor(self, color): - if color is None: - color = QColorDialog.getColor() - - if color is not None: - self.maskcolor = color - r = color.red() - g = color.green() - b = color.blue() - style = "background:rgb({},{},{})".format(r,g,b) - self.bt_color.setStyleSheet(style) - self.bt_color.update() + from timeseriesviewer.timeseries import SensorInstrument + assert isinstance(sensor, SensorInstrument) + self.sensor = sensor - def getMaskColor(self): - return (self.maskcolor.red(), self.maskcolor.green(), self.maskcolor.blue()) + lyr = QgsRasterLayer(self.sensor.refUri) + self.setLayerRenderer(lyr.renderer()) - def useMaskValues(self): - return self.cb_useMask.isChecked() + #provide default min max + s = "" - def setBands(self,bands): - assert len(bands) == 3 - for b in bands: - assert type(b) is int and b > 0 - assert b <= len(self.sensor.band_names), 'TimeSeries is not initializes/has no bands to show' - self.cb_r.setCurrentIndex(bands[0]-1) - self.cb_g.setCurrentIndex(bands[1]-1) - self.cb_b.setCurrentIndex(bands[2]-1) + def bandSelectionChanged(self, *args): + text = 'RGB {}-{}-{}'.format(self.ui.sliderRed.value(), + self.ui.sliderGreen.value(), + self.ui.sliderBlue.value()) + self.ui.labelBands.setText(text) s = "" - pass def setLayerRenderer(self, renderer): + ui = self.ui assert isinstance(renderer, QgsRasterRenderer) if isinstance(renderer, QgsMultiBandColorRenderer): - s = "" + ui.sliderRed.setValue(renderer.redBand()) + ui.sliderGreen.setValue(renderer.greenBand()) + ui.sliderBlue.setValue(renderer.blueBand()) - bands, ranges = bands_and_ranges - assert len(bands) == 3 - assert len(ranges) == 3 - for range in ranges: - assert len(range) == 2 and range[0] <= range[1] + ceRed = renderer.redContrastEnhancement() + ceGreen = renderer.greenContrastEnhancement() + ceBlue = renderer.blueContrastEnhancement() - #copy values only if all bands fit to this sensor - for b in bands: - if b > self.sensor.nb: - return + algs = [i[1] for i in self.ceAlgs] + ui.comboBoxContrastEnhancement.setCurrentIndex(algs.index(ceRed.contrastEnhancementAlgorithm())) - self.cb_r.setCurrentIndex(bands[0]-1) - self.cb_g.setCurrentIndex(bands[1]-1) - self.cb_b.setCurrentIndex(bands[2]-1) - self.tb_range_r_min.setText(str(ranges[0][0])) - self.tb_range_g_min.setText(str(ranges[1][0])) - self.tb_range_b_min.setText(str(ranges[2][0])) - self.tb_range_r_max.setText(str(ranges[0][1])) - self.tb_range_g_max.setText(str(ranges[1][1])) - self.tb_range_b_max.setText(str(ranges[2][1])) + def layerRenderer(self): + ui = self.ui + r = QgsMultiBandColorRenderer(None, + ui.sliderRed.value(), ui.sliderGreen.value(), ui.sliderBlue.value()) + i = self.ui.comboBoxContrastEnhancement.currentIndex() + alg = self.ui.comboBoxContrastEnhancement.itemData(i) - def getRGBSettings(self): - bands = [self.cb_r.currentIndex()+1, \ - self.cb_g.currentIndex()+1, \ - self.cb_b.currentIndex()+1] + if alg == QgsContrastEnhancement.NoEnhancement: + r.setRedContrastEnhancement(None) + r.setGreenContrastEnhancement(None) + r.setBlueContrastEnhancement(None) + else: + rgbEnhancements = [] + for i in range(3): + e = QgsContrastEnhancement(self.sensor.bandDataType) + e.setMinimumValue(float(self.ui.minValues[i].text())) + e.setMaximumValue(float(self.ui.maxValues[i].text())) + e.setContrastEnhancementAlgorithm(alg) + rgbEnhancements.append(e) + r.setRedContrastEnhancement(rgbEnhancements[0]) + r.setGreenContrastEnhancement(rgbEnhancements[1]) + r.setBlueContrastEnhancement(rgbEnhancements[2]) + return r - range_r = [float(self.tb_range_r_min.text()), float(self.tb_range_r_max.text())] - range_g = [float(self.tb_range_g_min.text()), float(self.tb_range_g_max.text())] - range_b = [float(self.tb_range_b_min.text()), float(self.tb_range_b_max.text())] - ranges = (range_r, range_g, range_b) - return bands, ranges + s = "" def contextMenuEvent(self, event): menu = QMenu() @@ -178,112 +183,3 @@ class ImageChipViewSettings(QGroupBox, menu.exec_(event.globalPos()) - -class BandViewSettings(QGroupBox, - loadUIFormClass(PATH_BANDVIEWSETTINGS_UI)): - - #define signals - - removeView = pyqtSignal() - - def __init__(self, SensorConfiguration, parent=None): - """Constructor.""" - super(BandViewSettings, self).__init__(parent) - # Set up the user interface from Designer. - # After setupUI you can access any designer object by doing - # self.<objectname>, and you can use autoconnect slots - see - # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html - # #widgets-and-dialogs-with-auto-connect - self.setupUi(self) - - - self.SensorConfiguration = SensorConfiguration - self.setTitle(SensorConfiguration.sensor_name) - - self.tb_range_min.setValidator(QDoubleValidator()) - self.tb_range_max.setValidator(QDoubleValidator()) - - self._initBands(self.SensorConfiguration.band_names) - - def ua_setMask(self, state): - raise NotImplementedError() - useMask = state != 0 - for w in [self.bt_color, self.label_maskexpression, self.tb_maskexpression]: - w.setEnabled(useMask) - - def ua_setMaskColor(self, color): - raise NotImplementedError() - if color is None: - color = QColorDialog.getColor() - - if color is not None: - self.maskcolor = color - r = color.red() - g = color.green() - b = color.blue() - style = "background:rgb({},{},{})".format(r,g,b) - self.bt_color.setStyleSheet(style) - self.bt_color.update() - - def getMaskColor(self): - raise NotImplementedError() - return (self.maskcolor.red(), self.maskcolor.green(), self.maskcolor.blue()) - - def useMaskValues(self): - return self.cb_useMask.isChecked() - - def _initBands(self, band_names): - cb_R = self.cb_r - cb_G = self.cb_g - cb_B = self.cb_b - - for i, bandname in enumerate(band_names): - cb_R.addItem(bandname, i+1) - cb_G.addItem(bandname, i+1) - cb_B.addItem(bandname, i+1) - - if len(self.SensorConfiguration.band_names) >= 3: - cb_R.setCurrentIndex(2) - cb_G.setCurrentIndex(1) - cb_B.setCurrentIndex(0) - - - def setBands(self,bands): - assert len(bands) == 3 - for b in bands: - assert type(b) is int and b > 0 - assert b <= len(self.SensorConfiguration.band_names), 'TimeSeries is not initializes/has no bands to show' - self.cb_r.setCurrentIndex(bands[0]-1) - self.cb_g.setCurrentIndex(bands[1]-1) - self.cb_b.setCurrentIndex(bands[2]-1) - - s = "" - pass - - def getRGBSettings(self): - bands = [self.cb_r.currentIndex()+1, \ - self.cb_g.currentIndex()+1, \ - self.cb_b.currentIndex()+1] - - range = [float(self.tb_range_min.text()), float(self.tb_range_max.text())] - ranges = (range, range, range) - - return bands, ranges - - -if __name__ == '__main__': - - import PyQt4.Qt - - app=PyQt4.Qt.QApplication([]) - W = QDialog() - W.setLayout(QHBoxLayout()) - L = W.layout() - import sensecarbon_tsv - S = sensecarbon_tsv.SensorConfiguration(6,30,30) - w = BandViewSettings(S) - L.addWidget(w) - W.show() - sys.exit(app.exec_()) - - print('Done') \ No newline at end of file diff --git a/timeseriesviewerplugin.py b/timeseriesviewerplugin.py index 205cfaf394f7b761c436f84b64bd1c0aee383c08..399e527fab22c1c03c358d80596f8bcd5f10056b 100644 --- a/timeseriesviewerplugin.py +++ b/timeseriesviewerplugin.py @@ -67,7 +67,7 @@ class TSVPlugin: self.iface.removeToolBarIcon(action) if isinstance(self.tsv, TimeSeriesViewer): - self.tsv.dlg.close() + self.tsv.ui.close() self.tsv = None