diff --git a/eotimeseriesviewer/__init__.py b/eotimeseriesviewer/__init__.py
index 517ade224ae12905acc799f8f5b6f4bc087dd59e..6e30947a67f1c5956470d991f7a119bb2aabf4da 100644
--- a/eotimeseriesviewer/__init__.py
+++ b/eotimeseriesviewer/__init__.py
@@ -19,7 +19,10 @@
  ***************************************************************************/
 """
 # noinspection PyPep8Naming
-
+import os
+import pathlib
+from qgis.core import QgsApplication, Qgis
+from qgis.PyQt.QtGui import QIcon
 
 __version__ = '1.13'  # sub-subversion number is added automatically
 LICENSE = 'GNU GPL-3'
@@ -37,16 +40,6 @@ CREATE_ISSUE = 'https://bitbucket.org/jakimowb/eo-time-series-viewer/issues/new'
 DEPENDENCIES = ['numpy', 'gdal']
 URL_TESTDATA = r''
 
-import os
-import sys
-import fnmatch
-import site
-import re
-import pathlib
-
-from qgis.core import QgsApplication, Qgis
-from qgis.PyQt.QtGui import QIcon
-
 DEBUG: bool = bool(os.environ.get('EOTSV_DEBUG', False))
 
 DIR = pathlib.Path(__file__).parent
diff --git a/eotimeseriesviewer/dateparser.py b/eotimeseriesviewer/dateparser.py
index 62a0c86913e05b8be861c324ec9b12c6edae23fb..0c26de72d5eadc53f41274208b210b84e2031cdb 100644
--- a/eotimeseriesviewer/dateparser.py
+++ b/eotimeseriesviewer/dateparser.py
@@ -1,22 +1,27 @@
-
-import os, re, logging, datetime
-from osgeo import gdal
+import datetime
+import re
+import os
 import numpy as np
-from qgis import *
-from qgis.PyQt.QtCore import QDate
+from osgeo import gdal
 
+from qgis.core import QgsMapLayer, QgsRasterLayer
+from qgis.PyQt.QtCore import QDate
 
 # regular expression. compile them only once
 
 # thanks to user "funkwurm" in
 # http://stackoverflow.com/questions/28020805/regex-validate-correct-iso8601-date-string-with-time
-regISODate1 = re.compile(r'(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:Z|[+-][01]\d:[0-5]\d)')
-regISODate3 = re.compile(r'([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?')
-regISODate2 = re.compile(r'(19|20|21\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?')
-#regISODate2 = re.compile(r'([12]\d{3}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?')
-#https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s07.html
-
-regYYYYMMDD = re.compile(r'(?P<year>(19|20)\d\d)(?P<hyphen>-?)(?P<month>1[0-2]|0[1-9])(?P=hyphen)(?P<day>3[01]|0[1-9]|[12][0-9])')
+regISODate1 = re.compile(
+    r'(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:Z|[+-][01]\d:[0-5]\d)')
+regISODate3 = re.compile(
+    r'([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?')
+regISODate2 = re.compile(
+    r'(19|20|21\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?')
+# regISODate2 = re.compile(r'([12]\d{3}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?')
+# https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s07.html
+
+regYYYYMMDD = re.compile(
+    r'(?P<year>(19|20)\d\d)(?P<hyphen>-?)(?P<month>1[0-2]|0[1-9])(?P=hyphen)(?P<day>3[01]|0[1-9]|[12][0-9])')
 regYYYY = re.compile(r'(?P<year>(19|20)\d\d)')
 regMissingHypen = re.compile(r'^\d{8}')
 regYYYYMM = re.compile(r'([0-9]{4})-(1[0-2]|0[1-9])')
@@ -24,6 +29,7 @@ regYYYYMM = re.compile(r'([0-9]{4})-(1[0-2]|0[1-9])')
 regYYYYDOY = re.compile(r'(?P<year>(19|20)\d\d)-?(?P<day>36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[1-9])')
 regDecimalYear = re.compile(r'(?P<year>(19|20)\d\d)\.(?P<datefraction>\d\d\d)')
 
+
 def matchOrNone(regex, text):
     match = regex.search(text)
     if match:
@@ -31,7 +37,8 @@ def matchOrNone(regex, text):
     else:
         return None
 
-def dateDOY(date:datetime.date) -> int:
+
+def dateDOY(date: datetime.date) -> int:
     """
     Returns the DOY
     :param date:
@@ -43,6 +50,7 @@ def dateDOY(date:datetime.date) -> int:
         date = date.astype(datetime.date)
     return date.timetuple().tm_yday
 
+
 def daysPerYear(year) -> int:
     """Returns the days per year"""
     if isinstance(year, np.datetime64):
@@ -52,6 +60,7 @@ def daysPerYear(year) -> int:
 
     return dateDOY(datetime.date(year=year, month=12, day=31))
 
+
 def num2date(n, dt64=True, qDate=False):
     """
     Converts a decimal-year number into a date
@@ -69,12 +78,11 @@ def num2date(n, dt64=True, qDate=False):
     yearDuration = daysPerYear(year)
     yearElapsed = fraction * yearDuration
 
-    import math
     doy = round(yearElapsed)
     if doy < 1:
         doy = 1
     try:
-        date = datetime.date(year, 1, 1) + datetime.timedelta(days=doy-1)
+        date = datetime.date(year, 1, 1) + datetime.timedelta(days=doy - 1)
     except:
         s = ""
     if qDate:
@@ -85,7 +93,7 @@ def num2date(n, dt64=True, qDate=False):
         return date
 
 
-def extractDateTimeGroup(text:str) -> np.datetime64:
+def extractDateTimeGroup(text: str) -> np.datetime64:
     """
     Extracts a date-time-group from a text string
     :param text: a string
@@ -95,7 +103,7 @@ def extractDateTimeGroup(text:str) -> np.datetime64:
     if match:
         matchedText = match.group()
         if regMissingHypen.search(matchedText):
-            matchedText = '{}-{}-{}'.format(matchedText[0:4],matchedText[4:6],matchedText[6:])
+            matchedText = '{}-{}-{}'.format(matchedText[0:4], matchedText[4:6], matchedText[6:])
         return np.datetime64(matchedText)
 
     match = regYYYYMMDD.search(text)
@@ -122,29 +130,32 @@ def extractDateTimeGroup(text:str) -> np.datetime64:
         return np.datetime64(match.group('year'))
     return None
 
+
 def datetime64FromYYYYMMDD(yyyymmdd):
     if re.search(r'^\d{8}$', yyyymmdd):
-        #insert hyphens
-        yyyymmdd = '{}-{}-{}'.format(yyyymmdd[0:4],yyyymmdd[4:6],yyyymmdd[6:8])
+        # insert hyphens
+        yyyymmdd = '{}-{}-{}'.format(yyyymmdd[0:4], yyyymmdd[4:6], yyyymmdd[6:8])
     return np.datetime64(yyyymmdd)
 
+
 def datetime64FromYYYYDOY(yyyydoy):
     return datetime64FromDOY(yyyydoy[0:4], yyyydoy[4:7])
 
+
 def DOYfromDatetime64(dt):
     doy = dt.astype('datetime64[D]') - dt.astype('datetime64[Y]') + 1
     doy = doy.astype(np.int16)
     return doy
 
-    return (dt.astype('datetime64[D]') - dt.astype('datetime64[Y]')).astype(int)+1
+    return (dt.astype('datetime64[D]') - dt.astype('datetime64[Y]')).astype(int) + 1
 
-def datetime64FromDOY(year, doy):
-        if type(year) is str:
-            year = int(year)
-        if type(doy) is str:
-            doy = int(doy)
-        return np.datetime64('{:04d}-01-01'.format(year)) + np.timedelta64(doy-1, 'D')
 
+def datetime64FromDOY(year, doy):
+    if type(year) is str:
+        year = int(year)
+    if type(doy) is str:
+        doy = int(doy)
+    return np.datetime64('{:04d}-01-01'.format(year)) + np.timedelta64(doy - 1, 'D')
 
 
 class ImageDateReader(object):
@@ -166,6 +177,7 @@ class ImageDateReader(object):
         raise NotImplementedError()
         return None
 
+
 class ImageReaderOWS(ImageDateReader):
     """Date reader for OGC web services"""
 
@@ -189,6 +201,7 @@ class ImageDateReaderDefault(ImageDateReader):
     """
     Default reader for dates in gdal.Datasets
     """
+
     def __init__(self, dataSet):
         super(ImageDateReaderDefault, self).__init__(dataSet)
         self.regDateKeys = re.compile('(acquisition[ _]*(time|date|datetime))', re.IGNORECASE)
@@ -218,10 +231,12 @@ class ImageDateReaderDefault(ImageDateReader):
             return dtg
         return None
 
+
 class ImageDateReaderPLEIADES(ImageDateReader):
     """
     Date reader for PLEIADES images
     """
+
     def __init__(self, dataSet):
         super(ImageDateReaderPLEIADES, self).__init__(dataSet)
 
@@ -254,20 +269,22 @@ class ImageDateReaderSentinel2(ImageDateReader):
             return np.datetime64(timeStamp)
         return None
 
+
 class ImageDateParserLandsat(ImageDateReader):
     """
     Reader for date in LANDSAT images
     #see https://landsat.usgs.gov/what-are-naming-conventions-landsat-scene-identifiers
     """
 
-    regLandsatSceneID  = re.compile(r'L[COTEM][4578]\d{3}\d{3}\d{4}\d{3}[A-Z]{2}[A-Z1]\d{2}')
-    regLandsatProductID = re.compile(r'L[COTEM]0[78]_(L1TP|L1GT|L1GS)_\d{3}\d{3}_\d{4}\d{2}\d{2}_\d{4}\d{2}\d{2}_0\d{1}_(RT|T1|T2)')
+    regLandsatSceneID = re.compile(r'L[COTEM][4578]\d{3}\d{3}\d{4}\d{3}[A-Z]{2}[A-Z1]\d{2}')
+    regLandsatProductID = re.compile(
+        r'L[COTEM]0[78]_(L1TP|L1GT|L1GS)_\d{3}\d{3}_\d{4}\d{2}\d{2}_\d{4}\d{2}\d{2}_0\d{1}_(RT|T1|T2)')
 
     def __init__(self, dataSet):
         super(ImageDateParserLandsat, self).__init__(dataSet)
 
     def readDTG(self):
-        #search for LandsatSceneID (old) and Landsat Product IDs (new)
+        # search for LandsatSceneID (old) and Landsat Product IDs (new)
         sceneID = matchOrNone(ImageDateParserLandsat.regLandsatSceneID, self.baseName)
         if sceneID:
             return datetime64FromYYYYDOY(sceneID[9:16])
@@ -278,15 +295,14 @@ class ImageDateParserLandsat(ImageDateReader):
         return None
 
 
-
 dateParserList = [c for c in ImageDateReader.__subclasses__()]
-dateParserList.insert(0, dateParserList.pop(dateParserList.index(ImageDateReaderDefault))) # set to first position
+dateParserList.insert(0, dateParserList.pop(dateParserList.index(ImageDateReaderDefault)))  # set to first position
 
-def parseDateFromDataSet(dataSet:gdal.Dataset) -> np.datetime64:
+
+def parseDateFromDataSet(dataSet: gdal.Dataset) -> np.datetime64:
     assert isinstance(dataSet, gdal.Dataset)
     for parser in dateParserList:
         dtg = parser(dataSet).readDTG()
         if dtg:
             return dtg
     return None
-
diff --git a/eotimeseriesviewer/labeling.py b/eotimeseriesviewer/labeling.py
index 3210b2fb6dd67a1b20d45029936999251712a675..aeda382075f7383242a047b3b18de2778506354a 100644
--- a/eotimeseriesviewer/labeling.py
+++ b/eotimeseriesviewer/labeling.py
@@ -1,10 +1,8 @@
 
 import enum
-from qgis.core import *
 from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsField, QgsFields, \
     QgsEditorWidgetSetup, QgsFeature, QgsVectorLayerTools, \
     QgsRendererCategory, QgsCategorizedSymbolRenderer, QgsProject, QgsMapLayerStore, QgsSymbol
-from qgis.gui import *
 from qgis.gui import QgsDockWidget, QgsSpinBox, QgsDoubleSpinBox, \
     QgsEditorConfigWidget, QgsEditorWidgetFactory, QgsEditorWidgetWrapper, \
     QgsGui, QgsEditorWidgetRegistry, QgsDateTimeEdit, QgsDateEdit, QgsTimeEdit
diff --git a/eotimeseriesviewer/main.py b/eotimeseriesviewer/main.py
index b299dac97704d6e3234d896378b1f7576d23a3d3..3251271062d201d316b09375800f871cdfedb6d8 100644
--- a/eotimeseriesviewer/main.py
+++ b/eotimeseriesviewer/main.py
@@ -43,7 +43,7 @@ from qgis.core import *
 from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsMessageOutput, QgsCoordinateReferenceSystem, \
     Qgis, QgsWkbTypes, QgsTask, QgsProviderRegistry, QgsMapLayerStore, QgsFeature, QgsField, \
     QgsTextFormat, QgsProject, QgsSingleSymbolRenderer, QgsGeometry, QgsApplication, QgsFillSymbol, \
-    QgsProjectArchive, QgsZipUtils
+    QgsProjectArchive, QgsZipUtils, QgsPointXY
 
 from qgis.gui import *
 from qgis.gui import QgsMapCanvas, QgsStatusBar, QgsFileWidget, \
@@ -410,8 +410,8 @@ class EOTimeSeriesViewer(QgisInterface, QObject):
         """
         return EOTimeSeriesViewer._instance
 
-    sigCurrentLocationChanged = pyqtSignal([SpatialPoint],
-                                           [SpatialPoint, QgsMapCanvas])
+    sigCurrentLocationChanged = pyqtSignal([QgsCoordinateReferenceSystem, QgsPointXY],
+                                           [QgsCoordinateReferenceSystem, QgsPointXY, QgsMapCanvas])
 
     sigCurrentSpectralProfilesChanged = pyqtSignal(list)
     sigCurrentTemporalProfilesChanged = pyqtSignal(list)
@@ -456,15 +456,19 @@ class EOTimeSeriesViewer(QgisInterface, QObject):
         self.mTaskManagerButton.setFont(statusBarFont)
         self.mStatusBar.addPermanentWidget(self.mTaskManagerButton, 10, QgsStatusBar.AnchorLeft)
         self.mTaskManagerButton.clicked.connect(lambda *args: self.ui.dockTaskManager.raise_())
-        mvd = self.ui.dockMapViews
-        dts = self.ui.dockTimeSeries
-        mw = self.ui.mMapWidget
-
         self.mMapLayerStore = self.mapWidget().mMapLayerStore
+
         import eotimeseriesviewer.utils
         eotimeseriesviewer.utils.MAP_LAYER_STORES.insert(0, self.mapLayerStore())
         from eotimeseriesviewer.timeseries import TimeSeriesDock
         from eotimeseriesviewer.mapvisualization import MapViewDock, MapWidget
+
+        mvd: MapViewDock = self.ui.dockMapViews
+        dts = self.ui.dockTimeSeries
+        mw: MapWidget = self.ui.mMapWidget
+
+
+
         assert isinstance(mvd, MapViewDock)
         assert isinstance(mw, MapWidget)
         assert isinstance(dts, TimeSeriesDock)
@@ -515,7 +519,9 @@ class EOTimeSeriesViewer(QgisInterface, QObject):
         mw.sigSpatialExtentChanged.connect(self.timeSeries().setCurrentSpatialExtent)
         mw.sigVisibleDatesChanged.connect(self.timeSeries().setVisibleDates)
         mw.sigMapViewAdded.connect(self.onMapViewAdded)
-        mw.sigCurrentLocationChanged.connect(self.setCurrentLocation)
+        mw.sigCurrentLocationChanged.connect(
+            lambda crs, pt, canvas=mw: self.setCurrentLocation(SpatialPoint(crs, pt),
+                                                               mapCanvas=mw.currentMapCanvas()))
         mw.sigCurrentLayerChanged.connect(self.updateCurrentLayerActions)
 
         self.ui.optionSyncMapCenter.toggled.connect(self.mapWidget().setSyncWithQGISMapCanvas)
@@ -631,14 +637,15 @@ class EOTimeSeriesViewer(QgisInterface, QObject):
         from eotimeseriesviewer import DOCUMENTATION, SpectralLibrary, SpectralLibraryPanel, SpectralLibraryWidget
         self.ui.actionShowOnlineHelp.triggered.connect(lambda: webbrowser.open(DOCUMENTATION))
 
-        SLW = self.ui.dockSpectralLibrary.spectralLibraryWidget()
+        SLW: SpectralLibraryWidget = self.ui.dockSpectralLibrary.spectralLibraryWidget()
         assert isinstance(SLW, SpectralLibraryWidget)
-
-        SLW.setMapInteraction(True)
-        SLW.setCurrentProfilesMode(SpectralLibraryWidget.CurrentProfilesMode.automatically)
-        SLW.sigMapExtentRequested.connect(self.setSpatialExtent)
-        SLW.sigMapCenterRequested.connect(self.setSpatialCenter)
-
+        SLW.actionSelectProfilesFromMap.setVisible(True)
+        SLW.sigLoadFromMapRequest.connect(lambda *args: self.setMapTool(MapTools.SpectralProfile))
+        #SLW.setMapInteraction(True)
+        #SLW.setCurrentProfilesMode(SpectralLibraryWidget.CurrentProfilesMode.automatically)
+        #SLW.sigMapExtentRequested.connect(self.setSpatialExtent)
+        #SLW.sigMapCenterRequested.connect(self.setSpatialCenter)
+        SLW.setVectorLayerTools(self.mVectorLayerTools)
         # add time-specific fields
         sl = self.spectralLibrary()
 
@@ -1202,7 +1209,8 @@ class EOTimeSeriesViewer(QgisInterface, QObject):
         self.mCurrentMapLocation = spatialPoint
 
         if isinstance(mapCanvas, QgsMapCanvas):
-            self.sigCurrentLocationChanged[SpatialPoint, QgsMapCanvas].emit(self.mCurrentMapLocation, mapCanvas)
+            self.sigCurrentLocationChanged[QgsCoordinateReferenceSystem, QgsPointXY, QgsMapCanvas].emit(
+                self.mCurrentMapLocation.crs(), self.mCurrentMapLocation, mapCanvas)
 
             if bCLV:
                 self.loadCursorLocationValueInfo(spatialPoint, mapCanvas)
@@ -1216,7 +1224,9 @@ class EOTimeSeriesViewer(QgisInterface, QObject):
         if bTP:
             self.loadCurrentTemporalProfile(spatialPoint)
 
-        self.sigCurrentLocationChanged[SpatialPoint].emit(self.mCurrentMapLocation)
+        self.sigCurrentLocationChanged[QgsCoordinateReferenceSystem, QgsPointXY].emit(
+            self.mCurrentMapLocation.crs(),
+            self.mCurrentMapLocation)
 
     @pyqtSlot(SpatialPoint, QgsMapCanvas)
     def loadCursorLocationValueInfo(self, spatialPoint: SpatialPoint, mapCanvas: QgsMapCanvas):
diff --git a/eotimeseriesviewer/mapcanvas.py b/eotimeseriesviewer/mapcanvas.py
index 5085cd1070201f63401caa06ca3cda44d46ca629..0f6ae816bea01a4f3a6d91285dea9c881658ce82 100644
--- a/eotimeseriesviewer/mapcanvas.py
+++ b/eotimeseriesviewer/mapcanvas.py
@@ -19,27 +19,18 @@
  ***************************************************************************/
 """
 # noinspection PyPep8Naming
