From ed849ee7922dbb94031f596d0d751263716386c4 Mon Sep 17 00:00:00 2001
From: Benjamin Jakimow <no.email>
Date: Thu, 22 Dec 2016 10:25:41 +0100
Subject: [PATCH] refactoring

minor UI changes
---
 timeseriesviewer/main.py                     | 397 ++++++++++++--
 timeseriesviewer/tests.py                    |   2 +-
 timeseriesviewer/timeseries.py               |  22 +-
 timeseriesviewer/ui/bandview.ui              | 161 +++---
 timeseriesviewer/ui/imagechipviewsettings.ui |  16 +-
 timeseriesviewer/ui/timseriesviewer.ui       | 514 +++++++++----------
 timeseriesviewer/ui/widgets.py               | 169 +++++-
 7 files changed, 880 insertions(+), 401 deletions(-)

diff --git a/timeseriesviewer/main.py b/timeseriesviewer/main.py
index 09d5184c..d6842424 100644
--- a/timeseriesviewer/main.py
+++ b/timeseriesviewer/main.py
@@ -47,11 +47,10 @@ except:
 
 import numpy as np
 
-import multiprocessing, site
+import sys, bisect, multiprocessing, site
 from PyQt4.QtCore import *
 from PyQt4.QtGui import *
 from PyQt4.uic.Compiler.qtproxies import QtGui, QtCore
-import sys
 import code
 import codecs
 
@@ -218,13 +217,147 @@ class TimeSeriesItemModel(QAbstractItemModel):
         return 1
 
 
+class TimeSeriesDatumViewManager(QObject):
+
+    def __init__(self, timeSeriesViewer):
+        assert isinstance(timeSeriesViewer, TimeSeriesViewer)
+        super(TimeSeriesDatumViewManager, self).__init__()
+
+        self.TSV = timeSeriesViewer
+
+        self.TSDViews = list()
+        self.bandViewMananger = self.TSV.bandViewManager
+        self.bandViewMananger.sigBandViewAdded.connect(self.addBandView)
+        self.bandViewMananger.sigBandViewRemoved.connect(self.removeBandView)
+        self.setExtent(self.TSV.TS.getMaxExtent())
+        self.setMaxTSDViews()
+        self.setTimeSeries(self.TSV.TS)
+        self.L = self.TSV.ui.scrollAreaSubsetContent.layout()
+        self.setSubsetSize(QSize(100,50))
+
+    def setSubsetSize(self, size):
+        assert isinstance(size, QSize)
+        self.subsetSize = size
+        for tsdv in self.TSDViews:
+            tsdv.setSubsetSize(size)
+        self.adjustScrollArea()
+
+
+    def adjustScrollArea(self):
+
+        m = self.L.contentsMargins()
+        n = len(self.TSDViews)
+        if n > 0:
+            refTSDView = self.TSDViews[0]
+            size = refTSDView.ui.size()
+
+            w = n * size.width() + (n-1) * (m.left()+ m.right())
+            h = max([refTSDView.ui.minimumHeight() + m.top() + m.bottom(),
+                     self.TSV.ui.scrollAreaSubsets.height()-25])
+
+            self.L.parentWidget().setFixedSize(w,h)
+
+
+    def setTimeSeries(self,TS):
+        assert isinstance(TS, TimeSeries)
+        self.TS = TS
+        self.TS.sigTimeSeriesDatumAdded.connect(self.createTSDView)
+
+
+    def setMaxTSDViews(self, n=-1):
+        self.nMaxTSDViews = n
+        #todo: remove views
+
+    def setExtent(self, extent):
+        self.extent = extent
+        assert isinstance(extent, QgsRectangle)
+        tsdviews = sorted(self.TSDViews, key=lambda t:t.TSD)
+        for tsdview in tsdviews:
+            tsdview.setSpatialExtent(extent)
+
+    def navToDOI(self, TSD):
+        assert isinstance(TSD, TimeSeriesDatum)
+        #get widget related to TSD
+        tsdviews = [t for t in self.TSDViews if t.TSD == TSD]
+        if len(tsdviews) > 0:
+            i = self.TSDViews.index(tsdviews[0])+1.5
+            n = len(self.TSDViews)
+
+            scrollBar = self.TSV.ui.scrollAreaSubsets.horizontalScrollBar()
+            smin = scrollBar.minimum()
+            smax = scrollBar.maximum()
+            v = smin + (smax - smin) * float(i) / n
+            scrollBar.setValue(int(round(v)))
+
+    def addBandView(self, bandView):
+        assert isinstance(bandView, BandView)
+
+        w = self.L.parentWidget()
+        w.setUpdatesEnabled(False)
+
+        for tsdv in self.TSDViews:
+            tsdv.ui.setUpdatesEnabled(False)
+
+        for tsdv in self.TSDViews:
+            tsdv.insertBandView(bandView)
+
+        for tsdv in self.TSDViews:
+            tsdv.ui.setUpdatesEnabled(True)
+
+        w.setUpdatesEnabled(True)
+
+
+
+    def removeBandView(self, bandView):
+        assert isinstance(bandView, BandView)
+        for tsdv in self.TSDViews:
+            tsdv.removeBandView(bandView)
+
+
+
+    def createTSDView(self, TSD):
+        assert isinstance(TSD, TimeSeriesDatum)
+        tsdView = TimeSeriesDatumView(TSD)
+        tsdView.setSubsetSize(self.subsetSize)
+        tsdView.setSpatialExtent(self.extent)
+        self.addTSDView(tsdView)
+
+
+    def removeTSD(self, TSD):
+        assert isinstance(TSDV, TimeSeriesDatum)
+        tsdvs = [tsdv for tsdv in self.TSDViews if tsdv.TSD == TSD]
+        assert len(tsdvs) == 1
+        self.removeTSDView(tsdvs[0])
+
+    def removeTSDView(self, TSDV):
+        assert isinstance(TSDV, TimeSeriesDatumView)
+        self.TSDViews.remove(TSDV)
+
+    def addTSDView(self, TSDV):
+        assert isinstance(TSDV, TimeSeriesDatumView)
+
+        if len(self.TSDViews) < 10:
+            pass
+
+        bisect.insort(self.TSDViews, TSDV)
+
+        TSDV.ui.setParent(self.L.parentWidget())
+        self.L.addWidget(TSDV.ui)
+
+        self.adjustScrollArea()
+        #self.TSV.ui.scrollAreaSubsetContent.update()
+        #self.TSV.ui.scrollAreaSubsets.update()
+        s = ""
+
+
+
 
 class BandView(QObject):
 
     sigAddBandView = pyqtSignal(object)
     sigRemoveBandView = pyqtSignal(object)
 
-    def __init__(self, TS, recommended_bands=None, parent=None, showSensorNames=True):
+    def __init__(self, recommended_bands=None, parent=None, showSensorNames=True):
         super(BandView, self).__init__()
         self.ui = widgets.BandViewUI(parent)
         self.ui.create()
@@ -233,17 +366,9 @@ class BandView(QObject):
         self.ui.actionAddBandView.triggered.connect(lambda : self.sigAddBandView.emit(self))
         self.ui.actionRemoveBandView.triggered.connect(lambda: self.sigRemoveBandView.emit(self))
 
