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