-KEY_LAST_CLICKED = 'LAST_CLICKED'
-
 import time
-
-import eotimeseriesviewer.settings
+import sys
+import typing
+import os
+import re
+import enum
 import qgis.utils
-from .externals.qps.classification.classificationscheme import ClassificationScheme, ClassInfo
-from .externals.qps.crosshair.crosshair import CrosshairDialog, CrosshairStyle, CrosshairMapCanvasItem
-from .externals.qps.layerproperties import showLayerPropertiesDialog
-from .externals.qps.maptools import *
-from .externals.qps.utils import *
-from .labeling import quickLabelLayers, setQuickTSDLabelsForRegisteredLayers
-from .timeseries import TimeSeriesDate, TimeSeriesSource, SensorProxyLayer
-
 from qgis.PyQt.QtGui import QIcon, QContextMenuEvent, QMouseEvent, QPainter, QFont, QColor
 from qgis.PyQt.QtWidgets import QApplication, QDialog, QMenu, QFileDialog, QSizePolicy, QStyle, QStyleOptionProgressBar
 from qgis.PyQt.QtCore import QSize, QDate, QDateTime, QDir, QFile, QMimeData, pyqtSignal, Qt, \
     QPoint, QObject, QRectF, QPointF, QRect, QTimer
 
-from qgis.core import *
-from qgis.gui import *
 from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsContrastEnhancement, \
     QgsDateTimeRange, QgsProject, QgsTextRenderer, QgsApplication, QgsCoordinateReferenceSystem, \
     QgsMapToPixel, QgsRenderContext, QgsMapSettings, QgsRasterRenderer, \
@@ -47,7 +38,7 @@ from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsContrastEn
     QgsSingleBandPseudoColorRenderer, QgsWkbTypes, QgsRasterLayerTemporalProperties, QgsRasterDataProvider, \
     QgsTextFormat, QgsMapLayerStore, QgsMultiBandColorRenderer, QgsSingleBandGrayRenderer, QgsField, \
     QgsRectangle, QgsPolygon, QgsMultiBandColorRenderer, QgsRectangle, QgsSingleBandGrayRenderer, \
-    QgsLayerTreeGroup, QgsUnitTypes
+    QgsLayerTreeGroup, QgsUnitTypes, QgsMimeDataUtils
 
 from qgis.gui import QgsMapCanvas, QgisInterface, QgsFloatingWidget, QgsUserInputWidget, \
     QgsAdvancedDigitizingDockWidget, QgsMapCanvasItem, \
@@ -55,6 +46,20 @@ from qgis.gui import QgsMapCanvas, QgisInterface, QgsFloatingWidget, QgsUserInpu
     QgsGeometryRubberBand
 
 
+from .externals.qps.classification.classificationscheme import ClassificationScheme, ClassInfo
+from .externals.qps.crosshair.crosshair import CrosshairDialog, CrosshairStyle, CrosshairMapCanvasItem
+from .externals.qps.layerproperties import showLayerPropertiesDialog
+from .externals.qps.maptools import QgsMapToolSelectionHandler, \
+    CursorLocationMapTool, QgsMapToolAddFeature, \
+    SpectralProfileMapTool, TemporalProfileMapTool, MapToolCenter, PixelScaleExtentMapTool, FullExtentMapTool, QgsMapToolSelect
+from .externals.qps.utils import SpatialExtent, SpatialPoint
+from .labeling import quickLabelLayers, setQuickTSDLabelsForRegisteredLayers
+from .timeseries import TimeSeriesDate, TimeSeriesSource, SensorProxyLayer
+import eotimeseriesviewer.settings
+
+KEY_LAST_CLICKED = 'LAST_CLICKED'
+
+
 def toQgsMimeDataUtilsUri(mapLayer: QgsMapLayer):
     uri = QgsMimeDataUtils.Uri()
     uri.name = mapLayer.name()
@@ -1360,7 +1365,7 @@ class MapCanvas(QgsMapCanvas):
             path = filenameFromString('{}.{}'.format(self.mTSD.date(), self.mMapView.title()))
         else:
             path = 'mapcanvas'
-        path = jp(lastDir, '{}.{}'.format(path, fileType.lower()))
+        path = os.path.join(lastDir, '{}.{}'.format(path, fileType.lower()))
         path, _ = QFileDialog.getSaveFileName(self, 'Save map as {}'.format(fileType), path)
         if len(path) > 0:
             self.saveAsImage(path, None, fileType)
diff --git a/eotimeseriesviewer/mapviewscrollarea.py b/eotimeseriesviewer/mapviewscrollarea.py
index ce155f79a089655ca5a335b9621ec0e40e857261..7f0248096d49d0ad4a114ba14c0a52c9c6fadde9 100644
--- a/eotimeseriesviewer/mapviewscrollarea.py
+++ b/eotimeseriesviewer/mapviewscrollarea.py
@@ -19,35 +19,22 @@
 # noinspection PyPep8Naming
 
 from qgis.PyQt.QtCore import *
-from qgis.PyQt.QtGui import *
 from qgis.PyQt.QtWidgets import *
 
 
 class MapViewScrollArea(QScrollArea):
 
-    #sigResized = pyqtSignal()
-
     def __init__(self, *args, **kwds):
         super(MapViewScrollArea, self).__init__(*args, **kwds)
         self.horizontalScrollBar().setTracking(False)
         self.verticalScrollBar().setTracking(False)
 
-    #def resizeEvent(self, event):
-        #super(MapViewScrollArea, self).resizeEvent(event)
-        #self.sigResized.emit()
-
-    def distanceToCenter(self, widget:QWidget) -> int:
+    def distanceToCenter(self, widget: QWidget) -> int:
         # self.visibleRegion().boundingRect().isValid()
         halfSize = widget.size() * 0.5
         centerInParent = widget.mapToParent(QPoint(halfSize.width(), halfSize.height()))
         r = self.viewport().rect()
-        centerViewPort = QPoint(int(r.x() + r.width() *0.5 ), int(r.y()+r.height()*0.5))
+        centerViewPort = QPoint(int(r.x() + r.width() * 0.5), int(r.y() + r.height() * 0.5))
 
         diff = centerInParent - centerViewPort
         return diff.manhattanLength()
-
-    #def sizeHint(self):
-    #    parent = self.parent()
-    #    hint = super(MapViewScrollArea, self).sizeHint()
-
-    #    return hint
diff --git a/eotimeseriesviewer/mapvisualization.py b/eotimeseriesviewer/mapvisualization.py
index 58ef68d7563673e30baccf5b5acc9e63513c0c9e..159d97489483432561065b484f7e0d4d50ecfc0d 100644
--- a/eotimeseriesviewer/mapvisualization.py
+++ b/eotimeseriesviewer/mapvisualization.py
@@ -20,43 +20,34 @@
  ***************************************************************************/
 """
 
-import os
+import enum
 import sys
-import re
-import fnmatch
-import collections
-import copy
 import traceback
-import bisect
-import json
-from eotimeseriesviewer import DIR_UI
-from qgis.core import *
-from qgis.core import QgsContrastEnhancement, QgsRasterShader, QgsColorRampShader, QgsProject, \
-    QgsCoordinateReferenceSystem, QgsVector, QgsTextFormat, \
+import typing
+
+import numpy as np
+
+import qgis.utils
+from qgis.PyQt.QtCore import Qt, QSize, pyqtSignal, QModelIndex, QTimer, QAbstractListModel
+from qgis.PyQt.QtGui import QColor, QIcon, QGuiApplication, QMouseEvent
+from qgis.PyQt.QtWidgets import QWidget, QLayoutItem, QFrame, QLabel, QGridLayout, QSlider, QMenu, QToolBox, QDialog
+from qgis.PyQt.QtXml import QDomDocument, QDomNode, QDomElement
+from qgis.core import QgsCoordinateReferenceSystem, QgsVector, QgsTextFormat, \
     QgsRectangle, QgsRasterRenderer, QgsMapLayerStore, QgsMapLayerStyle, \
     QgsLayerTreeModel, QgsLayerTreeGroup, \
     QgsLayerTree, QgsLayerTreeLayer, \
-    QgsRasterLayer, QgsVectorLayer, QgsMapLayer, QgsMapLayerProxyModel, QgsColorRamp, QgsSingleBandPseudoColorRenderer
-
-from qgis.gui import *
-from qgis.gui import QgsDockWidget, QgsMapCanvas, QgsMapTool, QgsCollapsibleGroupBox, QgsLayerTreeView, \
+    QgsRasterLayer, QgsVectorLayer, QgsMapLayer, QgsMapLayerProxyModel, QgsPointXY, QgsReadWriteContext
+from qgis.gui import QgsDockWidget, QgsMapCanvas, QgsLayerTreeView, \
     QgisInterface, QgsLayerTreeViewMenuProvider, QgsLayerTreeMapCanvasBridge, \
     QgsProjectionSelectionWidget, QgsMessageBar
-from qgis.PyQt.QtXml import *
-from qgis.PyQt.QtCore import *
-from qgis.PyQt.QtGui import *
-import numpy as np
-from .utils import *
-from . import Option, OptionListModel
-from .timeseries import SensorInstrument, TimeSeriesDate, TimeSeries, SensorProxyLayer
-from .utils import loadUi
-from .mapviewscrollarea import MapViewScrollArea
-from .mapcanvas import MapCanvas, MapTools, MapCanvasInfoItem, MapCanvasMapTools, KEY_LAST_CLICKED
-from eotimeseriesviewer import debugLog
 from .externals.qps.crosshair.crosshair import getCrosshairStyle, CrosshairStyle, CrosshairMapCanvasItem
-from .externals.qps.layerproperties import showLayerPropertiesDialog
-from .externals.qps.maptools import *
-
+from .externals.qps.layerproperties import VectorLayerTools
+from .externals.qps.maptools import MapTools
+from .mapcanvas import MapCanvas, MapCanvasInfoItem, KEY_LAST_CLICKED
+from .timeseries import SensorInstrument, TimeSeriesDate, TimeSeries, SensorProxyLayer
+from .utils import loadUi, SpatialPoint, SpatialExtent, datetime64
+from eotimeseriesviewer import DIR_UI
+from eotimeseriesviewer.main import fixMenuButtons
 KEY_LOCKED_LAYER = 'eotsv/locked'
 KEY_SENSOR_GROUP = 'eotsv/sensorgroup'
 KEY_SENSOR_LAYER = 'eotsv/sensorlayer'
@@ -999,7 +990,7 @@ class MapWidget(QFrame):
     sigCurrentCanvasChanged = pyqtSignal(MapCanvas)
     sigCurrentMapViewChanged = pyqtSignal(MapView)
     sigCurrentDateChanged = pyqtSignal(TimeSeriesDate)
-    sigCurrentLocationChanged = pyqtSignal(SpatialPoint, MapCanvas)
+    sigCurrentLocationChanged = pyqtSignal(QgsCoordinateReferenceSystem, QgsPointXY)
     sigVisibleDatesChanged = pyqtSignal(list)
     sigViewModeChanged = pyqtSignal(ViewMode)
 
@@ -1350,7 +1341,6 @@ class MapWidget(QFrame):
             self.mTimeSeries.sigTimeSeriesDatesAdded.disconnect(self._updateSliderRange)
             self.mTimeSeries.sigTimeSeriesDatesRemoved.disconnect(self._updateSliderRange)
 
-
         self.mTimeSeries = ts
         if isinstance(self.mTimeSeries, TimeSeries):
             self.mTimeSeries.sigVisibilityChanged.connect(self._updateCanvasDates)
@@ -1728,7 +1718,7 @@ class MapWidget(QFrame):
         # mapCanvas.sigDestinationCrsChanged.connect(self.setCrs)
         mapCanvas.sigCrosshairPositionChanged.connect(self.onCrosshairPositionChanged)
         mapCanvas.sigCanvasClicked.connect(self.onCanvasClicked)
-        mapCanvas.mapTools().mtCursorLocation.sigLocationRequest[SpatialPoint, QgsMapCanvas].connect(
+        mapCanvas.mapTools().mtCursorLocation.sigLocationRequest[QgsCoordinateReferenceSystem, QgsPointXY].connect(
             self.sigCurrentLocationChanged)
 
     def _disconnectCanvasSignals(self, mapCanvas: MapCanvas):
@@ -2209,8 +2199,6 @@ class MapViewDock(QgsDockWidget):
         return QSize(self.spinBoxMapSizeX.value(),
                      self.spinBoxMapSizeY.value())
 
-    def dummySlot(self):
-        s = ""
 
     def onMapViewsRemoved(self, mapViews):
 
diff --git a/eotimeseriesviewer/mimedata.py b/eotimeseriesviewer/mimedata.py
index 5581106d7c067ebbb95913085603ae523c640587..cacf7132f03dc2ba20048137ee97e67648892b86 100644
--- a/eotimeseriesviewer/mimedata.py
+++ b/eotimeseriesviewer/mimedata.py
@@ -1,8 +1,7 @@
-
-
-from qgis.core import *
 from qgis.PyQt.QtCore import *
 from qgis.PyQt.QtXml import *
+from qgis.core import QgsReadWriteContext, QgsProject, QgsMapLayer, QgsRasterLayer, QgsVectorLayer
+from qgis.gui import QgsLayerTreeLayer, QgsLayerTreeGroup, QgsLayerTree
 import re
 from qgis.gui import *
 
@@ -60,8 +59,6 @@ def fromLayerList(mapLayers):
     return mimeData
 
 
-
-
 def toLayerList(mimeData):
     """
     Extracts a layer-tree-group from a QMimeData
@@ -77,14 +74,13 @@ def toLayerList(mimeData):
         xml = doc.toString()
         node = doc.firstChildElement(MDF_LAYERTREEMODELDATA_XML)
         context = QgsReadWriteContext()
-        #context.setPathResolver(QgsProject.instance().pathResolver())
+        # context.setPathResolver(QgsProject.instance().pathResolver())
         layerTree = QgsLayerTree.readXml(node, context)
         lt = QgsLayerTreeGroup.readXml(node, context)
-        #layerTree.resolveReferences(QgsProject.instance(), True)
+        # layerTree.resolveReferences(QgsProject.instance(), True)
         registeredLayers = QgsProject.instance().mapLayers()
 
-
-        attributesLUT= {}
+        attributesLUT = {}
         childs = node.childNodes()
 
         for i in range(childs.count()):
@@ -118,7 +114,7 @@ def toLayerList(mimeData):
             if isinstance(mapLayer, QgsMapLayer):
                 newMapLayers.append(mapLayer)
     elif MDF_URILIST in mimeData.formats():
-       pass
+        pass
     else:
         s = ""
 
@@ -139,6 +135,7 @@ def textToByteArray(text):
         data.append(text)
         return data
 