-        assert type(TS) is TimeSeries
         self.sensorViews = collections.OrderedDict()
-        self.TS = TS
-        self.TS.sigSensorAdded.connect(self.addSensor)
-        self.TS.sigChanged.connect(self.removeSensor)
-
         self.mShowSensorNames = showSensorNames
 
-        for sensor in self.TS.Sensors:
-            self.addSensor(sensor)
-
     def setTitle(self, title):
         self.ui.labelViewName.setText(title)
 
@@ -257,9 +382,16 @@ class BandView(QObject):
 
     def removeSensor(self, sensor):
         assert type(sensor) is SensorInstrument
-        assert sensor in self.sensorViews.keys()
-        self.sensorViews[sensor].close()
-        self.sensorViews.pop(sensor)
+        if sensor in self.sensorViews.keys():
+            self.sensorViews[sensor].close()
+            self.sensorViews.pop(sensor)
+            return True
+        else:
+            return False
+
+    def hasSensor(self, sensor):
+        assert type(sensor) is SensorInstrument
+        return sensor in self.sensorViews.keys()
 
     def addSensor(self, sensor):
         """
@@ -271,14 +403,173 @@ class BandView(QObject):
         w = widgets.ImageChipViewSettings(sensor)
         w.showSensorName(self.mShowSensorNames)
         self.sensorViews[sensor] = w
-        i = self.ui.mainLayout.count()-1
-        self.ui.mainLayout.insertWidget(i, w.ui)
-        s = ""
+        l = self.ui.sensorList.layout()
+        i = l.count() #last widget is a spacer
+        l.insertWidget(i-1, w.ui)
+
 
     def getSensorWidget(self, sensor):
         assert type(sensor) is SensorInstrument
         return self.sensorViews[sensor]
 
+
+
+class BandViewManager(QObject):
+
+    sigSensorAdded = pyqtSignal(SensorInstrument)
+    sigSensorRemoved = pyqtSignal(SensorInstrument)
+    sigBandViewAdded = pyqtSignal(BandView)
+    sigBandViewRemoved = pyqtSignal(BandView)
+
+    def __init__(self, timeSeriesViewer):
+        assert isinstance(timeSeriesViewer, TimeSeriesViewer)
+        super(BandViewManager, self).__init__()
+
+        self.TSV = timeSeriesViewer
+        self.bandViews = []
+
+    def removeSensor(self, sensor):
+        assert isinstance(sensor, SensorInstrument)
+
+        removed = False
+        for view in self.bandViews:
+            removed = removed and view.removeSensor(sensor)
+
+        if removed:
+            self.sigSensorRemoved(sensor)
+
+
+    def createBandView(self):
+        bandView = BandView(parent=self.TSV.ui.scrollAreaBandViewsContent, showSensorNames=False)
+        bandView.sigAddBandView.connect(self.createBandView)
+        bandView.sigRemoveBandView.connect(self.removeBandView)
+
+        self.addBandView(bandView)
+
+    def addBandView(self, bandView):
+        assert isinstance(bandView, BandView)
+        l = self.TSV.BVP
+        self.bandViews.append(bandView)
+        l.addWidget(bandView.ui)
+        n = len(self)
+
+        for sensor in self.TSV.TS.Sensors:
+            bandView.addSensor(sensor)
+
+        bandView.showSensorNames(n == 1)
+        bandView.setTitle('#{}'.format(n))
+        self.sigBandViewAdded.emit(bandView)
+
+    def removeBandView(self, bandView):
+        assert isinstance(bandView, BandView)
+        self.bandViews.remove(bandView)
+        self.TSV.BVP.removeWidget(bandView.ui)
+        bandView.ui.close()
+        #self.TSV.ui.scrollAreaBandViewsContent.update()
+
+        if len(self.bandViews) > 0:
+            self.bandViews[0].showSensorNames(True)
+            for i, bv in enumerate(self.bandViews):
+                bv.setTitle('#{}'.format(i + 1))
+
+        self.sigBandViewRemoved.emit(bandView)
+
+    def __len__(self):
+        return len(self.bandViews)
+
+    def __iter__(self):
+        return iter(self.bandViews)
+
+    def __getitem__(self, key):
+        return self.bandViews[key]
+
+    def __contains__(self, bandView):
+        return bandView in self.bandViews
+
+
+class TimeSeriesDatumView(QObject):
+    def __init__(self, TSD, parent=None):
+
+        super(TimeSeriesDatumView, self).__init__()
+        self.ui = widgets.TimeSeriesDatumViewUI(parent)
+        self.ui.create()
+
+        self.TSD = None
+        self.setTimeSeriesDatum(TSD)
+        self.bandViewCanvases = []
+        self.L = self.ui.layout()
+        self.wOffset = self.L.count()-1
+        self.setSubsetSize(QSize(300,300))
+
+
+    def setSpatialExtent(self, extent):
+
+        assert isinstance(extent, QgsRectangle)
+        for t in self.bandViewCanvases:
+            c = t[1]
+            c.setExtent(extent)
+
+    def setSubsetSize(self, size):
+        assert isinstance(size, QSize)
+        assert size.width() > 5 and size.height() > 5
+        self.subsetSize = size
+        m = self.L.contentsMargins()
+
+        self.ui.labelTitle.setFixedWidth(size.width())
+        self.ui.line.setFixedWidth(size.width())
+
+        #apply new subset size to existing canvases
+        for c in self.ui.findChildren(widgets.BandViewMapCanvas):
+            c.setFixedSize(size)
+
+        self.ui.setFixedWidth(size.width() + 2*(m.left() + m.right()))
+        n = len(self.bandViewCanvases)
+        #todo: improve size forecast
+        self.ui.setMinimumHeight((n+1) * size.height())
+
+
+    def setTimeSeriesDatum(self, TSD):
+        assert isinstance(TSD, TimeSeriesDatum)
+        self.TSD = TSD
+        self.ui.labelTitle.setText(str(TSD.date))
+
+        for c in self.ui.findChildren(widgets.BandViewMapCanvas):
+            c.setLayer(self.TSD.pathImg)
+
+    def removeBandView(self, bandView):
+        toRemove = self.findItems(bandView)
+        for t in toRemove:
+            self.bandViewCanvases.remove(t)
+            bandView, canvas = t
+            self.L.removeWidget(canvas)
+            canvas.close()
+
+
+    def findItems(self, item):
+        if isinstance(item, BandView):
+            return [t for t in self.bandViewCanvases if t[0] == item]
+        if isinstance(item, widgets.BandViewMapCanvas):
+            return [t for t in self.bandViewCanvases if t[1] == item]
+
+
+    def insertBandView(self, bandView, i=-1):
+        assert isinstance(bandView, BandView)
+        assert len(self.findItems(bandView)) == 0
+        assert i >= -1 and i <= len(self.bandViewCanvases)
+        if i == -1:
+            i = len(self.bandViewCanvases)
+
+        canvas = widgets.BandViewMapCanvas(self.ui)
+        canvas.setLayer(self.TSD.pathImg)
+        canvas.setFixedSize(self.subsetSize)
+        self.bandViewCanvases.insert(i, (bandView, canvas))
+        self.L.insertWidget(self.wOffset + i, canvas)
+
+    def __lt__(self, other):
+
+        return self.TSD < other.TSD
+
+
 class RenderJob(object):
 
     def __init__(self, TSD, renderer, destinationId=None):
@@ -739,10 +1030,16 @@ class TimeSeriesViewer:
         #init TS model
         TSM = TimeSeriesTableModel(self.TS)
         D = self.ui
+        self.ICP = D.scrollAreaSubsetContent.layout()
+        D.scrollAreaBandViewsContent.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
+        self.BVP = self.ui.scrollAreaBandViewsContent.layout()
+
         D.tableView_TimeSeries.setModel(TSM)
         D.tableView_TimeSeries.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents)
 
-        self.BAND_VIEWS = list()
+        self.bandViewManager = BandViewManager(self)
+        self.timeSeriesViewManager = TimeSeriesDatumViewManager(self)
+
         self.ImageChipBuffer = ImageChipBuffer()
         self.PIXMAPS = PixmapBuffer()
         self.PIXMAPS.sigPixmapCreated.connect(self.showSubset)
@@ -757,8 +1054,7 @@ class TimeSeriesViewer:
         D.actionSelectCenter.triggered.connect(self.ua_selectByCoordinate)
         D.actionSelectArea.triggered.connect(self.ua_selectByRectangle)
 
-        D.actionAddBandView.triggered.connect(self.ua_addBandView)
-        #D.actionRemoveBandView.triggered.connect(self.ua_removeBandView)
+        D.actionAddBandView.triggered.connect(self.bandViewManager.createBandView)
 
         D.actionAddTSD.triggered.connect(self.ua_addTSImages)
         D.actionRemoveTSD.triggered.connect(self.ua_removeTSD)
@@ -780,8 +1076,10 @@ class TimeSeriesViewer:
 
 
         D.sliderDOI.valueChanged.connect(self.setDOI)
-        D.spinBox_ncpu.setRange(0, multiprocessing.cpu_count())
 
+        D.actionSetSubsetSize.triggered.connect(lambda : self.timeSeriesViewManager.setSubsetSize(
+                                                self.ui.subsetSize()))
+        D.actionSetExtent.triggered.connect(lambda: self.timeSeriesViewManager.setExtent(self.ui.extent()))
 
         self.RectangleMapTool = None
         self.PointMapTool = None
@@ -798,9 +1096,6 @@ class TimeSeriesViewer:
             D.btnSelectArea.setEnabled(False)
             #self.RectangleMapTool.connect(self.ua_selectByRectangle_Done)
 
-        self.ICP = D.scrollAreaSubsetContent.layout()
-        D.scrollAreaBandViewsContent.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
-        self.BVP = self.ui.scrollAreaBandViewsContent.layout()
 
         self.check_enabled()
         s = ""
@@ -820,9 +1115,19 @@ class TimeSeriesViewer:
         ui.sliderDOI.setValue(v)
 
     def setDOI(self, i):
-        TSD = self.TS.data[i-1]
-        self.ui.labelDOI.setText(str(TSD.date))
-        s = ""
+
+        TSD = None
+
+        if i is None or i > len(self.TS):
+            text = '<empty timeseries>'
+        else:
+            TSD = self.TS.data[i - 1]
+            text = str(TSD.date)
+
+        self.ui.labelDOI.setText(text)
+
+        if TSD:
+            self.timeSeriesViewManager.navToDOI(TSD)
 
     @staticmethod
     def icon():
@@ -841,8 +1146,14 @@ class TimeSeriesViewer:
             self.hasInitialCenterPoint = True
         if len(self.TS.data) == 0:
             self.hasInitialCenterPoint = False
+        else:
+            if len(self.bandViewManager) == 0:
+                # add two empty band-views by default
+                self.ua_addBandView()
+                self.ua_addBandView()
 
-    def ua_loadTSFile(self, path=None):
+
+    def ua_loadTSFile(self, path=None, n_max=None):
         if path is None or path is False:
             path = QFileDialog.getOpenFileName(self.ui, 'Open Time Series file', '')
 
@@ -852,7 +1163,7 @@ class TimeSeriesViewer:
             M = self.ui.tableView_TimeSeries.model()
             M.beginResetModel()
             self.ua_clear_TS()
-            self.TS.loadFromFile(path)
+            self.TS.loadFromFile(path, n_max=n_max)
             M.endResetModel()
 
         self.check_enabled()
@@ -941,7 +1252,7 @@ class TimeSeriesViewer:
             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)
+                bbox = self.TS.getMaxExtent(srs=self.canvasCrs)
 
                 self.ui.spinBox_coordinate_x.setRange(bbox.xMinimum(), bbox.xMaximum())
                 self.ui.spinBox_coordinate_y.setRange(bbox.yMinimum(), bbox.yMaximum())
@@ -957,7 +1268,7 @@ class TimeSeriesViewer:
     def check_enabled(self):
         D = self.ui
         hasTS = len(self.TS) > 0 or DEBUG
-        hasTSV = len(self.BAND_VIEWS) > 0
+        hasTSV = len(self.bandViewManager) > 0
         hasQGIS = qgis_available
 
         #D.tabWidget_viewsettings.setEnabled(hasTS)
@@ -1086,7 +1397,7 @@ class TimeSeriesViewer:
                 self.ICP.addWidget(textLabel, 0, cnt_chips)
                 viewList = list()
                 j = 1
-                for view in self.BAND_VIEWS:
+                for view in self.bandViewManager:
                     viewWidget = view.getSensorWidget(TSD.sensor)
                     layerRenderer = viewWidget.layerRenderer()
 
@@ -1123,7 +1434,7 @@ class TimeSeriesViewer:
         #(TSD, [renderers] in order of views)
 
         LUT_RENDERER = {}
-        for view in self.BAND_VIEWS:
+        for view in self.bandViewManager:
             for sensor in view.Sensors.keys():
                 if sensor not in LUT_RENDERER.keys():
                     LUT_RENDERER[sensor] = []
@@ -1196,26 +1507,10 @@ class TimeSeriesViewer:
 
 
     def ua_addBandView(self):
-
-        bandView = BandView(self.TS, showSensorNames=len(self.BAND_VIEWS)==0)
-        bandView.sigAddBandView.connect(self.ua_addBandView)
-        bandView.sigRemoveBandView.connect(self.ua_removeBandView)
-
-        #bandView.removeView.connect(self.ua_removeBandView)
-        self.BAND_VIEWS.append(bandView)
-        self.BVP.addWidget(bandView.ui)
-
-        bandView.setTitle('#{}'.format(len(self.BAND_VIEWS)))
+        self.bandViewManager.createBandView()
 
     def ua_removeBandView(self, bandView):
-        #bandView.ui.setHidden(True)
-        self.BAND_VIEWS.remove(bandView)
-        self.BVP.removeWidget(bandView.ui)
-        bandView.ui.close()
-        self.BAND_VIEWS[0].showSensorNames(True)
-        for i, bv in enumerate(self.BAND_VIEWS):
-            bv.setTitle('#{}'.format(i+1))
-        #self.ui.scrollAreaBandViewsContent.update()
+        self.bandViewManager.removeBandView(bandView)
 
 
 
diff --git a/timeseriesviewer/tests.py b/timeseriesviewer/tests.py
index 00255569..3773ad65 100644
--- a/timeseriesviewer/tests.py
+++ b/timeseriesviewer/tests.py
@@ -48,7 +48,7 @@ def test_gui():
     S = TimeSeriesViewer(None)
     S.run()
 
-    S.ua_loadTSFile(path=PATH_EXAMPLE_TIMESERIES)
+    S.ua_loadTSFile(path=PATH_EXAMPLE_TIMESERIES, n_max=10)
     pass
 
 def test_component():
diff --git a/timeseriesviewer/timeseries.py b/timeseriesviewer/timeseries.py
index 2c1903f6..8753788b 100644
--- a/timeseriesviewer/timeseries.py
+++ b/timeseriesviewer/timeseries.py
@@ -400,8 +400,14 @@ class TimeSeriesDatum(QObject):
     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 __eq__(self, other):
+    #    return self.date == other.date and self.sensor == other.sensor
+
+    def __lt__(self, other):
+        if self.date < other.date:
+            return True
+        else:
+            return self.sensor.sensorName < other.sensor.sensorName
 
     def __hash__(self):
         return hash((self.date,self.sensor.sensorName))
@@ -445,7 +451,7 @@ class TimeSeries(QObject):
     _sep = ';'
 
 
-    def loadFromFile(self, path):
+    def loadFromFile(self, path, n_max=None):
 
         images = []
         masks = []
@@ -461,8 +467,12 @@ class TimeSeries(QObject):
                 if len(parts) > 1:
                     masks.append(parts[1])
 
-        self.addFiles(images)
-        self.addMasks(masks)
+        if n_max:
+            n_max = min([len(images), n_max])
+            self.addFiles(images[0:n_max])
+        else:
+            self.addFiles(images)
+        #self.addMasks(masks)
 
 
     def saveToFile(self, path):
@@ -490,7 +500,7 @@ class TimeSeries(QObject):
 
     def getMaxExtent(self, srs=None):
         if len(self.data) == 0:
-            return None
+            return QgsRectangle()
 
         if srs is None:
             srs = self.data[0].crs()
diff --git a/timeseriesviewer/ui/bandview.ui b/timeseriesviewer/ui/bandview.ui
index c6350804..8bc4779d 100644
--- a/timeseriesviewer/ui/bandview.ui
+++ b/timeseriesviewer/ui/bandview.ui
@@ -6,10 +6,15 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>206</width>
-    <height>140</height>
+    <width>530</width>
+    <height>175</height>
    </rect>
   </property>
+  <property name="font">
+   <font>
+    <pointsize>10</pointsize>
+   </font>
+  </property>
   <property name="windowTitle">
    <string>Frame</string>
   </property>
@@ -19,7 +24,7 @@
   <property name="frameShadow">
    <enum>QFrame::Plain</enum>
   </property>
-  <layout class="QHBoxLayout" name="mainLayout" stretch="0,0">
+  <layout class="QVBoxLayout" name="verticalLayout">
    <property name="spacing">
     <number>0</number>
    </property>
@@ -27,78 +32,89 @@
     <number>0</number>
    </property>
    <item>
-    <layout class="QVBoxLayout" name="headerColumnLayout">
-     <property name="spacing">
-      <number>1</number>
+    <widget class="QFrame" name="sensorList">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="font">
+      <font>
+       <pointsize>10</pointsize>
+      </font>
      </property>
-     <item>
-      <widget class="QLabel" name="labelViewName">
-       <property name="text">
-        <string>#1</string>
-       </property>
-       <property name="alignment">
-        <set>Qt::AlignCenter</set>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QToolButton" name="btnAddBandView">
-       <property name="text">
-        <string>...</string>
-       </property>
-       <property name="icon">
-        <iconset resource="resources.qrc">
-         <normaloff>:/timseriesviewer/icons/mActionAddBandView.png</normaloff>:/timseriesviewer/icons/mActionAddBandView.png</iconset>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QToolButton" name="btnRemoveBandView">
-       <property name="autoFillBackground">
-        <bool>false</bool>
-       </property>
-       <property name="text">
-        <string>...</string>
-       </property>
-       <property name="icon">
-        <iconset resource="resources.qrc">
-         <normaloff>:/timseriesviewer/icons/mActionRemoveBandView.png</normaloff>:/timseriesviewer/icons/mActionRemoveBandView.png</iconset>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <spacer name="verticalSpacer">
-       <property name="orientation">
-        <enum>Qt::Vertical</enum>
-       </property>
-       <property name="sizeHint" stdset="0">
-        <size>
-         <width>20</width>
-         <height>0</height>
-        </size>
-       </property>
-      </spacer>
-     </item>
-    </layout>
-   </item>
-   <item>
-    <widget class="QFrame" name="sensorRowLayout">
      <property name="autoFillBackground">
       <bool>true</bool>
      </property>
      <property name="frameShape">
-      <enum>QFrame::StyledPanel</enum>
+      <enum>QFrame::NoFrame</enum>
      </property>
      <property name="frameShadow">
-      <enum>QFrame::Raised</enum>
+      <enum>QFrame::Plain</enum>
      </property>
      <layout class="QHBoxLayout" name="horizontalLayout">
       <property name="spacing">
-       <number>1</number>
+       <number>5</number>
       </property>
       <property name="margin">
-       <number>1</number>
+       <number>0</number>
       </property>
+      <item>
+       <layout class="QVBoxLayout" name="headerColumnLayout">
+        <property name="spacing">
+         <number>1</number>
+        </property>
+        <item>
+         <widget class="QLabel" name="labelViewName">
+          <property name="text">
+           <string>#1</string>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignCenter</set>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QToolButton" name="btnAddBandView">
+          <property name="text">
+           <string>...</string>
+          </property>
+          <property name="icon">
+           <iconset resource="resources.qrc">
+            <normaloff>:/timseriesviewer/icons/mActionAddBandView.png</normaloff>:/timseriesviewer/icons/mActionAddBandView.png</iconset>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QToolButton" name="btnRemoveBandView">
+          <property name="autoFillBackground">
+           <bool>false</bool>
+          </property>
+          <property name="text">
+           <string>...</string>
+          </property>
+          <property name="icon">
+           <iconset resource="resources.qrc">
+            <normaloff>:/timseriesviewer/icons/mActionRemoveBandView.png</normaloff>:/timseriesviewer/icons/mActionRemoveBandView.png</iconset>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <spacer name="verticalSpacer">
+          <property name="orientation">
+           <enum>Qt::Vertical</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>20</width>
+            <height>0</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+       </layout>
+      </item>
       <item>
        <spacer name="horizontalSpacer">
         <property name="orientation">
@@ -106,7 +122,7 @@
         </property>
         <property name="sizeHint" stdset="0">
          <size>
-          <width>171</width>
+          <width>494</width>
           <height>20</height>
          </size>
         </property>
@@ -115,6 +131,25 @@
      </layout>
     </widget>
    </item>
+   <item>
+    <widget class="Line" name="bottomLine">
+     <property name="minimumSize">
+      <size>
+       <width>0</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Plain</enum>
+     </property>
+     <property name="lineWidth">
+      <number>1</number>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
   </layout>
   <action name="actionRemoveBandView">
    <property name="icon">
diff --git a/timeseriesviewer/ui/imagechipviewsettings.ui b/timeseriesviewer/ui/imagechipviewsettings.ui
index 8e4a65eb..7ccb3646 100644
--- a/timeseriesviewer/ui/imagechipviewsettings.ui
+++ b/timeseriesviewer/ui/imagechipviewsettings.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>285</width>
-    <height>137</height>
+    <width>263</width>
+    <height>154</height>
    </rect>
   </property>
   <property name="sizePolicy">
@@ -18,27 +18,27 @@
   </property>
   <property name="font">
    <font>
-    <pointsize>10</pointsize>
+    <pointsize>11</pointsize>
    </font>
   </property>
   <property name="windowTitle">
    <string>GroupBox</string>
   </property>
   <property name="title">
-   <string/>
+   <string>&lt;Sensor&gt;</string>
   </property>
   <property name="flat">
-   <bool>false</bool>
+   <bool>true</bool>
   </property>
   <property name="checkable">
    <bool>false</bool>
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
    <property name="spacing">
-    <number>0</number>
+    <number>1</number>
    </property>
    <property name="margin">
-    <number>0</number>
+    <number>1</number>
    </property>
    <item>
     <layout class="QGridLayout" name="bandSelectionGridLayout" rowstretch="0,0,0,0,0,0" columnstretch="0,0,0,0">
@@ -147,7 +147,7 @@
         </size>
        </property>
        <property name="text">
-        <string/>
+        <string>&lt;RGB Bands&gt;</string>
        </property>
       </widget>
      </item>
diff --git a/timeseriesviewer/ui/timseriesviewer.ui b/timeseriesviewer/ui/timseriesviewer.ui
index abb8bde9..43c21c63 100644
--- a/timeseriesviewer/ui/timseriesviewer.ui
+++ b/timeseriesviewer/ui/timseriesviewer.ui
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>1095</width>
-    <height>760</height>
+    <height>820</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -41,10 +41,16 @@
      <widget class="QScrollArea" name="scrollAreaSubsets">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
-        <horstretch>0</horstretch>
+        <horstretch>1</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
+      <property name="minimumSize">
+       <size>
+        <width>200</width>
+        <height>200</height>
+       </size>
+      </property>
       <property name="frameShape">
        <enum>QFrame::StyledPanel</enum>
       </property>
@@ -55,23 +61,40 @@
        <enum>Qt::ScrollBarAsNeeded</enum>
       </property>
       <property name="widgetResizable">
-       <bool>false</bool>
+       <bool>true</bool>
       </property>
       <widget class="QWidget" name="scrollAreaSubsetContent">
        <property name="geometry">
         <rect>
-         <x>-110</x>
+         <x>0</x>
          <y>0</y>
-         <width>723</width>
-         <height>491</height>
+         <width>613</width>
+         <height>595</height>
         </rect>
        </property>
        <property name="sizePolicy">
-        <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+        <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
          <horstretch>0</horstretch>
          <verstretch>0</verstretch>
         </sizepolicy>
        </property>
+       <property name="minimumSize">
+        <size>
+         <width>200</width>
+         <height>200</height>
+        </size>
+       </property>
+       <layout class="QHBoxLayout" name="scrollAreaLayout" stretch="">
+        <property name="spacing">
+         <number>1</number>
+        </property>
+        <property name="sizeConstraint">
+         <enum>QLayout::SetNoConstraint</enum>
+        </property>
+        <property name="margin">
+         <number>0</number>
+        </property>
+       </layout>
       </widget>
      </widget>
     </item>
@@ -99,7 +122,7 @@
         <bool>true</bool>
        </property>
        <property name="currentIndex">
-        <number>1</number>
+        <number>0</number>
        </property>
        <widget class="QWidget" name="tab_timeseries">
         <attribute name="title">
@@ -228,16 +251,22 @@
          <item>
           <widget class="QScrollArea" name="scrollAreaBandViews">
            <property name="sizePolicy">
-            <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+            <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
              <horstretch>1</horstretch>
              <verstretch>0</verstretch>
             </sizepolicy>
            </property>
+           <property name="minimumSize">
+            <size>
+             <width>100</width>
+             <height>100</height>
+            </size>
+           </property>
            <property name="autoFillBackground">
             <bool>true</bool>
            </property>
            <property name="verticalScrollBarPolicy">
-            <enum>Qt::ScrollBarAlwaysOn</enum>
+            <enum>Qt::ScrollBarAsNeeded</enum>
            </property>
            <property name="horizontalScrollBarPolicy">
             <enum>Qt::ScrollBarAsNeeded</enum>
@@ -250,7 +279,7 @@
              <rect>
               <x>0</x>
               <y>0</y>
-              <width>1068</width>
+              <width>1083</width>
               <height>120</height>
              </rect>
             </property>
@@ -432,7 +461,7 @@
                 </widget>
                </item>
                <item>
-                <widget class="QDoubleSpinBox" name="spinBox_coordinate_x">
+                <widget class="QDoubleSpinBox" name="spinBoxExtentCenterX">
                  <property name="maximumSize">
                   <size>
                    <width>100</width>
@@ -464,7 +493,7 @@
                 </widget>
                </item>
                <item>
-                <widget class="QDoubleSpinBox" name="spinBox_coordinate_y">
+                <widget class="QDoubleSpinBox" name="spinBoxExtentCenterY">
                  <property name="maximumSize">
                   <size>
                    <width>100</width>
@@ -528,7 +557,7 @@
                 </widget>
                </item>
                <item>
-                <widget class="QDoubleSpinBox" name="doubleSpinBox_subset_size_x">
+                <widget class="QDoubleSpinBox" name="spinBoxExtentWidth">
                  <property name="minimumSize">
                   <size>
                    <width>100</width>
@@ -566,7 +595,7 @@
                 </widget>
                </item>
                <item>
-                <widget class="QDoubleSpinBox" name="doubleSpinBox_subset_size_y">
+                <widget class="QDoubleSpinBox" name="spinBoxExtentHeight">
                  <property name="minimumSize">
                   <size>
                    <width>100</width>
@@ -624,7 +653,7 @@
              </widget>
             </item>
             <item row="3" column="1">
-             <widget class="QPlainTextEdit" name="tb_bb_srs">
+             <widget class="QPlainTextEdit" name="textBoxCRSInfo">
               <property name="enabled">
                <bool>true</bool>
               </property>
@@ -885,6 +914,8 @@
     <addaction name="actionLoadTS"/>
     <addaction name="actionSaveTS"/>
     <addaction name="actionClearTS"/>
+    <addaction name="separator"/>
+    <addaction name="actionAddTSExample"/>
    </widget>
    <widget class="QMenu" name="menuAbout">
     <property name="title">
@@ -905,265 +936,168 @@
   <widget class="QDockWidget" name="dockRendering">
    <property name="minimumSize">
     <size>
-     <width>281</width>
-     <height>216</height>
+     <width>324</width>
+     <height>232</height>
     </size>
    </property>
    <property name="features">
     <set>QDockWidget::AllDockWidgetFeatures</set>
    </property>
+   <property name="windowTitle">
+    <string>Rendering</string>
+   </property>
    <attribute name="dockWidgetArea">
     <number>1</number>
    </attribute>
    <widget class="QWidget" name="dockWidgetContents">
     <layout class="QVBoxLayout" name="verticalLayout_5">
+     <property name="spacing">
+      <number>1</number>
+     </property>
+     <property name="margin">
+      <number>1</number>
+     </property>
      <item>
-      <widget class="QGroupBox" name="groupBox_rendering">
-       <property name="minimumSize">
-        <size>
-         <width>0</width>
-         <height>0</height>
-        </size>
+      <widget class="QFrame" name="frame_5">
+       <property name="frameShape">
+        <enum>QFrame::NoFrame</enum>
        </property>
-       <property name="title">
-        <string>Rendering</string>
+       <property name="frameShadow">
+        <enum>QFrame::Raised</enum>
        </property>
-       <layout class="QFormLayout" name="formLayout">
-        <property name="fieldGrowthPolicy">
-         <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
-        </property>
-        <property name="horizontalSpacing">
-         <number>2</number>
+       <layout class="QHBoxLayout" name="horizontalLayout_9">
+        <property name="spacing">
+         <number>1</number>
         </property>
-        <property name="verticalSpacing">
-         <number>5</number>
+        <property name="margin">
+         <number>0</number>
         </property>
-        <property name="bottomMargin">
-         <number>6</number>
-        </property>
-        <item row="0" column="0" colspan="2">
-         <widget class="QRadioButton" name="rb_showEntireTS">
-          <property name="text">
-           <string>Entire Time Series</string>
-          </property>
-          <property name="checked">
-           <bool>true</bool>
-          </property>
-         </widget>
-        </item>
-        <item row="2" column="0">
-         <widget class="QRadioButton" name="rb_showTimeWindow">
-          <property name="enabled">
-           <bool>true</bool>
-          </property>
-          <property name="toolTip">
-           <string>Select </string>
-          </property>
-          <property name="text">
-           <string>Time Window</string>
-          </property>
-          <property name="checked">
-           <bool>false</bool>
-          </property>
-         </widget>
-        </item>
-        <item row="2" column="1">
-         <widget class="QFrame" name="frame_timewindow">
-          <property name="enabled">
-           <bool>false</bool>
-          </property>
-          <property name="minimumSize">
-           <size>
-            <width>50</width>
-            <height>0</height>
-           </size>
-          </property>
-          <property name="frameShape">
-           <enum>QFrame::NoFrame</enum>
-          </property>
-          <property name="frameShadow">
-           <enum>QFrame::Raised</enum>
-          </property>
-          <layout class="QHBoxLayout" name="horizontalLayout_3">
-           <property name="spacing">
-            <number>2</number>
-           </property>
-           <property name="margin">
-            <number>0</number>
-           </property>
-           <item>
-            <widget class="QLabel" name="label_8">
-             <property name="text">
-              <string>before</string>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QFrame" name="frame_4">
-             <property name="frameShape">
-              <enum>QFrame::NoFrame</enum>
-             </property>
-             <property name="frameShadow">
-              <enum>QFrame::Raised</enum>
-             </property>
-             <layout class="QHBoxLayout" name="horizontalLayout_8">
-              <property name="margin">
-               <number>0</number>
-              </property>
-             </layout>
-            </widget>
-           </item>
-           <item>
-            <widget class="QSpinBox" name="sb_ndates_before">
-             <property name="maximum">
-              <number>9999</number>
-             </property>
-             <property name="value">
-              <number>1</number>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QLabel" name="label_9">
-             <property name="text">
-              <string>after</string>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QSpinBox" name="sb_ndates_after">
-             <property name="maximum">
-              <number>9999</number>
-             </property>
-             <property name="value">
-              <number>1</number>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QLabel" name="label_14">
-             <property name="text">
-              <string>DOI</string>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <spacer name="horizontalSpacer_3">
-             <property name="orientation">
-              <enum>Qt::Horizontal</enum>
-             </property>
-             <property name="sizeHint" stdset="0">
-              <size>
-               <width>40</width>
-               <height>20</height>
-              </size>
-             </property>
-            </spacer>
-           </item>
-          </layout>
-         </widget>
-        </item>
-        <item row="3" column="0" colspan="2">
-         <widget class="QFrame" name="frame_5">
-          <property name="frameShape">
-           <enum>QFrame::NoFrame</enum>
-          </property>
-          <property name="frameShadow">
-           <enum>QFrame::Raised</enum>
-          </property>
-          <layout class="QHBoxLayout" name="horizontalLayout_9">
-           <property name="margin">
-            <number>0</number>
-           </property>
-           <item>
-            <widget class="QLabel" name="label_11">
-             <property name="text">
-              <string>max. size </string>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QSpinBox" name="spinBoxMaxPixmapSize">
-             <property name="minimumSize">
-              <size>
-               <width>50</width>
-               <height>0</height>
-              </size>
-             </property>
-             <property name="toolTip">
-              <string>Max. length of an image chip on screen.</string>
-             </property>
-             <property name="suffix">
-              <string>px</string>
-             </property>
-             <property name="minimum">
-              <number>20</number>
-             </property>
-             <property name="maximum">
-              <number>1000</number>
-             </property>
-             <property name="singleStep">
-              <number>50</number>
-             </property>
-             <property name="value">
-              <number>300</number>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QLabel" name="label">
-             <property name="text">
-              <string>#cpu</string>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QSpinBox" name="spinBox_ncpu">
-             <property name="toolTip">
-              <string>Number of CPUs used for parallel image chip calculation</string>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <spacer name="horizontalSpacer_5">
-             <property name="orientation">
-              <enum>Qt::Horizontal</enum>
-             </property>
-             <property name="sizeHint" stdset="0">
-              <size>
-               <width>40</width>
-               <height>20</height>
-              </size>
-             </property>
-            </spacer>
-           </item>
-          </layout>
-         </widget>
-        </item>
        </layout>
       </widget>
      </item>
      <item>
-      <widget class="QGroupBox" name="groupBox">
-       <property name="minimumSize">
+      <layout class="QHBoxLayout" name="horizontalLayout_8">
+       <property name="spacing">
+        <number>1</number>
+       </property>
+       <property name="topMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <widget class="QLabel" name="label_11">
+         <property name="text">
+          <string>Subset size x</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QSpinBox" name="spinBoxSubsetSizeX">
+         <property name="minimumSize">
+          <size>
+           <width>50</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="toolTip">
+          <string>Max. length of an image chip on screen.</string>
+         </property>
+         <property name="specialValueText">
+          <string/>
+         </property>
+         <property name="suffix">
+          <string>px</string>
+         </property>
+         <property name="minimum">
+          <number>20</number>
+         </property>
+         <property name="maximum">
+          <number>1000</number>
+         </property>
+         <property name="singleStep">
+          <number>25</number>
+         </property>
+         <property name="value">
+          <number>100</number>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QSpinBox" name="spinBoxSubsetSizeY">
+         <property name="suffix">
+          <string>px</string>
+         </property>
+         <property name="prefix">
+          <string/>
+         </property>
+         <property name="minimum">
+          <number>20</number>
+         </property>
+         <property name="maximum">
+          <number>1000</number>
+         </property>
+         <property name="singleStep">
+          <number>25</number>
+         </property>
+         <property name="value">
+          <number>50</number>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QCheckBox" name="checkBoxLockSubsetAspect">
+         <property name="text">
+          <string>lock aspect</string>
+         </property>
+         <property name="checked">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <spacer name="horizontalSpacer_5">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </item>
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout_3">
+       <item>
+        <widget class="QLabel" name="label">
+         <property name="text">
+          <string>Progress</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QProgressBar" name="progressBar">
+         <property name="value">
+          <number>0</number>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+     <item>
+      <spacer name="verticalSpacer_2">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
         <size>
-         <width>0</width>
-         <height>0</height>
+         <width>20</width>
+         <height>40</height>
         </size>
        </property>
-       <property name="title">
-        <string>Progress</string>
-       </property>
-       <layout class="QVBoxLayout" name="verticalLayout_10">
-        <item>
-         <widget class="QProgressBar" name="progressBar">
-          <property name="value">
-           <number>0</number>
-          </property>
-         </widget>
-        </item>
-       </layout>
-      </widget>
+      </spacer>
      </item>
     </layout>
    </widget>
@@ -1362,24 +1296,82 @@
     <string>Remove all images from time series</string>
    </property>
   </action>
+  <action name="actionSetSubsetSize">
+   <property name="text">
+    <string>setSubsetSize</string>
+   </property>
+  </action>
+  <action name="actionSetExtent">
+   <property name="text">
+    <string>setExtent</string>
+   </property>
+  </action>
  </widget>
  <resources>
   <include location="resources.qrc"/>
  </resources>
  <connections>
   <connection>
-   <sender>rb_showTimeWindow</sender>
-   <signal>toggled(bool)</signal>
-   <receiver>frame_timewindow</receiver>
-   <slot>setEnabled(bool)</slot>
+   <sender>spinBoxExtentCenterX</sender>
+   <signal>valueChanged(double)</signal>
+   <receiver>actionSetExtent</receiver>
+   <slot>trigger()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>125</x>
+     <y>150</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>-1</x>
+     <y>-1</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>spinBoxExtentCenterY</sender>
+   <signal>valueChanged(double)</signal>
+   <receiver>actionSetExtent</receiver>
+   <slot>trigger()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>234</x>
+     <y>150</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>-1</x>
+     <y>-1</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>spinBoxExtentHeight</sender>
+   <signal>valueChanged(double)</signal>
+   <receiver>actionSetExtent</receiver>
+   <slot>trigger()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>234</x>
+     <y>175</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>-1</x>
+     <y>-1</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>spinBoxExtentWidth</sender>
+   <signal>valueChanged(double)</signal>
+   <receiver>actionSetExtent</receiver>
+   <slot>trigger()</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>101</x>
-     <y>437</y>
+     <x>125</x>
+     <y>175</y>
     </hint>
     <hint type="destinationlabel">
-     <x>451</x>
-     <y>445</y>
+     <x>-1</x>
+     <y>-1</y>
     </hint>
    </hints>
   </connection>
diff --git a/timeseriesviewer/ui/widgets.py b/timeseriesviewer/ui/widgets.py
index b086bbe4..c37ace76 100644
--- a/timeseriesviewer/ui/widgets.py
+++ b/timeseriesviewer/ui/widgets.py
@@ -34,6 +34,7 @@ PATH_MAIN_UI = jp(DIR_UI, 'timseriesviewer.ui')
 PATH_BANDVIEWSETTINGS_UI = jp(DIR_UI, 'bandviewsettings.ui')
 PATH_IMAGECHIPVIEWSETTINGS_UI = jp(DIR_UI, 'imagechipviewsettings.ui')
 PATH_BANDVIEW_UI = jp(DIR_UI, 'bandview.ui')
+PATH_TSDVIEW_UI = jp(DIR_UI, 'timeseriesdatumview.ui')
 
 class TimeSeriesViewerUI(QMainWindow,
                          loadUIFormClass(PATH_MAIN_UI)):
@@ -47,9 +48,11 @@ class TimeSeriesViewerUI(QMainWindow,
         # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
         # #widgets-and-dialogs-with-auto-connect
         self.setupUi(self)
+        self.mCrs = None
 
-        #connect buttons with actions
-        self.btnCRS.setDefaultAction(self.actionSelectCRS)
+        #set button default actions -> this will show the action icons as well
+        #I don't know why this is not possible in the QDesigner when QToolButtons are
+        #placed outside a toolbar
         self.btnSelectArea.setDefaultAction(self.actionSelectArea)
         self.btnSelectCenterCoordinate.setDefaultAction(self.actionSelectCenter)
 
@@ -64,6 +67,94 @@ class TimeSeriesViewerUI(QMainWindow,
         self.btnSaveTS.setDefaultAction(self.actionSaveTS)
         self.btnClearTS.setDefaultAction(self.actionClearTS)
 
+        #define subset-size behaviour
+        self.spinBoxSubsetSizeX.valueChanged.connect(lambda: self.onSubsetValueChanged('X'))
+        self.spinBoxSubsetSizeY.valueChanged.connect(lambda: self.onSubsetValueChanged('Y'))
+        self.lastSubsetSizeX = self.spinBoxSubsetSizeX.value()
+        self.lastSubsetSizeY = self.spinBoxSubsetSizeY.value()
+
+
+    def crs(self):
+        return self.mCrs
+        pass
+
+    sigCrsChanged = pyqtSignal(QgsCoordinateReferenceSystem)
+    def setCrs(self, crs):
+        assert isinstance(crs, QgsCoordinateReferenceSystem)
+        old = self.mCrs
+        self.mCrs = crs
+        self.textBoxCRSInfo.setText(crs.toWkt())
+        if self.mCrs != old:
+            self.sigCrsChanged.emit(crs)
+
+
+    def extent(self):
+        width = QgsVector(self.spinBoxExtentWidth.value(), 0.0)
+        height = QgsVector(0.0, self.spinBoxExtentHeight.value())
+
+        Center = QgsPoint(self.spinBoxExtentCenterX.value(), self.spinBoxExtentCenterY.value())
+        UL = Center - (width * 0.5) + (height * 0.5)
+        LR = Center + (width * 0.5) - (height * 0.5)
+        return QgsRectangle(UL, LR)
+
+    sigExtentChanged = pyqtSignal(QgsRectangle)
+    def setExtent(self, extent):
+        old = self.extent()
+        assert isinstance(extent, QgsRectangle)
+        center = extent.center()
+        self.spinBoxExtentCenterX.setValue(center.x())
+        self.spinBoxExtentCenterY.setValue(center.y())
+        self.spinBoxExtentWidth.setValue(extent.width())
+        self.spinBoxExtentHeight.setValue(extent.height())
+
+        if old != extent:
+            self.sigExtentChanged.emit(extent)
+
+    sigSubsetSizeChanged = pyqtSignal(QSize)
+
+    def setSubsetSize(self, size):
+        old = self.subsetSize()
+        self.spinBoxSubsetSizeX.setValue(size.width())
+        self.spinBoxSubsetSizeY.setValue(size.height())
+
+        if old != size:
+            self.sigSubsetSizeChanged(size)
+
+    def subsetSize(self):
+        return QSize(self.spinBoxSubsetSizeX.value(),
+                     self.spinBoxSubsetSizeY.value())
+
+
+    def setProgress(self, value, valueMax=None, valueMin=0):
+        p = self.progressBar
+        if valueMin is not None and valueMin != self.progessBar.minimum():
+            p.setMinimum(valueMin)
+        if valueMax is not None and valueMax != self.progessBar.maximum():
+            p.setMaximum(valueMax)
+        self.progressBar.setValue(value)
+
+
+    def onSubsetValueChanged(self, key):
+        if self.checkBoxLockSubsetAspect.isChecked():
+
+            if key == 'X':
+                v_old = self.lastSubsetSizeX
+                v_new = self.spinBoxSubsetSizeX.value()
+                s = self.spinBoxSubsetSizeY
+            elif key == 'Y':
+                v_old = self.lastSubsetSizeY
+                v_new = self.spinBoxSubsetSizeY.value()
+                s = self.spinBoxSubsetSizeX
+
+            oldState = s.blockSignals(True)
+            s.setValue(int(round(float(v_new) / v_old * s.value())))
+            s.blockSignals(oldState)
+
+        self.lastSubsetSizeX = self.spinBoxSubsetSizeX.value()
+        self.lastSubsetSizeY = self.spinBoxSubsetSizeY.value()
+
+        self.actionSetSubsetSize.activate(QAction.Trigger)
+
 class VerticalLabel(QLabel):
     def __init__(self, text, orientation='vertical', forceWidth=True):
         QLabel.__init__(self, text)
@@ -136,7 +227,33 @@ class BandViewUI(QFrame, loadUIFormClass(PATH_BANDVIEW_UI)):
         self.btnAddBandView.setDefaultAction(self.actionAddBandView)
 
 
+class TimeSeriesDatumViewUI(QFrame, loadUIFormClass(PATH_TSDVIEW_UI)):
+    def __init__(self, title='<#>', parent=None):
+        super(TimeSeriesDatumViewUI, self).__init__(parent)
+
+        self.emptyHeight = self.height()
+        self.setupUi(self)
+
+    def sizeHint(self):
 
+        w = self.minimumWidth()
+        canvases = self.findChildren(BandViewMapCanvas)
+        h = self.emptyHeight + len(canvases) * w
+        return QSize(w,h)
+
+class LineWidget(QFrame):
+
+    def __init__(self, parent=None, orientation='horizontal'):
+        super(LineWidget, self).__init__(parent)
+
+        self.setFrameShadow(QFrame.Sunken)
+        self.setFixedHeight(3)
+        self.setStyleSheet("background-color: #c0c0c0;")
+        self.orientation = orientation
+        if self.orientation == 'horizontal':
+            self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
+        else:
+            self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
 
 class ImageChipViewSettingsUI(QGroupBox,
                              loadUIFormClass(PATH_IMAGECHIPVIEWSETTINGS_UI)):
@@ -158,6 +275,35 @@ class ImageChipViewSettingsUI(QGroupBox,
         self.btn453.setDefaultAction(self.actionSet453)
 
 
+class BandViewMapCanvas(QgsMapCanvas):
+
+    def __init__(self, parent=None):
+        super(BandViewMapCanvas, self).__init__(parent)
+        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
+        self.lyr = None
+        self.renderer = None
+        self.registry = QgsMapLayerRegistry.instance()
+
+    def setLayer(self, uri):
+        assert isinstance(uri, str)
+
+        self.setLayerSet([])
+        if self.lyr is not None:
+            #de-register layer
+            self.registry.removeMapLayer(self.lyr)
+
+        self.lyr = QgsRasterLayer(uri)
+        self.lyr.setRenderer(self.renderer)
+        self.registry.addMapLayer(self.lyr, False)
+
+        lset = [QgsMapCanvasLayer(self.lyr)]
+        self.setLayerSet(lset)
+
+    def setRenderer(self, renderer):
+        s = ""
+        self.renderer = renderer.clone()
+
+
 
 
 class ImageChipViewSettings(QObject):
@@ -165,8 +311,8 @@ class ImageChipViewSettings(QObject):
     #define signals
 
 
-
-    removeView = pyqtSignal()
+    sigRendererChanged = pyqtSignal(QgsRasterRenderer)
+    sigRemoveView = pyqtSignal()
 
     def __init__(self, sensor, parent=None):
         """Constructor."""
@@ -187,7 +333,7 @@ class ImageChipViewSettings(QObject):
         for sl in self.sliders:
             sl.setMinimum(1)
             sl.setMaximum(sensor.nb)
-            sl.valueChanged.connect(self.bandSelectionChanged)
+            sl.valueChanged.connect(self.layerRendererChanged)
 
         self.ceAlgs = [("No enhancement", QgsContrastEnhancement.NoEnhancement),
                        ("Stretch to MinMax", QgsContrastEnhancement.StretchToMinimumMaximum),
@@ -239,13 +385,14 @@ class ImageChipViewSettings(QObject):
 
         for i, b in enumerate(bands):
             self.sliders[i].setValue(b)
+            #slider value change emits signal -> no emit required here
 
     def rgb(self):
         return [self.ui.sliderRed.value(),
                self.ui.sliderGreen.value(),
                self.ui.sliderBlue.value()]
 
-    def bandSelectionChanged(self, *args):
+    def setRenderInfo(self, *args):
         rgb = self.rgb()
 
         text = 'RGB {}-{}-{}'.format(*rgb)
@@ -253,10 +400,7 @@ class ImageChipViewSettings(QObject):
             text += ' ({} {})'.format(
                 ','.join(['{:0.2f}'.format(self.sensor.wavelengths[b-1]) for b in rgb]),
                 self.sensor.wavelengthUnits)
-
-
         self.ui.labelBands.setText(text)
-        s = ""
 
     def setLayerRenderer(self, renderer):
         ui = self.ui
@@ -273,8 +417,11 @@ class ImageChipViewSettings(QObject):
 
             algs = [i[1] for i in self.ceAlgs]
             ui.comboBoxContrastEnhancement.setCurrentIndex(algs.index(ceRed.contrastEnhancementAlgorithm()))
+            self.layerRendererChanged()
 
-
+    def layerRendererChanged(self):
+        self.setRenderInfo()
+        self.sigRendererChanged.emit(self.layerRenderer())
 
     def layerRenderer(self):
         ui = self.ui
@@ -310,7 +457,7 @@ class ImageChipViewSettings(QObject):
         #add general options
         action = menu.addAction('Remove Band View')
         action.setToolTip('Removes this band view')
-        action.triggered.connect(lambda : self.removeView.emit())
+        action.triggered.connect(lambda : self.sigRemoveView.emit())
         #add QGIS specific options
         txt = QApplication.clipboard().text()
         if re.search('<!DOCTYPE(.|\n)*rasterrenderer.*type="multibandcolor"', txt) is not None:
-- 
GitLab