+
 def textFromByteArray(data):
     """
     Decodes a QByteArray into a str
@@ -148,4 +145,3 @@ def textFromByteArray(data):
     assert isinstance(data, QByteArray)
     s = data.data().decode()
     return s
-
diff --git a/eotimeseriesviewer/profilevisualization.py b/eotimeseriesviewer/profilevisualization.py
index ecf1eda2e2c93ccd0fed66423f61a4e1a9baa610..691706d8a4683475b64103549791598e76371b3f 100644
--- a/eotimeseriesviewer/profilevisualization.py
+++ b/eotimeseriesviewer/profilevisualization.py
@@ -20,28 +20,35 @@
 """
 # noinspection PyPep8Naming
 
-import os, sys, pickle, datetime
+import sys
+import typing
+import re
 from collections import OrderedDict
-from qgis.gui import *
-from qgis.core import *
 from qgis.PyQt.QtCore import *
 from qgis.PyQt.QtXml import *
 from qgis.PyQt.QtGui import *
+from qgis.PyQt.QtWidgets import *
+from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsPoint, QgsPointXY, \
+    QgsAttributeTableConfig, QgsMapLayerProxyModel, QgsFeature, QgsCoordinateReferenceSystem
+from qgis.gui import QgsFieldExpressionWidget, QgsFeatureListComboBox, QgsDockWidget
 
 
+import numpy as np
+
 from eotimeseriesviewer import DIR_UI
-from .timeseries import *
+from .timeseries import TimeSeries, TimeSeriesDate, SensorInstrument
 from .utils import SpatialExtent, SpatialPoint, px2geo, loadUi, nextColor
 from .externals.qps.plotstyling.plotstyling import PlotStyle, PlotStyleButton, PlotStyleDialog
 from .externals.pyqtgraph import ScatterPlotItem, SpotItem, GraphicsScene
 from .externals.qps.externals.pyqtgraph.GraphicsScene.mouseEvents import MouseClickEvent, MouseDragEvent
 from .externals import pyqtgraph as pg
+from .externals.pyqtgraph import fn
 from .externals.qps.layerproperties import AttributeTableWidget
 from .externals.qps.vectorlayertools import VectorLayerTools
 from .sensorvisualization import SensorListModel
-from .temporalprofiles import *
-
-import numpy as np
+from .temporalprofiles import TemporalProfile, num2date, date2num, TemporalProfileLayer, \
+    LABEL_EXPRESSION_2D, LABEL_TIME, sensorExampleQgsFeature, FN_ID, bandKey2bandIndex, bandIndex2bandKey, \
+    dateDOY, rxBandKey, rxBandKeyExact
 
 DEBUG = False
 OPENGL_AVAILABLE = False
@@ -49,13 +56,13 @@ ENABLE_OPENGL = False
 
 try:
     import OpenGL
+
     OPENGL_AVAILABLE = True
 
 except Exception as ex:
     print('unable to import OpenGL based packages:\n{}'.format(ex))
 
 
-
 def getTextColorWithContrast(c):
     assert isinstance(c, QColor)
     if c.lightness() < 0.5:
@@ -63,6 +70,7 @@ def getTextColorWithContrast(c):
     else:
         return QColor('black')
 
+
 def selectedModelIndices(tableView):
     assert isinstance(tableView, QTableView)
     result = {}
@@ -77,7 +85,6 @@ def selectedModelIndices(tableView):
     return result.values()
 
 
-
 class _SensorPoints(pg.PlotDataItem):
     def __init__(self, *args, **kwds):
         super(_SensorPoints, self).__init__(*args, **kwds)
@@ -86,12 +93,11 @@ class _SensorPoints(pg.PlotDataItem):
         self.menu = None
 
     def boundingRect(self):
-        return super(_SensorPoints,self).boundingRect()
+        return super(_SensorPoints, self).boundingRect()
 
     def paint(self, p, *args):
         super(_SensorPoints, self).paint(p, *args)
 
-
     # On right-click, raise the context menu
     def mouseClickEvent(self, ev):
         if ev.button() == QtCore.Qt.RightButton:
@@ -128,7 +134,7 @@ class _SensorPoints(pg.PlotDataItem):
 
             alpha = QWidgetAction(self.menu)
             alphaSlider = QSlider()
-            alphaSlider.setOrientation(QtCore.Qt.Horizontal)
+            alphaSlider.setOrientation(Qt.Horizontal)
             alphaSlider.setMaximum(255)
             alphaSlider.setValue(255)
             alphaSlider.valueChanged.connect(self.setAlpha)
@@ -140,7 +146,6 @@ class _SensorPoints(pg.PlotDataItem):
 
 
 class TemporalProfilePlotStyle(PlotStyle):
-
     sigStyleUpdated = pyqtSignal()
     sigDataUpdated = pyqtSignal()
 
@@ -167,7 +172,8 @@ class TemporalProfilePlotStyle(PlotStyle):
         """
         Returns True if this style has all information to get plotted
         """
-        return self.isVisible() and isinstance(self.temporalProfile(), TemporalProfile) and isinstance(self.sensor(), SensorInstrument)
+        return self.isVisible() and isinstance(self.temporalProfile(), TemporalProfile) and isinstance(self.sensor(),
+                                                                                                       SensorInstrument)
 
     def createPlotItem(self):
         raise NotImplementedError()
@@ -198,7 +204,7 @@ class TemporalProfilePlotStyle(PlotStyle):
 
     def expression(self) -> str:
         return self.mExpression
-    
+
     def expressionBandIndices(self) -> typing.List[int]:
         return [bandKey2bandIndex(k) for k in self.expressionBandKeys()]
 
@@ -213,7 +219,7 @@ class TemporalProfilePlotStyle(PlotStyle):
 
     def __getstate__(self):
         result = super(TemporalProfilePlotStyle, self).__getstate__()
-        #remove
+        # remove
         del result['mTP']
         del result['mSensor']
 
@@ -236,22 +242,23 @@ class TemporalProfilePlotStyle(PlotStyle):
             self.setSensor(plotStyle.sensor())
             self.setTemporalProfile(plotStyle.temporalProfile())
 
+
 class TemporalProfilePlotDataItem(pg.PlotDataItem):
 
     def __init__(self, plotStyle: TemporalProfilePlotStyle, parent=None):
         assert isinstance(plotStyle, TemporalProfilePlotStyle)
 
         super(TemporalProfilePlotDataItem, self).__init__([], [], parent=parent)
-        self.menu = None
-        #self.setFlags(QGraphicsItem.ItemIsSelectable)
-        self.mPlotStyle : TemporalProfilePlotStyle = plotStyle
+        self.menu: QMenu = None
+        # self.setFlags(QGraphicsItem.ItemIsSelectable)
+        self.mPlotStyle: TemporalProfilePlotStyle = plotStyle
         self.setAcceptedMouseButtons(Qt.LeftButton | Qt.RightButton)
         self.mPlotStyle.sigUpdated.connect(self.updateDataAndStyle)
         self.updateDataAndStyle()
 
     # On right-click, raise the context menu
     def mouseClickEvent(self, ev):
-        if ev.button() == QtCore.Qt.RightButton:
+        if ev.button() == Qt.RightButton:
             if self.raiseContextMenu(ev):
                 ev.accept()
 
@@ -263,7 +270,7 @@ class TemporalProfilePlotDataItem(pg.PlotDataItem):
         menu = self.scene().addParentContextMenus(self, menu, ev)
 
         pos = ev.screenPos()
-        menu.popup(QtCore.QPoint(pos.x(), pos.y()))
+        menu.popup(QPoint(pos.x(), pos.y()))
         return True
 
     # This method will be called when this item's _children_ want to raise
@@ -285,7 +292,7 @@ class TemporalProfilePlotDataItem(pg.PlotDataItem):
 
             alpha = QWidgetAction(self.menu)
             alphaSlider = QSlider()
-            alphaSlider.setOrientation(QtCore.Qt.Horizontal)
+            alphaSlider.setOrientation(Qt.Horizontal)
             alphaSlider.setMaximum(255)
             alphaSlider.setValue(255)
             alphaSlider.valueChanged.connect(self.setAlpha)
@@ -358,6 +365,7 @@ class TemporalProfilePlotDataItem(pg.PlotDataItem):
         pen.setWidth(width)
         self.setPen(pen)
 
+
 class PlotSettingsModel(QAbstractTableModel):
 
     def __init__(self, temporalProfileLayer: TemporalProfileLayer, parent=None, *args):
@@ -385,11 +393,11 @@ class PlotSettingsModel(QAbstractTableModel):
     def temporalProfileLayer(self) -> TemporalProfileLayer:
         return self.mTemporalProfileLayer
 
-    def onTemporalProfilesAdded(self, temporalProfiles:typing.List[TemporalProfile]):
+    def onTemporalProfilesAdded(self, temporalProfiles: typing.List[TemporalProfile]):
         if len(temporalProfiles) > 0:
             self.setCurrentProfile(temporalProfiles[-1])
 
-    def onTemporalProfilesDeleted(self, temporalProfiles:typing.List[TemporalProfile]):
+    def onTemporalProfilesDeleted(self, temporalProfiles: typing.List[TemporalProfile]):
         # remote deleted temporal profiles from plotstyles
         col = self.columnNames.index(self.cnTemporalProfile)
         rowMin = rowMax = None
@@ -408,7 +416,7 @@ class PlotSettingsModel(QAbstractTableModel):
                 [Qt.EditRole, Qt.DisplayRole]
             )
 
-    def onTemporalProfilesUpdated(self, temporalProfiles:typing.List[TemporalProfile]):
+    def onTemporalProfilesUpdated(self, temporalProfiles: typing.List[TemporalProfile]):
 
         col = self.columnNames.index(self.cnTemporalProfile)
 
@@ -481,12 +489,12 @@ class PlotSettingsModel(QAbstractTableModel):
             i = len(self.mPlotSettings)
 
         if len(plotStyles) > 0:
-            self.beginInsertRows(QModelIndex(), i, i + len(plotStyles)-1)
+            self.beginInsertRows(QModelIndex(), i, i + len(plotStyles) - 1)
             for j, plotStyle in enumerate(plotStyles):
                 assert isinstance(plotStyle, TemporalProfilePlotStyle)
-                #plotStyle.sigExpressionUpdated.connect(lambda *args, s = plotStyle: self.onStyleUpdated(s))
-                #plotStyle.sigExpressionUpdated.connect(self.sigReloadMissingBandValuesRequest.emit)
-                self.mPlotSettings.insert(i+j, plotStyle)
+                # plotStyle.sigExpressionUpdated.connect(lambda *args, s = plotStyle: self.onStyleUpdated(s))
+                # plotStyle.sigExpressionUpdated.connect(self.sigReloadMissingBandValuesRequest.emit)
+                self.mPlotSettings.insert(i + j, plotStyle)
             self.endInsertRows()
             self.checkForRequiredDataUpdates(plotStyles)
 
@@ -509,7 +517,7 @@ class PlotSettingsModel(QAbstractTableModel):
             plotStyle.setTemporalProfile(temporalProfile)
 
         if len(self) > 0:
-            lastStyle = self[0] #top style in list is the most-recent
+            lastStyle = self[0]  # top style in list is the most-recent
             assert isinstance(lastStyle, TemporalProfilePlotStyle)
             markerColor = nextColor(lastStyle.markerBrush.color())
             plotStyle.markerBrush.setColor(markerColor)
@@ -529,17 +537,16 @@ class PlotSettingsModel(QAbstractTableModel):
                 assert isinstance(plotStyle, PlotStyle)
                 if plotStyle in self.mPlotSettings:
                     idx = self.plotStyle2idx(plotStyle)
-                    self.beginRemoveRows(QModelIndex(), idx.row(),idx.row())
+                    self.beginRemoveRows(QModelIndex(), idx.row(), idx.row())
                     self.mPlotSettings.remove(plotStyle)
                     self.endRemoveRows()
 
-    def rowCount(self, parent = QModelIndex()):
+    def rowCount(self, parent=QModelIndex()):
         return len(self.mPlotSettings)
 
+    def removeRows(self, row, count, parent=QModelIndex()):
 
-    def removeRows(self, row, count , parent = QModelIndex()):
-
-        self.beginRemoveRows(parent, row, row + count-1)
+        self.beginRemoveRows(parent, row, row + count - 1)
 
         toRemove = self.mPlotSettings[row:row + count]
 
@@ -563,7 +570,7 @@ class PlotSettingsModel(QAbstractTableModel):
             return self.mPlotSettings[index.row()]
         return None
 
-    def columnCount(self, parent = QModelIndex()):
+    def columnCount(self, parent=QModelIndex()):
         return len(self.columnNames)
 
     def index(self, row: int, column: int, parent: QModelIndex = None) -> QModelIndex:
@@ -616,7 +623,7 @@ class PlotSettingsModel(QAbstractTableModel):
         columnName = self.columnNames[index.column()]
 
         result = False
-        plotStyle : TemporalProfilePlotStyle = index.data(Qt.UserRole)
+        plotStyle: TemporalProfilePlotStyle = index.data(Qt.UserRole)
 
         if isinstance(plotStyle, TemporalProfilePlotStyle):
             if role == Qt.CheckStateRole:
@@ -656,7 +663,7 @@ class PlotSettingsModel(QAbstractTableModel):
     def restorePlotSettings(self, sensor, index='DEFAULT'):
         return None
 
-    def checkForRequiredDataUpdates(self, profileStyles:typing.List[TemporalProfilePlotStyle]):
+    def checkForRequiredDataUpdates(self, profileStyles: typing.List[TemporalProfilePlotStyle]):
         if not isinstance(profileStyles, list):
             profileStyles = [profileStyles]
 
@@ -685,10 +692,11 @@ class PlotSettingsModel(QAbstractTableModel):
             flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
             if columnName in [self.cnTemporalProfile]:
                 flags = flags | Qt.ItemIsUserCheckable
-            if columnName in [self.cnTemporalProfile, self.cnSensor, self.cnExpression, self.cnStyle]: #allow check state
+            if columnName in [self.cnTemporalProfile, self.cnSensor, self.cnExpression,
+                              self.cnStyle]:  # allow check state
                 flags = flags | Qt.ItemIsEditable
             return flags
-            #return item.qt_flags(index.column())
+            # return item.qt_flags(index.column())
         return Qt.NoItemFlags
 
     def headerData(self, col, orientation, role):
@@ -700,6 +708,7 @@ class PlotSettingsModel(QAbstractTableModel):
             return str(col)
         return None
 
+
 class PlotSettingsTableView(QTableView):
 
     def __init__(self, *args, **kwds):
@@ -710,10 +719,10 @@ class PlotSettingsTableView(QTableView):
         pal.setColor(QPalette.Inactive, QPalette.Highlight, cSelected)
         self.setPalette(pal)
 
-    def sensorHasWavelengths(self, sensor:SensorInstrument) -> bool:
+    def sensorHasWavelengths(self, sensor: SensorInstrument) -> bool:
         return isinstance(sensor, SensorInstrument) and \
-                sensor.wl is not None and \
-                len(sensor.wl) > 0
+               sensor.wl is not None and \
+               len(sensor.wl) > 0
 
     def contextMenuEvent(self, event: QContextMenuEvent):
         """
@@ -723,8 +732,6 @@ class PlotSettingsTableView(QTableView):
 
         indices = self.selectionModel().selectedIndexes()
 
-
-
         if len(indices) > 0:
             refIndex = indices[0]
             assert isinstance(refIndex, QModelIndex)
@@ -763,7 +770,7 @@ class PlotSettingsTableView(QTableView):
             a.setToolTip('Show values of red band (band closest to {} nm'.format(LUT_WAVELENGTH['NIR']))
             a.setToolTip('Show values of Near Infrared (NIR) band')
             a.triggered.connect(lambda *args, exp='<NIR>':
-                                       self.onSetExpression(exp))
+                                self.onSetExpression(exp))
 
             a = m.addAction('SWIR1 Band')
             a.setToolTip('Show values of SWIR 1 band (band closest to {} nm'.format(LUT_WAVELENGTH['SWIR1']))
@@ -789,12 +796,12 @@ class PlotSettingsTableView(QTableView):
             a = m.addAction('NBR')
             a.setToolTip('Calculate the Normalized Burn Ratio (NBR)')
             a.triggered.connect(lambda *args, exp='(<NIR>-<SWIR2>)/(<NIR>+<SWIR2>)':
-                                       self.onSetExpression(exp))
+                                self.onSetExpression(exp))
 
             a = m.addAction('NBR 2')
             a.setToolTip('Calculate the Normalized Burn Ratio between two SWIR bands (NBR2)')
             a.triggered.connect(lambda *args, exp='(<SWIR1>-<SWIR2>)/(<SWIR1>+<SWIR2>)':
-                                       self.onSetExpression(exp))
+                                self.onSetExpression(exp))
 
             menu.popup(QCursor.pos())
 
@@ -834,7 +841,6 @@ class PlotSettingsTableView(QTableView):
             if '<' not in expr2:
                 self.model().setData(idx, expr2, Qt.EditRole)
 
-
     def plotSettingsModel(self) -> PlotSettingsModel:
         return self.model().sourceModel()
 
@@ -855,10 +861,12 @@ class PlotSettingsTableView(QTableView):
                     idx2 = self.model().index(idx.row(), col)
                     self.model().setData(idx2, newStyle, role=Qt.EditRole)
 
+
 class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate):
     """
 
     """
+
     def __init__(self, tableView, parent=None):
         assert isinstance(tableView, PlotSettingsTableView)
         super(PlotSettingsTableViewWidgetDelegate, self).__init__(parent=parent)
@@ -877,7 +885,7 @@ class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate):
             self.mSensorListModel = SensorListModel(self.mTemporalProfileLayer.timeSeries())
         return self.mSensorListModel
 
-    def paint(self, painter: QPainter, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex):
+    def paint(self, painter: QPainter, option: 'QStyleOptionViewItem', index: QModelIndex):
         if index.column() == 2:
             style: TemporalProfilePlotStyle = index.data(Qt.UserRole)
 
@@ -889,7 +897,7 @@ class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate):
                 label = QLabel()
                 label.setPixmap(px)
                 painter.drawPixmap(option.rect, px)
-                #QApplication.style().drawControl(QStyle.CE_CustomBase, label, painter)
+                # QApplication.style().drawControl(QStyle.CE_CustomBase, label, painter)
             else:
                 super(PlotSettingsTableViewWidgetDelegate, self).paint(painter, option, index)
         else:
@@ -916,6 +924,7 @@ class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate):
     def columnName(self, index: QModelIndex) -> str:
         assert index.isValid()
         return index.model().headerData(index.column(), Qt.Horizontal, Qt.DisplayRole)
+
     """
     def sizeHint(self, options, index):
         s = super(ExpressionDelegate, self).sizeHint(options, index)
@@ -926,6 +935,7 @@ class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate):
         s = QSize(x, s.height())
         return self._preferedSize
     """
+
     def exampleLyr(self, sensor):
         # if isinstance(sensor, SensorInstrument):
         if sensor not in self.mSensorLayers.keys():
@@ -958,7 +968,7 @@ class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate):
                     w = QgsFieldExpressionWidget(parent=parent)
                     w.setExpressionDialogTitle('Values')
                     w.setToolTip('Set an expression to specify the image band or calculate a spectral index.')
-                    #w.fieldChanged[str, bool].connect(lambda n, b: self.checkData(index, w, w.expression()))
+                    # w.fieldChanged[str, bool].connect(lambda n, b: self.checkData(index, w, w.expression()))
                     w.setExpression(plotStyle.expression())
 
                     if isinstance(plotStyle.sensor(), SensorInstrument):
@@ -968,7 +978,7 @@ class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate):
                     w = PlotStyleButton(parent=parent)
                     w.setPlotStyle(plotStyle)
                     w.setToolTip('Set style.')
-                    #w.sigPlotStyleChanged.connect(lambda ps: self.checkData(index, w, ps))
+                    # w.sigPlotStyleChanged.connect(lambda ps: self.checkData(index, w, ps))
 
                 elif cname == model.cnSensor:
                     w = QComboBox(parent=parent)
@@ -997,14 +1007,14 @@ class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate):
                     self.commitData.emit(w)
                 else:
                     s = ""
-                    #print(('Delegate commit failed',w.asExpression()))
+                    # print(('Delegate commit failed',w.asExpression()))
             if isinstance(w, PlotStyleButton):
                 self.commitData.emit(w)
 
     def setEditorData(self, editor, index):
 
         model = self.plotSettingsModel()
-        #index = self.sortFilterProxyModel().mapToSource(index)
+        # index = self.sortFilterProxyModel().mapToSource(index)
         w = None
         if index.isValid() and isinstance(model, PlotSettingsModel):
             style = index.data(Qt.UserRole)
@@ -1037,9 +1047,9 @@ class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate):
             else:
                 raise NotImplementedError()
 
-    def setModelData(self, w, model, index:QModelIndex):
+    def setModelData(self, w, model, index: QModelIndex):
         cname = self.columnName(index)
-        #model = self.plotSettingsModel()
+        # model = self.plotSettingsModel()
 
         srcModel = index.model().sourceModel()
         assert isinstance(srcModel, PlotSettingsModel)
@@ -1091,7 +1101,7 @@ class DateTimePlotWidget(pg.PlotWidget):
     A plotwidget to visualize temporal profiles
     """
 
-    def __init__(self, parent: QWidget=None):
+    def __init__(self, parent: QWidget = None):
         """
         Constructor of the widget
         """
@@ -1149,17 +1159,16 @@ class DateTimePlotWidget(pg.PlotWidget):
             self.mPlotSettingsModel.dataChanged.connect(self.onPlotSettingsChanged)
             self.mUpdateTimer.start()
 
-    def onPlotSettingsChanged(self, idx0: QModelIndex, idxe: QModelIndex, roles:list):
+    def onPlotSettingsChanged(self, idx0: QModelIndex, idxe: QModelIndex, roles: list):
         if not isinstance(self.mPlotSettingsModel, PlotSettingsModel):
             return None
         row = idx0.row()
         while row <= idxe.row():
-            style = self.mPlotSettingsModel.index(row,  0).data(Qt.UserRole)
+            style = self.mPlotSettingsModel.index(row, 0).data(Qt.UserRole)
             assert isinstance(style, TemporalProfilePlotStyle)
             self.mUpdatedProfileStyles.add(style)
             row += 1
 
-
     def setUpdateInterval(self, msec: int):
         """
         Sets the update interval
@@ -1227,7 +1236,7 @@ class DateTimePlotWidget(pg.PlotWidget):
                     assert isinstance(pdi, TemporalProfilePlotDataItem)
                     assert pdi in self.temporalProfilePlotDataItems()
                     assert pdi.isVisible()
-                    #assert len(pdi.xData) > 0
+                    # assert len(pdi.xData) > 0
                     assert len(pdi.xData) == len(pdi.yData)
 
         if len(toBeUpdated) > 0:
@@ -1236,7 +1245,6 @@ class DateTimePlotWidget(pg.PlotWidget):
                 assert isinstance(pdi, TemporalProfilePlotDataItem)
                 pdi.updateDataAndStyle()
 
-
     def resetViewBox(self):
         self.plotItem.getViewBox().autoRange()
 
@@ -1530,7 +1538,6 @@ class DateTimeViewBox(pg.ViewBox):
 
 
 class ProfileViewDock(QgsDockWidget):
-
     """
     Signalizes to move to specific date of interest
     """
@@ -1574,14 +1581,13 @@ class ProfileViewDock(QgsDockWidget):
         self.mAttributeTableToolBar.insertSeparator(before)
 
         self.mPlotToolBar.setMovable(False)
-        #self.mPlotToolBar.addActions(self.mActions2D)
-        #self.mPlotToolBar.addSeparator()
-        #self.mPlotToolBar.addActions(self.mActionsTP)
+        # self.mPlotToolBar.addActions(self.mActions2D)
+        # self.mPlotToolBar.addSeparator()
+        # self.mPlotToolBar.addActions(self.mActionsTP)
 
+        # self.pagePixel.addToolBar(self.mTemporalProfilesToolBar)
 
-        #self.pagePixel.addToolBar(self.mTemporalProfilesToolBar)
-
-        #self.mTemporalProfilesToolBar.setMovable(False)
+        # self.mTemporalProfilesToolBar.setMovable(False)
 
         config = QgsAttributeTableConfig()
         config.update(self.mTemporalProfileLayer.fields())
@@ -1607,7 +1613,7 @@ class ProfileViewDock(QgsDockWidget):
         self.delegateTableView2D = PlotSettingsTableViewWidgetDelegate(self.tableView2DProfiles)
 
         self.plot2D: DateTimePlotWidget = self.plotWidget2D
-        assert isinstance(self.plot2D, DateTimePlotWidget )
+        assert isinstance(self.plot2D, DateTimePlotWidget)
         self.plot2D.setPlotSettingsModel(self.plotSettingsModel2D)
         self.plot2D.getViewBox().sigMoveToDate.connect(self.sigMoveToDate)
         self.plot2D.getViewBox().scene().sigMouseClicked.connect(self.onPointsClicked2D)
@@ -1643,7 +1649,7 @@ class ProfileViewDock(QgsDockWidget):
     def vectorLayerTools(self) -> VectorLayerTools:
         return self.pagePixel.vectorLayerTools()
 
-    def setTimeSeries(self, timeSeries:TimeSeries):
+    def setTimeSeries(self, timeSeries: TimeSeries):
         self.temporalProfileLayer().setTimeSeries(timeSeries)
 
     def timeSeries(self) -> TimeSeries:
@@ -1732,7 +1738,7 @@ class ProfileViewDock(QgsDockWidget):
         self.actionAddStyle2D.triggered.connect(self.createNewPlotStyle2D)
         self.actionRefresh2D.triggered.connect(self.updatePlot2D)
         self.actionRemoveStyle2D.triggered.connect(
-            lambda:self.removePlotStyles2D(self.selected2DPlotStyles()))
+            lambda: self.removePlotStyles2D(self.selected2DPlotStyles()))
         self.actionLoadTPFromOgr.triggered.connect(self.onLoadFromVector)
         self.actionLoadMissingValues.triggered.connect(
             lambda *args: self.mTemporalProfileLayer.loadMissingBandInfos())
@@ -1755,7 +1761,6 @@ class ProfileViewDock(QgsDockWidget):
                 self.mTemporalProfileLayer.loadCoordinatesFromOgr(l.source())
                 break
 
-
     def onToggleEditing(self, b):
 
         if self.mTemporalProfileLayer.isEditable():
@@ -1764,7 +1769,6 @@ class ProfileViewDock(QgsDockWidget):
             self.mTemporalProfileLayer.startEditing()
         self.onEditingToggled()
 
-
     def onMoveToDate(self, date):
         dt = np.asarray([np.abs(tsd.date() - date) for tsd in self.timeSeries()])
         i = np.argmin(dt)
@@ -1798,9 +1802,8 @@ class ProfileViewDock(QgsDockWidget):
         if len(self.mTemporalProfileLayer) == 1 and len(self.plotSettingsModel2D) == 0:
             self.createNewPlotStyle2D()
 
-    @QtCore.pyqtSlot()
+    @pyqtSlot()
     def updatePlot2D(self):
         if isinstance(self.plotSettingsModel2D, PlotSettingsModel):
             self.plot2D.mUpdatedProfileStyles.update(self.plotSettingsModel2D[:])
             self.plot2D.updateTemporalProfilePlotItems()
-
diff --git a/eotimeseriesviewer/sensorvisualization.py b/eotimeseriesviewer/sensorvisualization.py
index e0d6f9a48eb168b4f778423881bec60f17e6b553..774c7c6461eed82dbbf284953a3770c5d3ac3de3 100644
--- a/eotimeseriesviewer/sensorvisualization.py
+++ b/eotimeseriesviewer/sensorvisualization.py
@@ -20,14 +20,15 @@
 """
 # noinspection PyPep8Naming
 
-from eotimeseriesviewer import DIR_UI
-from eotimeseriesviewer.timeseries import TimeSeries, SensorInstrument, TimeSeriesDate
-from eotimeseriesviewer.utils import loadUi
 from qgis.PyQt.QtCore import *
 from qgis.PyQt.QtGui import *
 from qgis.PyQt.QtWidgets import *
 from qgis.gui import QgsDockWidget
 
+from eotimeseriesviewer import DIR_UI
+from eotimeseriesviewer.timeseries import TimeSeries, SensorInstrument, TimeSeriesDate
+from eotimeseriesviewer.utils import loadUi
+
 
 class SensorDockUI(QgsDockWidget):
     def __init__(self, parent=None):
@@ -35,6 +36,8 @@ class SensorDockUI(QgsDockWidget):
         loadUi(DIR_UI / 'sensordock.ui', self)
 
         self.TS = None
+        self.mSensorModel: SensorTableModel = None
+        self.mSortedModel: QSortFilterProxyModel = QSortFilterProxyModel()
 
     def setTimeSeries(self, timeSeries):
         from eotimeseriesviewer.timeseries import TimeSeries
@@ -42,11 +45,9 @@ class SensorDockUI(QgsDockWidget):
         assert isinstance(timeSeries, TimeSeries)
         self.TS = timeSeries
         self.mSensorModel = SensorTableModel(self.TS)
-        self.mSortedModel = QSortFilterProxyModel()
         self.mSortedModel.setSourceModel(self.mSensorModel)
         self.sensorView.setModel(self.mSortedModel)
         self.sensorView.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents)
-        s = ""
 
 
 class SensorTableModel(QAbstractTableModel):
diff --git a/eotimeseriesviewer/stackedbandinput.py b/eotimeseriesviewer/stackedbandinput.py
index 4838c4263aa3f0ad53dd4c97728e60b8b1c28e4b..acd02883c41ac40d314781d69b8d64a014cb5560 100644
--- a/eotimeseriesviewer/stackedbandinput.py
+++ b/eotimeseriesviewer/stackedbandinput.py
@@ -19,15 +19,25 @@
 *                                                                         *
 ***************************************************************************
 """
-
-
-from .utils import *
-from .virtualrasters import *
-from .dateparser import *
+import os
+import copy
+from osgeo import gdal
+import numpy as np
+from collections import OrderedDict
+from xml.etree import ElementTree
+from qgis.PyQt.QtCore import Qt, QModelIndex, QAbstractTableModel, QItemSelectionModel, QTimer
+from qgis.PyQt.QtGui import QColor
+from qgis.PyQt.QtWidgets import QHeaderView, QDialog, QDialogButtonBox, QFileDialog
+from qgis.core import QgsRasterLayer, QgisInterface, QgsProviderRegistry, QgsProject
+from qgis.gui import QgsFileWidget
+import qgis.utils
+from .utils import read_vsimem, loadUi
+from .virtualrasters import VRTRaster, VRTRasterBand, VRTRasterInputSourceBand
 from eotimeseriesviewer import DIR_UI
+from eotimeseriesviewer.dateparser import extractDateTimeGroup
 
-def datesFromDataset(dataset:gdal.Dataset) -> list:
 
+def datesFromDataset(dataset: gdal.Dataset) -> list:
     nb = dataset.RasterCount
 
     def checkDates(dateList):
@@ -50,7 +60,7 @@ def datesFromDataset(dataset:gdal.Dataset) -> list:
     searchedKeysBand.append(re.compile('date$', re.I))
     searchedKeysBand.append(re.compile('wavelength$', re.I))
 
-    #1. Check Metadata
+    # 1. Check Metadata
     for domain in dataset.GetMetadataDomainList():
         domainData = dataset.GetMetadata_Dict(domain)
         assert isinstance(domainData, dict)
@@ -64,10 +74,9 @@ def datesFromDataset(dataset:gdal.Dataset) -> list:
                     if checkDates(dateValues):
                         return dateValues
 
-
     # 2. Search in band metadata
     # 2.1. via GetDescription
-    bandDates = [extractDateTimeGroup(dataset.GetRasterBand(b+1).GetDescription()) for b in range(nb)]
+    bandDates = [extractDateTimeGroup(dataset.GetRasterBand(b + 1).GetDescription()) for b in range(nb)]
     bandDates = [b for b in bandDates if isinstance(b, np.datetime64)]
     if checkDates(bandDates):
         return bandDates
@@ -75,7 +84,7 @@ def datesFromDataset(dataset:gdal.Dataset) -> list:
     # 2.2 via Band Metadata
     bandDates = []
     for b in range(nb):
-        band = dataset.GetRasterBand(b+1)
+        band = dataset.GetRasterBand(b + 1)
         assert isinstance(band, gdal.Band)
         bandDate = None
         for domain in band.GetMetadataDomainList():
@@ -104,17 +113,17 @@ def datesFromDataset(dataset:gdal.Dataset) -> list:
     if checkDates(bandDates):
         return bandDates
 
-
     return []
 
+
 class InputStackInfo(object):
 
     def __init__(self, dataset):
         if isinstance(dataset, str):
-            #test ENVI header first
+            # test ENVI header first
             basename = os.path.splitext(dataset)[0]
             ds = None
-            if os.path.isfile(basename+'.hdr'):
+            if os.path.isfile(basename + '.hdr'):
                 ds = gdal.OpenEx(dataset, allowed_drivers=['ENVI'])
             if not isinstance(ds, gdal.Dataset):
                 ds = gdal.Open(dataset)
@@ -152,12 +161,11 @@ class InputStackInfo(object):
         self.nodatavalues = []
 
         for b in range(self.nb):
-            band = dataset.GetRasterBand(b+1)
+            band = dataset.GetRasterBand(b + 1)
             assert isinstance(band, gdal.Band)
             self.bandnames.append(band.GetDescription())
             self.nodatavalues.append(band.GetNoDataValue())
 
-
         self.mDates = datesFromDataset(dataset)
 
     def __len__(self):
@@ -167,7 +175,6 @@ class InputStackInfo(object):
         """Returns a list of dates"""
         return self.mDates
 
-
     def structure(self):
         return (self.ns, self.nl, self.nb, self.gt, self.wkt)
 
@@ -183,21 +190,17 @@ class OutputVRTDescription(object):
     Descrbies an output VRT
     """
 
-    def __init__(self, path:str, date:np.datetime64):
+    def __init__(self, path: str, date: np.datetime64):
         super(OutputVRTDescription, self).__init__()
         self.mPath = path
         self.mDate = date
 
-
-    def setPath(self, path:str):
+    def setPath(self, path: str):
         self.mPath = path
 
 
-
 class InputStackTableModel(QAbstractTableModel):
 
-
-
     def __init__(self, parent=None):
 
         super(InputStackTableModel, self).__init__(parent)
@@ -211,7 +214,8 @@ class InputStackTableModel(QAbstractTableModel):
         self.cn_nb = 'nb'
         self.cn_name = 'Band Name'
         self.cn_wl = 'Wavelength'
-        self.mColumnNames = [self.cn_source, self.cn_dates, self.cn_name, self.cn_wl, self.cn_ns, self.cn_nl, self.cn_nb, self.cn_crs]
+        self.mColumnNames = [self.cn_source, self.cn_dates, self.cn_name, self.cn_wl, self.cn_ns, self.cn_nl,
+                             self.cn_nb, self.cn_crs]
 
         self.mColumnTooltips = {}
 
@@ -241,7 +245,7 @@ class InputStackTableModel(QAbstractTableModel):
         :return: [all dates], [dates in common]
         """
         if len(self) == 0:
-            return [],[]
+            return [], []
         datesTotal = set()
         datesInCommon = None
         for i, f in enumerate(self.mStackImages):
@@ -261,11 +265,11 @@ class InputStackTableModel(QAbstractTableModel):
         if index.isValid():
             columnName = self.columnName(index)
             flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
-            if columnName in [self.cn_name, self.cn_wl]: #allow check state
+            if columnName in [self.cn_name, self.cn_wl]:  # allow check state
                 flags = flags | Qt.ItemIsEditable
 
             return flags
-            #return item.qt_flags(index.column())
+            # return item.qt_flags(index.column())
         return None
 
     def headerData(self, col, orientation, role):
@@ -303,15 +307,15 @@ class InputStackTableModel(QAbstractTableModel):
         infos = [InputStackInfo(p) for p in paths]
         if len(infos) > 0:
 
-            self.beginInsertRows(QModelIndex(), i, i+len(infos)-1)
+            self.beginInsertRows(QModelIndex(), i, i + len(infos) - 1)
             for j, info in enumerate(infos):
                 assert isinstance(info, InputStackInfo)
                 if len(info.outputBandName) == 0:
-                    info.outputBandName = 'Band {}'.format(i+j+1)
-                self.mStackImages.insert(i+j, info)
+                    info.outputBandName = 'Band {}'.format(i + j + 1)
+                self.mStackImages.insert(i + j, info)
             self.endInsertRows()
 
-    def removeSources(self, stackInfos:list):
+    def removeSources(self, stackInfos: list):
 
         for stackInfo in stackInfos:
             assert stackInfo in self.mStackImages
@@ -332,7 +336,7 @@ class InputStackTableModel(QAbstractTableModel):
         ref = self.mStackImages[0]
         assert isinstance(ref, InputStackInfo)
 
-        #all input stacks need to have the same characteristic
+        # all input stacks need to have the same characteristic
         for stackInfo in self.mStackImages[1:]:
             assert isinstance(stackInfo, InputStackInfo)
             if not ref.dates() == stackInfo.dates():
@@ -341,13 +345,12 @@ class InputStackTableModel(QAbstractTableModel):
                 return False
         return True
 
-
-    def index2info(self, index:QModelIndex) -> InputStackInfo:
+    def index2info(self, index: QModelIndex) -> InputStackInfo:
         return self.mStackImages[index.row()]
 
-    def info2index(self, info:InputStackInfo) -> QModelIndex:
+    def info2index(self, info: InputStackInfo) -> QModelIndex:
         r = self.mStackImages.index(info)
-        return self.createIndex(r,0, info)
+        return self.createIndex(r, 0, info)
 
     def data(self, index: QModelIndex, role: int):
         if not index.isValid():
@@ -372,7 +375,6 @@ class InputStackTableModel(QAbstractTableModel):
                             dates = dates[0:10] + ['...']
                         return '\n'.join([str(d) for d in dates])
 
-
             if cname == self.cn_ns:
                 return info.ns
             if cname == self.cn_nl:
@@ -426,12 +428,12 @@ class InputStackTableModel(QAbstractTableModel):
             self.dataChanged.emit(index, index)
         return changed
 
+
 class OutputImageModel(QAbstractTableModel):
 
     def __init__(self, parent=None):
         super(OutputImageModel, self).__init__(parent)
 
-
         self.cn_uri = 'Path'
         self.cn_date = 'Date'
         self.mOutputImages = []
@@ -446,8 +448,6 @@ class OutputImageModel(QAbstractTableModel):
         self.mOutputDir = '/vsimem/'
         self.mOutputPrefix = 'date'
 
-
-
     def headerData(self, col, orientation, role):
         if Qt is None:
             return None
@@ -461,7 +461,7 @@ class OutputImageModel(QAbstractTableModel):
             return col
         return None
 
-    def createVRTUri(self, date:np.datetime64):
+    def createVRTUri(self, date: np.datetime64):
 
         path = os.path.join(self.mOutputDir, self.mOutputPrefix)
         path = '{}{}.vrt'.format(path, date)
@@ -473,7 +473,7 @@ class OutputImageModel(QAbstractTableModel):
         self.mOutputImages = []
         self.endRemoveRows()
 
-    def setMultiStackSources(self, listOfInputStacks:list, dates:list):
+    def setMultiStackSources(self, listOfInputStacks: list, dates: list):
 
         self.clearOutputs()
 
@@ -490,13 +490,13 @@ class OutputImageModel(QAbstractTableModel):
         self.masterVRT_DateLookup.clear()
         self.masterVRT_InputStacks = listOfInputStacks
         self.masterVRT_SourceBandTemplates.clear()
-        #dates = set()
-        #for s in listOfInputStacks:
+        # dates = set()
+        # for s in listOfInputStacks:
         #    for d in s.dates():
         #        dates.add(d)
-        #dates = sorted(list(dates))
+        # dates = sorted(list(dates))
 
-        #create a LUT to get the stack indices for a related date (not each stack might contain a band for each date)
+        # create a LUT to get the stack indices for a related date (not each stack might contain a band for each date)
 
         for stackIndex, s in enumerate(listOfInputStacks):
             for bandIndex, bandDate in enumerate(s.dates()):
@@ -504,7 +504,7 @@ class OutputImageModel(QAbstractTableModel):
                     self.masterVRT_DateLookup[bandDate] = []
                 self.masterVRT_DateLookup[bandDate].append((stackIndex, bandIndex))
 
-        #create VRT Template XML
+        # create VRT Template XML
         VRT = VRTRaster()
         wavelength = []
         for stackIndex, stack in enumerate(listOfInputStacks):
@@ -525,7 +525,7 @@ class OutputImageModel(QAbstractTableModel):
             dsVRT.SetMetadataItem('wavelength units', 'Nanometers')
 
         for stackIndex, stack in enumerate(listOfInputStacks):
-            band = dsVRT.GetRasterBand(stackIndex+1)
+            band = dsVRT.GetRasterBand(stackIndex + 1)
             assert isinstance(band, gdal.Band)
             assert isinstance(stack, InputStackInfo)
             if isinstance(stack.colorTable, gdal.ColorTable) and stack.colorTable.GetCount() > 0:
@@ -539,10 +539,9 @@ class OutputImageModel(QAbstractTableModel):
         drv.Delete(pathVSITmp)
         outputVRTs = []
 
-
         eTree = ElementTree.fromstring(masterVRT_XML)
         for iBand, elemBand in enumerate(eTree.findall('VRTRasterBand')):
-            sourceElements  = elemBand.findall('ComplexSource') + elemBand.findall('SimpleSource')
+            sourceElements = elemBand.findall('ComplexSource') + elemBand.findall('SimpleSource')
             assert len(sourceElements) == 1
             self.masterVRT_SourceBandTemplates[iBand] = copy.deepcopy(sourceElements[0])
             elemBand.remove(sourceElements[0])
@@ -555,30 +554,28 @@ class OutputImageModel(QAbstractTableModel):
 
         self.masterVRT_XML = eTree
 
-
-        self.beginInsertRows(QModelIndex(), 0, len(outputVRTs)-1)
+        self.beginInsertRows(QModelIndex(), 0, len(outputVRTs) - 1)
         self.mOutputImages = outputVRTs[:]
         self.endInsertRows()
 
-    def setOutputDir(self, path:str):
+    def setOutputDir(self, path: str):
         self.mOutputDir = path
         self.updateOutputURIs()
 
-    def setOutputPrefix(self, basename:str):
+    def setOutputPrefix(self, basename: str):
         self.mOutputPrefix = basename
         self.updateOutputURIs()
 
     def updateOutputURIs(self):
         c = self.mColumnNames.index(self.cn_uri)
         ul = self.createIndex(0, c)
-        lr = self.createIndex(self.rowCount()-1, c)
+        lr = self.createIndex(self.rowCount() - 1, c)
 
         for outputVRT in self:
             assert isinstance(outputVRT, OutputVRTDescription)
             outputVRT.setPath(self.createVRTUri(outputVRT.mDate))
         self.dataChanged.emit(ul, lr)
 
-
     def __len__(self):
         return len(self.mOutputImages)
 
@@ -596,14 +593,14 @@ class OutputImageModel(QAbstractTableModel):
             i = i.column()
         return self.mColumnNames[i]
 
-    def columnIndex(self, columnName:str) ->  QModelIndex:
+    def columnIndex(self, columnName: str) -> QModelIndex:
         c = self.mColumnNames.index(columnName)
         return self.createIndex(0, c)
 
-    def index2vrt(self, index:QModelIndex) -> OutputVRTDescription:
+    def index2vrt(self, index: QModelIndex) -> OutputVRTDescription:
         return self.mOutputImages[index.row()]
 
-    def vrt2index(self, vrt:OutputVRTDescription) -> QModelIndex:
+    def vrt2index(self, vrt: OutputVRTDescription) -> QModelIndex:
         i = self.mOutputImages[vrt]
         return self.createIndex(i, 0, vrt)
 
@@ -620,7 +617,7 @@ class OutputImageModel(QAbstractTableModel):
             if cname == self.cn_date:
                 return str(vrt.mDate)
 
-    def vrtXML(self, outputDefinition:OutputVRTDescription, asElementTree=False) -> str:
+    def vrtXML(self, outputDefinition: OutputVRTDescription, asElementTree=False) -> str:
         """
         Create the VRT XML related to an outputDefinition
         :param outputDefinition:
@@ -632,7 +629,7 @@ class OutputImageModel(QAbstractTableModel):
         # xml = copy.deepcopy(eTree)
         if self.masterVRT_XML is None:
             return None
-        #xmlTree = ElementTree.fromstring(self.masterVRT_XML)
+        # xmlTree = ElementTree.fromstring(self.masterVRT_XML)
         xmlTree = copy.deepcopy(self.masterVRT_XML)
 
         # set metadata
@@ -649,7 +646,7 @@ class OutputImageModel(QAbstractTableModel):
             stackIndex, stackBandIndex = t
 
             stackSourceXMLTemplate = copy.deepcopy(self.masterVRT_SourceBandTemplates[stackIndex])
-            stackSourceXMLTemplate.find('SourceBand').text = str(stackBandIndex+1)
+            stackSourceXMLTemplate.find('SourceBand').text = str(stackBandIndex + 1)
             xmlVRTBands[stackIndex].append(stackSourceXMLTemplate)
 
         if asElementTree:
@@ -658,9 +655,6 @@ class OutputImageModel(QAbstractTableModel):
             return ElementTree.tostring(xmlTree).decode('utf-8')
 
 
-
-
-
 class StackedBandInputDialog(QDialog):
 
     def __init__(self, parent=None):
@@ -698,7 +692,7 @@ class StackedBandInputDialog(QDialog):
         sm = self.tableViewSourceStacks.selectionModel()
         assert isinstance(sm, QItemSelectionModel)
         sm.selectionChanged.connect(self.onSourceStackSelectionChanged)
-        self.onSourceStackSelectionChanged([],[])
+        self.onSourceStackSelectionChanged([], [])
 
         sm = self.tableViewOutputImages.selectionModel()
         assert isinstance(sm, QItemSelectionModel)
@@ -808,7 +802,6 @@ class StackedBandInputDialog(QDialog):
         """
         self.actionRemoveSourceStack.setEnabled(len(selected) > 0)
 
-
     def onOutputImageSelectionChanged(self, selected, deselected):
 
         if len(selected) > 0:
@@ -822,14 +815,12 @@ class StackedBandInputDialog(QDialog):
             self.tbXMLPreview.setPlainText(None)
             s = ""
 
-
     def saveImages(self):
         """
         Write the VRT images
         :return: [list-of-written-file-paths]
         """
 
-
         nTotal = len(self.tableModelOutputImages)
         writtenFiles = []
         if nTotal == 0:
@@ -859,4 +850,4 @@ class StackedBandInputDialog(QDialog):
             mapLayers = [QgsRasterLayer(p) for p in writtenFiles]
             QgsProject.instance().addMapLayers(mapLayers, addToLegend=True)
         self.mWrittenFiles.extend(writtenFiles)
-        return writtenFiles
\ No newline at end of file
+        return writtenFiles
diff --git a/eotimeseriesviewer/systeminfo.py b/eotimeseriesviewer/systeminfo.py
index d8c5db002bc7f51fb073afa06ee55c47b75e3c15..404d43195241749a7a80807c2ca9c7210253502e 100644
--- a/eotimeseriesviewer/systeminfo.py
+++ b/eotimeseriesviewer/systeminfo.py
@@ -20,8 +20,10 @@
 """
 # noinspection PyPep8Naming
 
-import sys, os, re
-from qgis.core import *
+import sys
+import os
+import re
+from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsProject
 from collections import OrderedDict
 from qgis.gui import QgsDockWidget
 from qgis.PyQt.QtCore import *
@@ -35,282 +37,278 @@ from eotimeseriesviewer.utils import loadUi, SpatialExtent
 PSUTIL_AVAILABLE = False
 try:
     import psutil
+
     PSUTIL_AVAILABLE = True
 except:
     pass
 
-def value2str(args, separator = ''):
-    return str(args)
 
-class MapLayerRegistryModel(QAbstractTableModel):
-
-        class LayerWrapper(object):
-            def __init__(self, lyr):
-                assert isinstance(lyr, QgsMapLayer)
-                self.lyr = lyr
+def value2str(args, separator=''):
+    return str(args)
 
-        def __init__(self, parent=None):
-            super(MapLayerRegistryModel, self).__init__(parent)
 
-            self.cID = '#'
-            self.cPID = 'PID'
-            self.cName = 'Name'
-            self.cSrc = 'Uri'
-            self.cType= 'Type'
+class MapLayerRegistryModel(QAbstractTableModel):
+    class LayerWrapper(object):
+        def __init__(self, lyr):
+            assert isinstance(lyr, QgsMapLayer)
+            self.lyr = lyr
 
-            self.mLayers = list()
-            self.REG = QgsProject.instance()
-            self.REG.layersAdded.connect(self.addLayers)
-            self.REG.layersWillBeRemoved.connect(self.removeLayers)
-            self.addLayers(self.REG.mapLayers().values())
+    def __init__(self, parent=None):
+        super(MapLayerRegistryModel, self).__init__(parent)
 
+        self.cID = '#'
+        self.cPID = 'PID'
+        self.cName = 'Name'
+        self.cSrc = 'Uri'
+        self.cType = 'Type'
 
-            s = ""
+        self.mLayers = list()
+        self.REG = QgsProject.instance()
+        self.REG.layersAdded.connect(self.addLayers)
+        self.REG.layersWillBeRemoved.connect(self.removeLayers)
+        self.addLayers(self.REG.mapLayers().values())
 
-        def addLayers(self, lyrs):
+        s = ""
 
-            lyrs = [l for l in lyrs if isinstance(l, QgsMapLayer)]
+    def addLayers(self, lyrs):
 
-            l = len(lyrs)
+        lyrs = [l for l in lyrs if isinstance(l, QgsMapLayer)]
 
-            if l > 0:
-                i = len(self.mLayers)
-                self.beginInsertRows(QModelIndex(),i, i+l)
-                self.mLayers.extend(lyrs)
-                self.endInsertRows()
+        l = len(lyrs)
 
-        #@pyqtSlot(list)
-        def removeLayers(self, lyrNames):
+        if l > 0:
+            i = len(self.mLayers)
+            self.beginInsertRows(QModelIndex(), i, i + l)
+            self.mLayers.extend(lyrs)
+            self.endInsertRows()
 
+    # @pyqtSlot(list)
+    def removeLayers(self, lyrNames):
 
-            to_remove = [self.REG.mapLayer(name) for name in lyrNames]
+        to_remove = [self.REG.mapLayer(name) for name in lyrNames]
 
-            for l in to_remove:
-                if l in self.mLayers:
-                    i = self.mLayers.index(l)
-                    self.beginRemoveRows(QModelIndex(),i,i)
-                    self.mLayers.remove(l)
-                    self.endRemoveRows()
-            #self.reset()
+        for l in to_remove:
+            if l in self.mLayers:
+                i = self.mLayers.index(l)
+                self.beginRemoveRows(QModelIndex(), i, i)
+                self.mLayers.remove(l)
+                self.endRemoveRows()
+        # self.reset()
 
-        def columnNames(self):
-            return [self.cID, self.cPID, self.cName, self.cType, self.cSrc]
+    def columnNames(self):
+        return [self.cID, self.cPID, self.cName, self.cType, self.cSrc]
 
-        def headerData(self, col, orientation, role):
-            if orientation == Qt.Horizontal and role == Qt.DisplayRole:
-                return self.columnNames()[col]
-            elif orientation == Qt.Vertical and role == Qt.DisplayRole:
-                return col
-            return None
+    def headerData(self, col, orientation, role):
+        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
+            return self.columnNames()[col]
+        elif orientation == Qt.Vertical and role == Qt.DisplayRole:
+            return col
+        return None
 
-        def sort(self, col, order):
-            """Sort table by given column number.
+    def sort(self, col, order):
+        """Sort table by given column number.
             """
-            self.layoutAboutToBeChanged.emit()
-            columnName = self.columnNames()[col]
-            rev = order == Qt.DescendingOrder
-            sortedLyers = None
-
-            if columnName == self.cName:
-                sortedLyers = sorted(self.mLayers, key=lambda l: l.name(), reverse=rev)
-            elif columnName == self.cSrc:
-                sortedLyers = sorted(self.mLayers, key=lambda l: l.source(), reverse=rev)
-            elif columnName == self.cID:
-                lyrs = self.REG.mapLayers().values()
-                sortedLyers = sorted(self.mLayers, key=lambda l: lyrs.index(l), reverse=rev)
-            elif columnName == self.cPID:
-                sortedLyers = sorted(self.mLayers, key=lambda l: id(l), reverse=rev)
-            elif columnName == self.cType:
-                types = [QgsVectorLayer, QgsRasterLayer]
-                sortedLyers = sorted(self.mLayers, key=lambda l: types.index(type(l)), reverse=rev)
-
-            del self.mLayers[:]
-            self.mLayers.extend(sortedLyers)
-            self.layoutChanged.emit()
-
-        def rowCount(self, parentIdx=None, *args, **kwargs):
-            return len(self.mLayers)
+        self.layoutAboutToBeChanged.emit()
+        columnName = self.columnNames()[col]
+        rev = order == Qt.DescendingOrder
+        sortedLyers = None
+
+        if columnName == self.cName:
+            sortedLyers = sorted(self.mLayers, key=lambda l: l.name(), reverse=rev)
+        elif columnName == self.cSrc:
+            sortedLyers = sorted(self.mLayers, key=lambda l: l.source(), reverse=rev)
+        elif columnName == self.cID:
+            lyrs = self.REG.mapLayers().values()
+            sortedLyers = sorted(self.mLayers, key=lambda l: lyrs.index(l), reverse=rev)
+        elif columnName == self.cPID:
+            sortedLyers = sorted(self.mLayers, key=lambda l: id(l), reverse=rev)
+        elif columnName == self.cType:
+            types = [QgsVectorLayer, QgsRasterLayer]
+            sortedLyers = sorted(self.mLayers, key=lambda l: types.index(type(l)), reverse=rev)
+
+        del self.mLayers[:]
+        self.mLayers.extend(sortedLyers)
+        self.layoutChanged.emit()
+
+    def rowCount(self, parentIdx=None, *args, **kwargs):
+        return len(self.mLayers)
+
+    def columnCount(self, QModelIndex_parent=None, *args, **kwargs):
+        return len(self.columnNames())
+
+    def lyr2idx(self, lyr):
+        assert isinstance(lyr, QgsMapLayer)
+        # return self.createIndex(self.mSpecLib.index(profile), 0)
+        # pw = self.mProfileWrappers[profile]
+        if not lyr in self.mLayers:
+            return None
+        return self.createIndex(self.mLayers.index(lyr), 0)
 
-        def columnCount(self, QModelIndex_parent=None, *args, **kwargs):
-            return len(self.columnNames())
+    def idx2lyr(self, index):
+        assert isinstance(index, QModelIndex)
+        if not index.isValid():
+            return None
+        return self.mLayers[index.row()]
 
-        def lyr2idx(self, lyr):
-            assert isinstance(lyr, QgsMapLayer)
-            # return self.createIndex(self.mSpecLib.index(profile), 0)
-            # pw = self.mProfileWrappers[profile]
-            if not lyr in self.mLayers:
-                return None
-            return self.createIndex(self.mLayers.index(lyr), 0)
+    def idx2lyrs(self, indices):
+        lyrs = [self.idx2lyr(i) for i in indices]
+        return [l for l in lyrs if isinstance(l, QgsMapLayer)]
 
-        def idx2lyr(self, index):
-            assert isinstance(index, QModelIndex)
-            if not index.isValid():
-                return None
-            return self.mLayers[index.row()]
+    def data(self, index, role=Qt.DisplayRole):
+        if role is None or not index.isValid():
+            return None
 
+        columnName = self.columnNames()[index.column()]
+        lyr = self.idx2lyr(index)
+        value = None
+        assert isinstance(lyr, QgsMapLayer)
+        if role == Qt.DisplayRole:
+            if columnName == self.cPID:
+                value = id(lyr)
+            elif columnName == self.cID:
+                value = list(self.REG.mapLayers().values()).index(lyr)
+            elif columnName == self.cName:
+                value = lyr.name()
+            elif columnName == self.cSrc:
+                value = lyr.source()
+            elif columnName in self.cType:
+                value = re.sub('[\'<>]', '', str(type(lyr))).split('.')[-1]
 
-        def idx2lyrs(self, indices):
-            lyrs = [self.idx2lyr(i) for i in indices]
-            return [l for l in lyrs if isinstance(l, QgsMapLayer)]
+        if role == Qt.UserRole:
+            value = lyr
 
-        def data(self, index, role=Qt.DisplayRole):
-            if role is None or not index.isValid():
-                return None
+        return value
 
+    def flags(self, index):
+        if index.isValid():
             columnName = self.columnNames()[index.column()]
-            lyr = self.idx2lyr(index)
-            value = None
-            assert isinstance(lyr, QgsMapLayer)
-            if role == Qt.DisplayRole:
-                if columnName == self.cPID:
-                    value = id(lyr)
-                elif columnName == self.cID:
-                    value = list(self.REG.mapLayers().values()).index(lyr)
-                elif columnName == self.cName:
-                    value = lyr.name()
-                elif columnName == self.cSrc:
-                    value = lyr.source()
-                elif columnName in self.cType:
-                    value = re.sub('[\'<>]','',str(type(lyr))).split('.')[-1]
-
-            if role == Qt.UserRole:
-                value = lyr
-
-            return value
-
-        def flags(self, index):
-            if index.isValid():
-                columnName = self.columnNames()[index.column()]
-                flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
-                return flags
-            return None
-
+            flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
+            return flags
+        return None
 
 
 class DataLoadingModel(QAbstractTableModel):
 
-        def __init__(self, parent=None):
-            super(DataLoadingModel, self).__init__(parent)
-
-            self.cName = 'Type'
-            self.cSamples = 'n'
-            self.cAvgAll = u'mean \u0394t(all) [ms]'
-            self.cMaxAll = u'max \u0394t(all) [ms]'
-            self.cAvg10 = u'mean \u0394t(10) [ms]'
-            self.cMax10 = u'max \u0394t(10) [ms]'
-            self.cLast = u'last \u0394t [ms]'
-
-            self.mCacheSize = 500
-            self.mLoadingTimes = OrderedDict()
-
-        def addTimeDelta(self, name, timedelta):
-            assert isinstance(timedelta, np.timedelta64)
-            #if timedelta.astype(float) > 0:
-            #print(timedelta)
-            if name not in self.mLoadingTimes.keys():
-                self.mLoadingTimes[name] = []
-            to_remove = max(0,len(self.mLoadingTimes[name]) + 1 - self.mCacheSize)
-            if to_remove > 0:
-                del self.mLoadingTimes[name][0:to_remove]
-            self.mLoadingTimes[name].append(timedelta)
-
+    def __init__(self, parent=None):
+        super(DataLoadingModel, self).__init__(parent)
+
+        self.cName = 'Type'
+        self.cSamples = 'n'
+        self.cAvgAll = u'mean \u0394t(all) [ms]'
+        self.cMaxAll = u'max \u0394t(all) [ms]'
+        self.cAvg10 = u'mean \u0394t(10) [ms]'
+        self.cMax10 = u'max \u0394t(10) [ms]'
+        self.cLast = u'last \u0394t [ms]'
+
+        self.mCacheSize = 500
+        self.mLoadingTimes = OrderedDict()
+
+    def addTimeDelta(self, name, timedelta):
+        assert isinstance(timedelta, np.timedelta64)
+        # if timedelta.astype(float) > 0:
+        # print(timedelta)
+        if name not in self.mLoadingTimes.keys():
+            self.mLoadingTimes[name] = []
+        to_remove = max(0, len(self.mLoadingTimes[name]) + 1 - self.mCacheSize)
+        if to_remove > 0:
+            del self.mLoadingTimes[name][0:to_remove]
+        self.mLoadingTimes[name].append(timedelta)
+
+        self.layoutChanged.emit()
+
+    def variableNames(self):
+        return [self.cName, self.cSamples, self.cLast, self.cMaxAll, self.cAvgAll, self.cMax10, self.cAvg10]
+
+    def headerData(self, col, orientation, role):
+        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
+            return self.variableNames()[col]
+        elif orientation == Qt.Vertical and role == Qt.DisplayRole:
+            return col
+        return None
+
+    def sort(self, col, order):
+        """Sort table by given column number.
+            """
+        self.layoutAboutToBeChanged.emit()
+        columnName = self.variableNames()[col]
+        rev = order == Qt.DescendingOrder
+        sortedNames = None
+        if columnName == self.cName:
+            sortedNames = sorted(self.mLoadingTimes.keys(), reverse=rev)
+        elif columnName == self.cSamples:
+            sortedNames = sorted(self.mLoadingTimes.keys(), key=lambda n: len(self.mLoadingTimes[n]), reverse=rev)
+        elif columnName == self.cAvgAll:
+            sortedNames = sorted(self.mLoadingTimes.keys(), key=lambda name:
+            np.asarray(self.mLoadingTimes[name]).mean(), reverse=rev)
+        elif columnName == self.cAvg10:
+            sortedNames = sorted(self.mLoadingTimes.keys(), key=lambda name:
+            np.asarray(self.mLoadingTimes[name][-10:]).mean(), reverse=rev)
+
+        if sortedNames is not None:
+            tmp = OrderedDict([(name, self.mLoadingTimes[name]) for name in sortedNames])
+            self.mLoadingTimes.clear()
+            self.mLoadingTimes.update(tmp)
             self.layoutChanged.emit()
 
-        def variableNames(self):
-            return [self.cName, self.cSamples, self.cLast, self.cMaxAll, self.cAvgAll, self.cMax10, self.cAvg10]
+    def rowCount(self, parentIdx=None, *args, **kwargs):
+        return len(self.mLoadingTimes)
+
+    def columnCount(self, QModelIndex_parent=None, *args, **kwargs):
+        return len(self.variableNames())
 
-        def headerData(self, col, orientation, role):
-            if orientation == Qt.Horizontal and role == Qt.DisplayRole:
-                return self.variableNames()[col]
-            elif orientation == Qt.Vertical and role == Qt.DisplayRole:
-                return col
+    def type2idx(self, type):
+        assert isinstance(type, str)
+        if type not in self.mLoadingTimes.keys():
             return None
+        return self.createIndex(self.mLoadingTimes.keys().index(type), 0)
 
-        def sort(self, col, order):
-            """Sort table by given column number.
-            """
-            self.layoutAboutToBeChanged.emit()
-            columnName = self.variableNames()[col]
-            rev = order == Qt.DescendingOrder
-            sortedNames = None
-            if columnName == self.cName:
-                sortedNames = sorted(self.mLoadingTimes.keys(), reverse=rev)
-            elif columnName == self.cSamples:
-                sortedNames = sorted(self.mLoadingTimes.keys(), key=lambda n: len(self.mLoadingTimes[n]), reverse=rev)
-            elif columnName == self.cAvgAll:
-                sortedNames = sorted(self.mLoadingTimes.keys(), key=lambda name:
-                    np.asarray(self.mLoadingTimes[name]).mean(), reverse=rev)
-            elif columnName == self.cAvg10:
-                sortedNames = sorted(self.mLoadingTimes.keys(), key=lambda name:
-                    np.asarray(self.mLoadingTimes[name][-10:]).mean(), reverse=rev)
-
-            if sortedNames is not None:
-                tmp = OrderedDict([(name, self.mLoadingTimes[name]) for name in sortedNames])
-                self.mLoadingTimes.clear()
-                self.mLoadingTimes.update(tmp)
-                self.layoutChanged.emit()
-
-        def rowCount(self, parentIdx=None, *args, **kwargs):
-            return len(self.mLoadingTimes)
-
-        def columnCount(self, QModelIndex_parent=None, *args, **kwargs):
-            return len(self.variableNames())
-
-        def type2idx(self, type):
-            assert isinstance(type, str)
-            if type not in self.mLoadingTimes.keys():
-                return None
-            return self.createIndex(self.mLoadingTimes.keys().index(type), 0)
-
-        def idx2type(self, index):
-            assert isinstance(index, QModelIndex)
-            if not index.isValid():
-                return None
-            return list(self.mLoadingTimes.keys())[index.row()]
-
-        def data(self, index, role=Qt.DisplayRole):
-            if role is None or not index.isValid():
-                return None
+    def idx2type(self, index):
+        assert isinstance(index, QModelIndex)
+        if not index.isValid():
+            return None
+        return list(self.mLoadingTimes.keys())[index.row()]
 
-            columnName = self.variableNames()[index.column()]
-            name = self.idx2type(index)
-            lTimes = self.mLoadingTimes[name]
-            value = None
-            if role in [Qt.DisplayRole, Qt.EditRole]:
-                if columnName == self.cName:
-                    value = name
-                elif columnName == self.cSamples:
-                    value = len(lTimes)
-
-                if len(lTimes) > 0:
-                    if columnName == self.cAvg10:
-                        value = float(np.asarray(lTimes[-10:]).mean().astype(float))
-                    elif columnName == self.cAvgAll:
-                        value = float(np.asarray(lTimes[:]).mean().astype(float))
-                    elif columnName == self.cMax10:
-                        value = float(np.asarray(lTimes[-10:]).max().astype(float))
-                    elif columnName == self.cMaxAll:
-                        value = float(np.asarray(lTimes[:]).max().astype(float))
-                    elif columnName == self.cLast:
-                        value = float(lTimes[-1].astype(float))
-
-            if role == Qt.UserRole:
-                value = lTimes
-
-            return value
-
-        def flags(self, index):
-            if index.isValid():
-                columnName = self.variableNames()[index.column()]
-                flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
-                return flags
+    def data(self, index, role=Qt.DisplayRole):
+        if role is None or not index.isValid():
             return None
 
+        columnName = self.variableNames()[index.column()]
+        name = self.idx2type(index)
+        lTimes = self.mLoadingTimes[name]
+        value = None
+        if role in [Qt.DisplayRole, Qt.EditRole]:
+            if columnName == self.cName:
+                value = name
+            elif columnName == self.cSamples:
+                value = len(lTimes)
+
+            if len(lTimes) > 0:
+                if columnName == self.cAvg10:
+                    value = float(np.asarray(lTimes[-10:]).mean().astype(float))
+                elif columnName == self.cAvgAll:
+                    value = float(np.asarray(lTimes[:]).mean().astype(float))
+                elif columnName == self.cMax10:
+                    value = float(np.asarray(lTimes[-10:]).max().astype(float))
+                elif columnName == self.cMaxAll:
+                    value = float(np.asarray(lTimes[:]).max().astype(float))
+                elif columnName == self.cLast:
+                    value = float(lTimes[-1].astype(float))
+
+        if role == Qt.UserRole:
+            value = lTimes
+
+        return value
+
+    def flags(self, index):
+        if index.isValid():
+            columnName = self.variableNames()[index.column()]
+            flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
+            return flags
+        return None
 
 
 class SystemInfoDock(QgsDockWidget):
 
-
     def __init__(self, parent=None):
         super(SystemInfoDock, self).__init__(parent)
         loadUi(DIR_UI / 'systeminfo.ui', self)
@@ -335,27 +333,25 @@ class SystemInfoDock(QgsDockWidget):
         self.labelPSUTIL.setVisible(PSUTIL_AVAILABLE == False)
         if PSUTIL_AVAILABLE:
             self.tableViewSystemParameters.setVisible(True)
-            #self.systemInfoModel = SystemInfoModel()
-            #self.tableViewSystemParameters.setModel(self.systemInfoModel)
+            # self.systemInfoModel = SystemInfoModel()
+            # self.tableViewSystemParameters.setModel(self.systemInfoModel)
         else:
             self.systemInfoModel = None
 
     def addTimeDelta(self, type, timedelta):
         self.dataLoadingModel.addTimeDelta(type, timedelta)
 
-
     def contextMenuEvent(self, tableView, event):
         assert isinstance(tableView, QTableView)
         menu = QMenu(self)
         a = menu.addAction("Copy selected")
-        a.triggered.connect(lambda :self.onCopy2Clipboard(tableView, 'SELECTED', separator=';'))
+        a.triggered.connect(lambda: self.onCopy2Clipboard(tableView, 'SELECTED', separator=';'))
 
         a = menu.addAction("Copy table")
         a.triggered.connect(lambda: self.onCopy2Clipboard(tableView, 'TABLE', separator=';'))
 
         a = menu.addAction('Save to file')
-        a.triggered.connect(lambda : self.onSaveToFile(tableView, 'TABLE'))
-
+        a.triggered.connect(lambda: self.onSaveToFile(tableView, 'TABLE'))
 
         menu.popup(QCursor.pos())
 
@@ -394,7 +390,6 @@ class SystemInfoDock(QgsDockWidget):
     def onSaveToFile(self, tableView, key):
         lines = self.readTableValues(key, tableView)
 
-
         if len(lines) > 0:
             filters = 'Textfile (*.txt);;CSV Table (*.csv)'
             path = QFileDialog.getSaveFileName(parent=None, caption="Save Table to file",
diff --git a/eotimeseriesviewer/temporalprofiles.py b/eotimeseriesviewer/temporalprofiles.py
index 64b721d8dc9476068433028e5d8a1c53e76c5d1e..925f0ba95901130d5e283b07c2a1460eb16e12b0 100644
--- a/eotimeseriesviewer/temporalprofiles.py
+++ b/eotimeseriesviewer/temporalprofiles.py
@@ -22,12 +22,12 @@
 
 import os
 import sys
-import pickle
 import datetime
 import re
-
+import typing
+import pathlib
+import traceback
 from collections import OrderedDict
-from qgis.core import *
 from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsMessageOutput, QgsCoordinateReferenceSystem, \
     Qgis, QgsWkbTypes, QgsTask, QgsProviderRegistry, QgsMapLayerStore, QgsFeature, QgsDateTimeRange, \
     QgsTextFormat, QgsProject, QgsSingleSymbolRenderer, QgsGeometry, QgsApplication, QgsFillSymbol,  \
@@ -37,23 +37,20 @@ from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsMessageOut
     QgsConditionalStyle, QgsConditionalLayerStyles, \
     QgsField, QgsFields, QgsExpressionContext, QgsExpression
 
-from qgis.gui import *
 from qgis.gui import QgsMapCanvas, QgsStatusBar, QgsFileWidget, \
     QgsMessageBar, QgsMessageViewer, QgsDockWidget, QgsTaskManagerWidget, QgisInterface, \
     QgsAttributeTableFilterModel, QgsIFeatureSelectionManager, QgsAttributeTableModel, QgsAttributeTableView
-from qgis.analysis import *
+
 from qgis.PyQt.QtCore import *
 from qgis.PyQt.QtGui import *
 from qgis.PyQt.QtWidgets import *
 import numpy as np
 from osgeo import ogr, osr, gdal
 from .externals import pyqtgraph as pg
-from .externals.pyqtgraph import functions as fn, AxisItem, ScatterPlotItem, SpotItem, GraphicsScene
-from .externals.qps.plotstyling.plotstyling import PlotStyle
 
 from .timeseries import TimeSeries, TimeSeriesDate, SensorInstrument, TimeSeriesSource
-from .utils import *
-from .externals.qps.speclib.core import createQgsField
+from .utils import SpatialExtent, SpatialPoint, px2geo, geo2px
+from .externals.qps.speclib.core import createQgsField, setQgsFieldValue
 
 
 LABEL_EXPRESSION_2D = 'DN or Index'
@@ -954,7 +951,8 @@ class TemporalProfileLayer(QgsVectorLayer):
 
         assert isinstance(temporalProfiles, list)
 
-        temporalProfiles = [tp for tp in temporalProfiles if isinstance(tp, TemporalProfile) and tp.id() in self.mProfiles.keys()]
+        temporalProfiles = [tp for tp in temporalProfiles
+                            if isinstance(tp, TemporalProfile) and tp.id() in self.mProfiles.keys()]
 
         if len(temporalProfiles) > 0:
             b = self.isEditable()
diff --git a/eotimeseriesviewer/tests.py b/eotimeseriesviewer/tests.py
index 1b925fcc1df4c82ed069a0dbd45168867927891e..6a561f330c7ac5616ee3c844244c2b59d0413ab6 100644
--- a/eotimeseriesviewer/tests.py
+++ b/eotimeseriesviewer/tests.py
@@ -21,11 +21,7 @@
 # noinspection PyPep8Naming
 
 import os
-import re
-import io
-import importlib
-import uuid
-from qgis.core import *
+import pathlib
 import numpy as np
 from qgis.gui import *
 from qgis.PyQt.QtCore import *
@@ -34,13 +30,11 @@ from qgis.PyQt.QtWidgets import *
 import eotimeseriesviewer.externals.qps.testing
 import eotimeseriesviewer.externals.qps
 from eotimeseriesviewer.utils import file_search
-from osgeo import ogr, osr, gdal, gdal_array
-import qgis.testing
+from osgeo import osr, gdal
 import example
 from eotimeseriesviewer import DIR_EXAMPLES, DIR_QGIS_RESOURCES, DIR_UI, DIR_REPO
 from eotimeseriesviewer.timeseries import TimeSeries
-from eotimeseriesviewer.externals.qps.resources import findQGISResourceFiles
-from eotimeseriesviewer.externals.qps.testing import *
+from eotimeseriesviewer.externals.qps.testing import TestObjects, TestCase, start_app
 from eotimeseriesviewer.externals.qps.resources import initQtResources
 
 
diff --git a/eotimeseriesviewer/timeseries.py b/eotimeseriesviewer/timeseries.py
index 0af066f72e39a7c996e12a0b02e21b52cc00c899..22fc0bb0dd52019652dd51534dac021cc4a7c4ff 100644
--- a/eotimeseriesviewer/timeseries.py
+++ b/eotimeseriesviewer/timeseries.py
@@ -19,7 +19,7 @@
  ***************************************************************************/
 """
 # noinspection PyPep8Naming
-
+import os
 import bisect
 import collections
 import datetime
@@ -35,7 +35,6 @@ from osgeo import osr, ogr, gdal_array
 
 from eotimeseriesviewer import DIR_UI
 from eotimeseriesviewer.utils import relativePath
-from qgis import *
 from qgis.PyQt.QtCore import *
 from qgis.PyQt.QtGui import *
 from qgis.PyQt.QtWidgets import *
@@ -1358,7 +1357,7 @@ class TimeSeries(QAbstractItemModel):
         self.mTSDs = list()
         self.mSensors = []
         self.mShape = None
-
+        self.mTreeView: QTreeView = None
         self.mDateTimePrecision = DateTimePrecision.Original
         self.mSensorMatchingFlags = SensorMatching.PX_DIMS
 
diff --git a/eotimeseriesviewer/utils.py b/eotimeseriesviewer/utils.py
index 14bdf2e4b59b436759763d1cd8b77779d4e15c5a..44c7b48a02ecdd861eeda14a7db287fbb109617a 100644
--- a/eotimeseriesviewer/utils.py
+++ b/eotimeseriesviewer/utils.py
@@ -20,20 +20,9 @@
 """
 # noinspection PyPep8Naming
 
-import os, sys, math, re, io, fnmatch, uuid
 
-
-from collections import defaultdict
-from qgis.core import *
-from qgis.gui import *
 from qgis.gui import QgisInterface
 import qgis.utils
-from qgis.PyQt.QtCore import *
-from qgis.PyQt.QtWidgets import *
-from qgis.PyQt.QtGui import *
-from qgis.PyQt.QtXml import QDomDocument
-from PyQt5 import uic
-from osgeo import gdal, ogr
 
 from eotimeseriesviewer.externals.qps.utils import *
 
diff --git a/eotimeseriesviewer/virtualrasters.py b/eotimeseriesviewer/virtualrasters.py
index 7051ba4dd6f0432d639ce27ed5ea75ea6edf83a3..8c020c12b019197bace21a9f9ad2e7c3295014cb 100644
--- a/eotimeseriesviewer/virtualrasters.py
+++ b/eotimeseriesviewer/virtualrasters.py
@@ -24,56 +24,57 @@ from xml.etree import ElementTree
 from collections import OrderedDict
 import tempfile
 from osgeo import gdal, osr, ogr, gdalconst as gc
-from qgis.core import *
+from qgis.core import QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsPoint, QgsCircularString, \
+    QgsPolygon, QgsRectangle
 from qgis.gui import *
 from qgis.PyQt.QtCore import *
 from qgis.PyQt.QtGui import *
 from qgis.PyQt.QtWidgets import *
 
 from eotimeseriesviewer import Option, OptionListModel
-#lookup GDAL Data Type and its size in bytes
-LUT_GDT_SIZE = {gdal.GDT_Byte:1,
-                gdal.GDT_UInt16:2,
-                gdal.GDT_Int16:2,
-                gdal.GDT_UInt32:4,
-                gdal.GDT_Int32:4,
-                gdal.GDT_Float32:4,
-                gdal.GDT_Float64:8,
-                gdal.GDT_CInt16:2,
-                gdal.GDT_CInt32:4,
-                gdal.GDT_CFloat32:4,
-                gdal.GDT_CFloat64:8}
-
-LUT_GDT_NAME = {gdal.GDT_Byte:'Byte',
-                gdal.GDT_UInt16:'UInt16',
-                gdal.GDT_Int16:'Int16',
-                gdal.GDT_UInt32:'UInt32',
-                gdal.GDT_Int32:'Int32',
-                gdal.GDT_Float32:'Float32',
-                gdal.GDT_Float64:'Float64',
-                gdal.GDT_CInt16:'Int16',
-                gdal.GDT_CInt32:'Int32',
-                gdal.GDT_CFloat32:'Float32',
-                gdal.GDT_CFloat64:'Float64'}
-
-
-GRA_tooltips = {'NearestNeighbour':'nearest neighbour resampling (default, fastest algorithm, worst interpolation quality).',
-              'Bilinear':'bilinear resampling.',
-              'Lanczos':'lanczos windowed sinc resampling.',
-              'Average':'average resampling, computes the average of all non-NODATA contributing pixels.',
-              'Cubic':'cubic resampling.',
-              'CubicSpline':'cubic spline resampling.',
-              'Mode':'mode resampling, selects the value which appears most often of all the sampled points',
-              'Max':'maximum resampling, selects the maximum value from all non-NODATA contributing pixels',
-              'Min':'minimum resampling, selects the minimum value from all non-NODATA contributing pixels.',
-              'Med':'median resampling, selects the median value of all non-NODATA contributing pixels.',
-              'Q1':'first quartile resampling, selects the first quartile value of all non-NODATA contributing pixels. ',
-              'Q3':'third quartile resampling, selects the third quartile value of all non-NODATA contributing pixels'
-              }
+
+# lookup GDAL Data Type and its size in bytes
+LUT_GDT_SIZE = {gdal.GDT_Byte: 1,
+                gdal.GDT_UInt16: 2,
+                gdal.GDT_Int16: 2,
+                gdal.GDT_UInt32: 4,
+                gdal.GDT_Int32: 4,
+                gdal.GDT_Float32: 4,
+                gdal.GDT_Float64: 8,
+                gdal.GDT_CInt16: 2,
+                gdal.GDT_CInt32: 4,
+                gdal.GDT_CFloat32: 4,
+                gdal.GDT_CFloat64: 8}
+
+LUT_GDT_NAME = {gdal.GDT_Byte: 'Byte',
+                gdal.GDT_UInt16: 'UInt16',
+                gdal.GDT_Int16: 'Int16',
+                gdal.GDT_UInt32: 'UInt32',
+                gdal.GDT_Int32: 'Int32',
+                gdal.GDT_Float32: 'Float32',
+                gdal.GDT_Float64: 'Float64',
+                gdal.GDT_CInt16: 'Int16',
+                gdal.GDT_CInt32: 'Int32',
+                gdal.GDT_CFloat32: 'Float32',
+                gdal.GDT_CFloat64: 'Float64'}
+
+GRA_tooltips = {
+    'NearestNeighbour': 'nearest neighbour resampling (default, fastest algorithm, worst interpolation quality).',
+    'Bilinear': 'bilinear resampling.',
+    'Lanczos': 'lanczos windowed sinc resampling.',
+    'Average': 'average resampling, computes the average of all non-NODATA contributing pixels.',
+    'Cubic': 'cubic resampling.',
+    'CubicSpline': 'cubic spline resampling.',
+    'Mode': 'mode resampling, selects the value which appears most often of all the sampled points',
+    'Max': 'maximum resampling, selects the maximum value from all non-NODATA contributing pixels',
+    'Min': 'minimum resampling, selects the minimum value from all non-NODATA contributing pixels.',
+    'Med': 'median resampling, selects the median value of all non-NODATA contributing pixels.',
+    'Q1': 'first quartile resampling, selects the first quartile value of all non-NODATA contributing pixels. ',
+    'Q3': 'third quartile resampling, selects the third quartile value of all non-NODATA contributing pixels'
+    }
 
 RESAMPLE_ALGS = OptionListModel()
 for GRAkey in [k for k in list(gdal.__dict__.keys()) if k.startswith('GRA_')]:
-
     GRA = gdal.__dict__[GRAkey]
     GRA_Name = GRAkey[4:]
 
@@ -88,13 +89,14 @@ def read_vsimem(fn):
     :param fn: vsimem path (str)
     :return: result of gdal.VSIFReadL(1, vsileng, vsifile)
     """
-    vsifile = gdal.VSIFOpenL(fn,'r')
+    vsifile = gdal.VSIFOpenL(fn, 'r')
     gdal.VSIFSeekL(vsifile, 0, 2)
     vsileng = gdal.VSIFTellL(vsifile)
     gdal.VSIFSeekL(vsifile, 0, 0)
     return gdal.VSIFReadL(1, vsileng, vsifile)
 
-def write_vsimem(fn:str,data:str):
+
+def write_vsimem(fn: str, data: str):
     """
     Writes data to vsimem path
     :param fn: vsimem path (str)
@@ -102,22 +104,22 @@ def write_vsimem(fn:str,data:str):
     :return: result of gdal.VSIFCloseL(vsifile)
     """
     '''Write GDAL vsimem files'''
-    vsifile = gdal.VSIFOpenL(fn,'w')
+    vsifile = gdal.VSIFOpenL(fn, 'w')
     size = len(data)
     gdal.VSIFWriteL(data, 1, size, vsifile)
     return gdal.VSIFCloseL(vsifile)
 
 
 def px2geo(px, gt):
-    #see http://www.gdal.org/gdal_datamodel.html
-    gx = gt[0] + px.x()*gt[1]+px.y()*gt[2]
-    gy = gt[3] + px.x()*gt[4]+px.y()*gt[5]
-    return QgsPoint(gx,gy)
+    # see http://www.gdal.org/gdal_datamodel.html
+    gx = gt[0] + px.x() * gt[1] + px.y() * gt[2]
+    gy = gt[3] + px.x() * gt[4] + px.y() * gt[5]
+    return QgsPoint(gx, gy)
 
 
 def describeRawFile(pathRaw, pathVrt, xsize, ysize,
                     bands=1,
-                    eType = gdal.GDT_Byte,
+                    eType=gdal.GDT_Byte,
                     interleave='bsq',
                     byteOrder='LSB',
                     headerOffset=0):
@@ -142,7 +144,7 @@ def describeRawFile(pathRaw, pathVrt, xsize, ysize,
     assert eType in LUT_GDT_SIZE.keys(), 'dataType "{}" is not a valid gdal datatype'.format(eType)
     interleave = interleave.lower()
 
-    assert interleave in ['bsq','bil','bip']
+    assert interleave in ['bsq', 'bil', 'bip']
     assert byteOrder in ['LSB', 'MSB']
 
     drvVRT = gdal.GetDriverByName('VRT')
@@ -150,7 +152,7 @@ def describeRawFile(pathRaw, pathVrt, xsize, ysize,
     dsVRT = drvVRT.Create(pathVrt, xsize, ysize, bands=0, eType=eType)
     assert isinstance(dsVRT, gdal.Dataset)
 
-    #vrt = ['<VRTDataset rasterXSize="{xsize}" rasterYSize="{ysize}">'.format(xsize=xsize,ysize=ysize)]
+    # vrt = ['<VRTDataset rasterXSize="{xsize}" rasterYSize="{ysize}">'.format(xsize=xsize,ysize=ysize)]
 
     vrtDir = os.path.dirname(pathVrt)
     if pathRaw.startswith(vrtDir):
@@ -191,15 +193,15 @@ def describeRawFile(pathRaw, pathVrt, xsize, ysize,
                                                          lineOffset=lineOffset,
                                                          byteOrder=byteOrder)
 
-        #md = {}
-        #md['source_0'] = xml
-        #vrtBand = dsVRT.GetRasterBand(b + 1)
+        # md = {}
+        # md['source_0'] = xml
+        # vrtBand = dsVRT.GetRasterBand(b + 1)
         assert dsVRT.AddBand(eType, options=options) == 0
 
-        vrtBand = dsVRT.GetRasterBand(b+1)
+        vrtBand = dsVRT.GetRasterBand(b + 1)
         assert isinstance(vrtBand, gdal.Band)
-        #vrtBand.SetMetadata(md, 'vrt_sources')
-        #vrt.append('  <VRTRasterBand dataType="{dataType}" band="{band}" subClass="VRTRawRasterBand">'.format(dataType=LUT_GDT_NAME[eType], band=b+1))
+        # vrtBand.SetMetadata(md, 'vrt_sources')
+        # vrt.append('  <VRTRasterBand dataType="{dataType}" band="{band}" subClass="VRTRawRasterBand">'.format(dataType=LUT_GDT_NAME[eType], band=b+1))
     dsVRT.FlushCache()
     return dsVRT
 
@@ -224,16 +226,13 @@ class VRTRasterInputSourceBand(object):
                 srcBands.append(VRTRasterInputSourceBand(path, b))
         return srcBands
 
-
-
-    def __init__(self, path:str, bandIndex:int, bandName:str=''):
+    def __init__(self, path: str, bandIndex: int, bandName: str = ''):
         self.mPath = path
         self.mBandIndex = bandIndex
         self.mBandName = bandName
         self.mNoData = None
         self.mVirtualBand = None
 
-
     def isEqual(self, other):
         if isinstance(other, VRTRasterInputSourceBand):
             return self.mPath == other.mPath and self.mBandIndex == other.mBandIndex
@@ -260,6 +259,7 @@ class VRTRasterBand(QObject):
     sigNameChanged = pyqtSignal(str)
     sigSourceInserted = pyqtSignal(int, VRTRasterInputSourceBand)
     sigSourceRemoved = pyqtSignal(int, VRTRasterInputSourceBand)
+
     def __init__(self, name='', parent=None):
         super(VRTRasterBand, self).__init__(parent)
         self.mSources = []
@@ -280,8 +280,6 @@ class VRTRasterBand(QObject):
     def name(self):
         return self.mName
 
-
-
     def addSource(self, virtualBandInputSource):
         assert isinstance(virtualBandInputSource, VRTRasterInputSourceBand)
         self.insertSource(len(self.mSources), virtualBandInputSource)
@@ -294,14 +292,14 @@ class VRTRasterBand(QObject):
             self.sigSourceInserted.emit(index, virtualBandInputSource)
         else:
             pass
-            #print('DEBUG: index <= len(self.sources)')
+            # print('DEBUG: index <= len(self.sources)')
+
     def bandIndex(self):
         if isinstance(self.mVRT, VRTRaster):
             return self.mVRT.mBands.index(self)
         else:
             return None
 
-
     def removeSource(self, vrtRasterInputSourceBand):
         """
         Removes a VRTRasterInputSourceBand
@@ -315,7 +313,6 @@ class VRTRasterBand(QObject):
             self.mSources.remove(vrtRasterInputSourceBand)
             self.sigSourceRemoved.emit(i, vrtRasterInputSourceBand)
 
-
     def sourceFiles(self):
         """
         :return: list of file-paths to all source files
@@ -332,7 +329,6 @@ class VRTRasterBand(QObject):
 
 
 class VRTRaster(QObject):
-
     sigSourceBandInserted = pyqtSignal(VRTRasterBand, VRTRasterInputSourceBand)
     sigSourceBandRemoved = pyqtSignal(VRTRasterBand, VRTRasterInputSourceBand)
     sigSourceRasterAdded = pyqtSignal(list)
@@ -341,7 +337,7 @@ class VRTRaster(QObject):
     sigBandRemoved = pyqtSignal(int, VRTRasterBand)
     sigCrsChanged = pyqtSignal(QgsCoordinateReferenceSystem)
     sigResolutionChanged = pyqtSignal()
-    sigResamplingAlgChanged = pyqtSignal([str],[int])
+    sigResamplingAlgChanged = pyqtSignal([str], [int])
     sigExtentChanged = pyqtSignal()
 
     def __init__(self, parent=None):
@@ -358,7 +354,6 @@ class VRTRaster(QObject):
         self.sigBandRemoved.connect(self.updateSourceRasterBounds)
         self.sigBandInserted.connect(self.updateSourceRasterBounds)
 
-
     def setResamplingAlg(self, value):
         """
         Sets the resampling algorithm
@@ -384,7 +379,6 @@ class VRTRaster(QObject):
             self.sigResamplingAlgChanged[str].emit(self.resamplingAlg(asString=True))
             self.sigResamplingAlgChanged[int].emit(self.resamplingAlg())
 
-
     def resamplingAlg(self, asString=False):
         """
         "Returns the resampling algorithms.
@@ -401,7 +395,7 @@ class VRTRaster(QObject):
     def setExtent(self, rectangle, crs=None):
         last = self.mExtent
         if rectangle is None:
-            #use implicit/automatic values
+            # use implicit/automatic values
             self.mExtent = None
         else:
             if isinstance(crs, QgsCoordinateReferenceSystem) and isinstance(self.mCrs, QgsCoordinateReferenceSystem):
@@ -437,7 +431,7 @@ class VRTRaster(QObject):
                 assert xy.height() > 0
                 self.mResolution = QSizeF(xy)
             elif isinstance(xy, str):
-                assert xy in ['average','highest','lowest']
+                assert xy in ['average', 'highest', 'lowest']
                 self.mResolution = xy
 
         if last != self.mResolution:
@@ -450,7 +444,6 @@ class VRTRaster(QObject):
         """
         return self.mResolution
 
-
     def setCrs(self, crs):
         """
         Sets the output Coordinate Reference System (CRS)
@@ -473,7 +466,6 @@ class VRTRaster(QObject):
                 self.mCrs = crs
                 self.sigCrsChanged.emit(self.mCrs)
 
-
     def crs(self):
         return self.mCrs
 
@@ -494,14 +486,12 @@ class VRTRaster(QObject):
         :param sourceBandIndex: source file band index
         """
 
-        while virtualBandIndex > len(self.mBands)-1:
-
+        while virtualBandIndex > len(self.mBands) - 1:
             self.insertVirtualBand(len(self.mBands), VRTRasterBand())
 
         vBand = self.mBands[virtualBandIndex]
         vBand.addSourceBand(pathSource, sourceBandIndex)
 
-
     def insertVirtualBand(self, index, virtualBand):
         """
         Inserts a VirtualBand
@@ -512,7 +502,7 @@ class VRTRaster(QObject):
         assert isinstance(virtualBand, VRTRasterBand)
         assert index <= len(self.mBands)
         if len(virtualBand.name()) == 0:
-            virtualBand.setName('Band {}'.format(index+1))
+            virtualBand.setName('Band {}'.format(index + 1))
         virtualBand.mVRT = self
 
         virtualBand.sigSourceInserted.connect(
@@ -525,8 +515,6 @@ class VRTRaster(QObject):
 
         return self[index]
 
-
-
     def removeVirtualBands(self, bandsOrIndices):
         assert isinstance(bandsOrIndices, list)
         to_remove = []
@@ -540,7 +528,6 @@ class VRTRaster(QObject):
             self.mBands.remove(virtualBand)
             self.sigBandRemoved.emit(index, virtualBand)
 
-
     def removeInputSource(self, path):
         assert path in self.sourceRaster()
         for vBand in self.mBands:
@@ -562,8 +549,8 @@ class VRTRaster(QObject):
             assert isinstance(ds, gdal.Dataset)
             nb = ds.RasterCount
             for b in range(nb):
-                if b+1 < len(self):
-                    #add new virtual band
+                if b + 1 < len(self):
+                    # add new virtual band
                     self.addVirtualBand(VRTRasterBand())
                 vBand = self[b]
                 assert isinstance(vBand, VRTRasterBand)
@@ -583,12 +570,11 @@ class VRTRaster(QObject):
             nb = ds.RasterCount
             ds = None
             for b in range(nb):
-                #each new band is a new virtual band
+                # each new band is a new virtual band
                 vBand = self.addVirtualBand(VRTRasterBand())
                 assert isinstance(vBand, VRTRasterBand)
                 vBand.addSource(VRTRasterInputSourceBand(file, b))
 
-
         return self
 
     def sourceRaster(self):
@@ -601,7 +587,6 @@ class VRTRaster(QObject):
     def sourceRasterBounds(self):
         return self.mSourceRasterBounds
 
-
     def updateSourceRasterBounds(self):
 
         srcFiles = self.sourceRaster()
@@ -619,18 +604,17 @@ class VRTRaster(QObject):
         elif len(srcFiles) == 0:
             self.setCrs(None)
 
-
         if len(toRemove) > 0:
             self.sigSourceRasterRemoved.emit(toRemove)
         if len(toAdd) > 0:
             self.sigSourceRasterAdded.emit(toAdd)
 
-    def loadVRT(self, pathVRT, bandIndex = None):
+    def loadVRT(self, pathVRT, bandIndex=None):
         """
         Load the VRT definition in pathVRT and appends it to this VRT
         :param pathVRT:
         """
-        if pathVRT in [None,'']:
+        if pathVRT in [None, '']:
             return
 
         if bandIndex is None:
@@ -641,10 +625,9 @@ class VRTRaster(QObject):
         assert ds.GetDriver().GetDescription() == 'VRT'
 
         for b in range(ds.RasterCount):
-            srcBand = ds.GetRasterBand(b+1)
+            srcBand = ds.GetRasterBand(b + 1)
             vrtBand = VRTRasterBand(name=srcBand.GetDescription().decode('utf-8'))
             for key, xml in srcBand.GetMetadata(str('vrt_sources')).items():
-
                 tree = ElementTree.fromstring(xml)
                 srcPath = tree.find('SourceFilename').text
                 srcBandIndex = int(tree.find('SourceBand').text)
@@ -653,10 +636,7 @@ class VRTRaster(QObject):
             self.insertVirtualBand(bandIndex, vrtBand)
             bandIndex += 1
 
-
-
-
-    def saveVRT(self, pathVRT, warpedImageFolder = '.warpedimage'):
+    def saveVRT(self, pathVRT, warpedImageFolder='.warpedimage'):
         """
         Save the VRT to path.
         If source images need to be warped to the final CRS warped VRT image will be created in a folder <directory>/<basename>+<warpedImageFolder>/
@@ -695,7 +675,7 @@ class VRTRaster(QObject):
             if crs == self.mCrs:
                 srcLookup[pathSrc] = pathSrc
             else:
-                #do a CRS transformation using VRTs
+                # do a CRS transformation using VRTs
 
                 warpedFileName = 'warped.{}.vrt'.format(os.path.basename(pathSrc))
                 if inMemory:
@@ -710,7 +690,7 @@ class VRTRaster(QObject):
                 assert isinstance(tmp, gdal.Dataset)
                 vrtXML = read_vsimem(warpedFileName)
                 xml = ElementTree.fromstring(vrtXML)
-                #print(vrtXML.decode('utf-8'))
+                # print(vrtXML.decode('utf-8'))
 
                 if False:
                     dsTmp = gdal.Open(warpedFileName)
@@ -723,7 +703,7 @@ class VRTRaster(QObject):
 
         srcFiles = [srcLookup[src] for src in self.sourceRaster()]
 
-        #these need to be set
+        # these need to be set
         ns = nl = gt = crs = eType = None
         res = self.resolution()
         extent = self.extent()
@@ -742,7 +722,7 @@ class VRTRaster(QObject):
                 kwds['xRes'] = res.width()
                 kwds['yRes'] = res.height()
             else:
-                assert res in ['highest','lowest','average']
+                assert res in ['highest', 'lowest', 'average']
                 kwds['resolution'] = res
 
             if isinstance(extent, QgsRectangle):
@@ -751,8 +731,6 @@ class VRTRaster(QObject):
             if srs is not None:
                 kwds['outputSRS'] = srs
 
-
-
             pathInMEMVRT = '/vsimem/{}.vrt'.format(uuid.uuid4())
             vro = gdal.BuildVRTOptions(separate=True, **kwds)
             dsVRTDst = gdal.BuildVRT(pathInMEMVRT, srcFiles, options=vro)
@@ -765,10 +743,10 @@ class VRTRaster(QObject):
             eType = dsVRTDst.GetRasterBand(1).DataType
             SOURCE_TEMPLATES = dict()
             for i, srcFile in enumerate(srcFiles):
-                vrt_sources = dsVRTDst.GetRasterBand(i+1).GetMetadata(str('vrt_sources'))
+                vrt_sources = dsVRTDst.GetRasterBand(i + 1).GetMetadata(str('vrt_sources'))
                 assert len(vrt_sources) == 1
                 srcXML = vrt_sources['source_0']
-                assert os.path.basename(srcFile)+'</SourceFilename>' in srcXML
+                assert os.path.basename(srcFile) + '</SourceFilename>' in srcXML
                 assert '<SourceBand>1</SourceBand>' in srcXML
                 SOURCE_TEMPLATES[srcFile] = srcXML
 
@@ -776,7 +754,7 @@ class VRTRaster(QObject):
 
         else:
             # special case: no source files defined
-            ns = nl = 1 #this is the minimum size
+            ns = nl = 1  # this is the minimum size
             if isinstance(extent, QgsRectangle):
                 x0 = extent.xMinimum()
                 y1 = extent.yMaximum()
@@ -794,38 +772,37 @@ class VRTRaster(QObject):
             gt = (x0, resx, 0, y1, 0, -resy)
             eType = gdal.GDT_Float32
 
-        #2. build final VRT from scratch
+        # 2. build final VRT from scratch
         drvVRT = gdal.GetDriverByName('VRT')
         assert isinstance(drvVRT, gdal.Driver)
-        dsVRTDst = drvVRT.Create(pathVRT, ns, nl,0, eType=eType)
-        #2.1. set general properties
+        dsVRTDst = drvVRT.Create(pathVRT, ns, nl, 0, eType=eType)
+        # 2.1. set general properties
         assert isinstance(dsVRTDst, gdal.Dataset)
 
         if srs is not None:
             dsVRTDst.SetProjection(srs)
         dsVRTDst.SetGeoTransform(gt)
 
-        #2.2. add virtual bands
+        # 2.2. add virtual bands
         for i, vBand in enumerate(self.mBands):
             assert isinstance(vBand, VRTRasterBand)
             assert dsVRTDst.AddBand(eType, options=['subClass=VRTSourcedRasterBand']) == 0
-            vrtBandDst = dsVRTDst.GetRasterBand(i+1)
+            vrtBandDst = dsVRTDst.GetRasterBand(i + 1)
             assert isinstance(vrtBandDst, gdal.Band)
             vrtBandDst.SetDescription(vBand.name())
             md = {}
-            #add all input sources for this virtual band
+            # add all input sources for this virtual band
             for iSrc, sourceInfo in enumerate(vBand.mSources):
                 assert isinstance(sourceInfo, VRTRasterInputSourceBand)
                 bandIndex = sourceInfo.mBandIndex
                 xml = SOURCE_TEMPLATES[srcLookup[sourceInfo.mPath]]
-                xml = re.sub('<SourceBand>1</SourceBand>', '<SourceBand>{}</SourceBand>'.format(bandIndex+1), xml)
+                xml = re.sub('<SourceBand>1</SourceBand>', '<SourceBand>{}</SourceBand>'.format(bandIndex + 1), xml)
                 md['source_{}'.format(iSrc)] = xml
-            vrtBandDst.SetMetadata(md,'vrt_sources')
-
+            vrtBandDst.SetMetadata(md, 'vrt_sources')
 
         dsVRTDst = None
 
-        #check if we get what we like to get
+        # check if we get what we like to get
         dsCheck = gdal.Open(pathVRT)
         assert isinstance(dsCheck, gdal.Dataset)
 
@@ -855,9 +832,6 @@ class VRTRaster(QObject):
         return iter(self.mClasses)
 
 
-
-
-
 def createVirtualBandMosaic(bandFiles, pathVRT):
     drv = gdal.GetDriverByName('VRT')
 
@@ -871,15 +845,15 @@ def createVirtualBandMosaic(bandFiles, pathVRT):
         separate=False
     )
     if len(bandFiles) > 1:
-        s =""
+        s = ""
     vrtDS = gdal.BuildVRT(pathVRT, bandFiles, options=vrtOptions)
     vrtDS.FlushCache()
 
     assert vrtDS.RasterCount == nb
     return vrtDS
 
-def createVirtualBandStack(bandFiles, pathVRT):
 
+def createVirtualBandStack(bandFiles, pathVRT):
     nb = len(bandFiles)
 
     drv = gdal.GetDriverByName('VRT')
@@ -898,9 +872,9 @@ def createVirtualBandStack(bandFiles, pathVRT):
 
     assert vrtDS.RasterCount == nb
 
-    #copy band metadata from
+    # copy band metadata from
     for i in range(nb):
-        band = vrtDS.GetRasterBand(i+1)
+        band = vrtDS.GetRasterBand(i + 1)
         band.SetDescription(bandFiles[i])
         band.ComputeBandStats()
 
@@ -910,7 +884,6 @@ def createVirtualBandStack(bandFiles, pathVRT):
     return vrtDS
 
 
-
 class RasterBounds(object):
     def __init__(self, path):
         self.path = None
@@ -921,7 +894,6 @@ class RasterBounds(object):
         if path is not None:
             self.fromImage(path)
 
-
     def fromImage(self, path):
         self.path = path
         ds = gdal.Open(path)
@@ -955,4 +927,3 @@ class RasterBounds(object):
 
     def __repr__(self):
         return self.polygon.asWkt()
-