Repository: ESA-PhiLab/OpenSarToolkit (language: python)

Issue:
stop importing gdal 
Based on gdal documentation and the fact that we request gdal>=2 in the setup.py we can safely drop the usage of:
```python 
import gdal
import ogr
```

The binding using osgeo module is available since gdal 1.7
```python 
from osgeo import gdal
from osgeo import ogr
```

> Additionally, there are five compatibility modules that are included but provide notices to state that they are deprecated and will be going away. If you are using GDAL 1.7 bindings, you should update your imports to utilize the usage above, but the following will work until GDAL 3.1.


Discussion / hints:

Fix patch:
diff --git a/.flake8 b/.flake8
new file mode 100644
index 00000000..c71ad965
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,4 @@
+[flake8]
+# E501: line too long | Black take care of it
+# W503: line break before binary operator | Black take care of it
+ignore = E501, W503
\ No newline at end of file
diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml
new file mode 100644
index 00000000..f5c607c1
--- /dev/null
+++ b/.github/workflows/unit.yml
@@ -0,0 +1,46 @@
+name: Build
+
+on: 
+  - push
+  - workflow_dispatch
+
+env:
+  GPT_PATH: /usr/bin/gpt
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: [3.7, 3.8]
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install GDAL
+        run: |
+          sudo add-apt-repository ppa:ubuntugis/ppa && sudo apt-get update
+          sudo apt-get update
+          sudo apt-get install gdal-bin
+          sudo apt-get install libgdal-dev
+          export CPLUS_INCLUDE_PATH=/usr/include/gdal
+          export C_INCLUDE_PATH=/usr/include/gdal
+          pip install GDAL==3.2.3
+      - name: Install dependencies
+        run: pip install .[dev]
+      - name: Test formatting
+        uses: psf/black@stable
+      - name: PEP8 rules
+        uses: tonybajan/flake8-check-action@v1.0.0
+        with:
+            repotoken: ${{ secrets.GITHUB_TOKEN }}
+      - name: test with pytest
+        run: coverage run -m pytest --color=yes tests
+      - name: coverage
+        run: coverage report -m
+      - name: codecov
+        run: bash <(curl -s https://codecov.io/bash)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..b06a1aa2
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,20 @@
+repos:
+  - repo: https://github.com/psf/black
+    rev: 8a59528c2d8ae1ef5f366039c728614aaf1a470b
+    hooks:
+      - id: black
+      - id: black-jupyter
+  - repo: https://github.com/kynan/nbstripout
+    rev: 0.5.0
+    hooks:
+      - id: nbstripout
+  - repo: https://gitlab.com/pycqa/flake8
+    rev: 3.9.2
+    hooks:
+      - id: flake8
+  - repo: 'https://github.com/commitizen-tools/commitizen'
+    rev: v2.18.0
+    hooks:
+      - id: commitizen
+        stages:
+          - commit-msg
\ No newline at end of file
diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644
index 00000000..1c12be2b
--- /dev/null
+++ b/.readthedocs.yml
@@ -0,0 +1,18 @@
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Build documentation in the docs/ directory with Sphinx
+sphinx:
+    configuration: docs/source/conf.py
+
+# Optionally build your docs in additional formats such as PDF
+formats: all
+
+# Optionally set the version of Python and requirements required to build your docs
+python:
+   version: 3.7
+   install:
+   - requirements: docs/requirements.txt
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..3cce948f
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+    "restructuredtext.confPath": ""
+}
\ No newline at end of file
diff --git a/README.rst b/README.rst
index f9c8f739..c3ddaacb 100755
--- a/README.rst
+++ b/README.rst
@@ -11,7 +11,23 @@ Open SAR Toolkit (OST)
     
 .. image:: https://img.shields.io/pypi/dm/opensartoolkit?color=307CC2&logo=python&logoColor=gainsboro  
     :target: https://pypi.org/project/opensartoolkit/
-    :alt: PyPI - Downloads
+    :alt: PyPI - Download
+
+.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+   :target: https://github.com/psf/black
+   :alt: Black badge
+   
+.. image:: https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg
+   :target: https://conventionalcommits.org
+   :alt: conventional commit
+
+.. image:: https://github.com/ESA-PhiLab/OpenSarToolkit/branch/master/graph/badge.svg
+    :target: https://codecov.io/gh/ESA-PhiLab/OpenSarToolkit
+    :alt: Test Coverage
+
+.. image:: https://readthedocs.org/projects/opensartoolkit/badge/?version=latest
+    :target: https://opensartoolkit.readthedocs.io/en/latest/?badge=latest
+    :alt: Documentation Status
 
 Objective
 ---------
@@ -43,9 +59,9 @@ Docker installation is possible on various OS. Installation instructions can be
 After docker is installed and running, launch the container with 
 (adapt the path to the shared host folder and the name of the docke rimage at the very end):
 
-```
-docker run -it -p 8888:8888 -v /shared/folder/on/host:/home/ost/shared docker/image
-```
+.. code-block:: console
+
+    docker run -it -p 8888:8888 -v /shared/folder/on/host:/home/ost/shared docker/image
 
 The docker image automatically executes the jupyter lab and runs it on port 8888. You can find the address to the notebook on the command line where docker is running. Copy it into your favorites browser and replace 127.0.0.1 with localhost.
 
@@ -75,24 +91,25 @@ Further dependencies (libs etc)
 ###############################
 
 Ubuntu 18.04 and later:
-```
-sudo apt install python3-pip git libgdal-dev python3-gdal libspatialindex-dev nodejs npm libgfortran5
-```
+
+.. code-block:: console
+
+    sudo apt install python3-pip git libgdal-dev python3-gdal libspatialindex-dev nodejs npm libgfortran5
 
 Any Operating system using (mini)conda https://www.anaconda.com/:
 
-```
-conda install pip gdal jupyter jupyterlab git matplotlib numpy rasterio imageio rtree geopandas fiona shapely matplotlib descartes tqdm scipy joblib retrying pytest pytest-cov nodejs
-```
+.. code-block:: console
+
+    conda install pip gdal jupyter jupyterlab git matplotlib numpy rasterio imageio rtree geopandas fiona shapely matplotlib descartes tqdm scipy joblib retrying pytest pytest-cov nodejs
 
 OST installation
 """"""""""""""""
 
 You can then use pip to install Open SAR Toolkit: 
 
-```
-pip install opensartoolkit
-```
+.. code-block:: console
+
+    pip install opensartoolkit
 
 Examples
 --------
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 00000000..d0c3cbf1
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS    ?=
+SPHINXBUILD   ?= sphinx-build
+SOURCEDIR     = source
+BUILDDIR      = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 00000000..6fcf05b4
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+	echo.
+	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+	echo.installed, then set the SPHINXBUILD environment variable to point
+	echo.to the full path of the 'sphinx-build' executable. Alternatively you
+	echo.may add the Sphinx directory to PATH.
+	echo.
+	echo.If you don't have Sphinx installed, grab it from
+	echo.https://www.sphinx-doc.org/
+	exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 00000000..edf3ba94
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,3 @@
+nbsphinx
+pydata-sphinx-theme
+sphinx-copybutton
\ No newline at end of file
diff --git a/docs/source/_images/header_image.PNG b/docs/source/_images/header_image.PNG
new file mode 100644
index 00000000..7d2614ca
Binary files /dev/null and b/docs/source/_images/header_image.PNG differ
diff --git a/docs/source/conf.py b/docs/source/conf.py
new file mode 100644
index 00000000..afbe28c5
--- /dev/null
+++ b/docs/source/conf.py
@@ -0,0 +1,79 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath("."))
+
+
+# -- Project information -----------------------------------------------------
+
+project = "OpenSarToolkit"
+copyright = "2021, Andreas Vollrath"
+author = "Andreas Vollrath"
+
+# The full version, including alpha/beta/rc tags
+release = "0.12.5"
+
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    "sphinx.ext.autosummary",
+    "sphinx.ext.viewcode",
+    "nbsphinx",
+    "sphinx_copybutton",
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ["_templates"]
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ["**.ipynb_checkpoints"]
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+#
+html_theme = "pydata_sphinx_theme"
+html_theme_options = {
+    "github_url": "https://github.com/ESA-PhiLab/OpenSarToolkit",
+    "show_nav_level": 2,
+    "show_prev_next": False,
+    "use_edit_page_button": True,
+    "navigation_with_keys": False,
+}
+html_context = {
+    "github_user": "ESA-PhiLab",
+    "github_repo": "OpenSarToolkit",
+    "github_version": "main",
+    "doc_path": "docs/source",
+}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ["_static"]
+
+# -- Options for autosummary output -------------------------------------------------
+autosummary_generate = False
+
+# -- Options for nb-sphinx output -------------------------------------------------
+nbsphinx_execute = "never"
diff --git a/docs/source/example/S1_GRD_batch_TS.ipynb b/docs/source/example/S1_GRD_batch_TS.ipynb
new file mode 100644
index 00000000..c33865f0
--- /dev/null
+++ b/docs/source/example/S1_GRD_batch_TS.ipynb
@@ -0,0 +1,436 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "P2SVmQcoWFBW"
+   },
+   "source": [
+    "<font face=\"Calibri\" size=\"2\"> <i>Open SAR Toolkit - Tutorial 4b, version 1.2, June 2020. Andreas Vollrath, ESA/ESRIN phi-lab</i>\n",
+    "</font>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "Y_9lvtkkWFBX"
+   },
+   "source": [
+    "![title](auxiliary/header_image.PNG)\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "# OST Tutorial IV-B\n",
+    "## How to create a timeseries animation of Iceberg A-68. Introduction to GRD Batch Processing part II.\n",
+    "\n",
+    "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ESA-PhiLab/OST_Notebooks/blob/master/4b%20-%20Sentinel-1%20GRD%20Batch%20-%20Timeseries.ipynb)\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "**Short description**\n",
+    "\n",
+    "This notebook continues to introduce you to the general workflow of OST for the batch processing of GRD data using the *Sentinel1Batch* class. In this example:\n",
+    "\n",
+    "1. across-track mosaicking based on a refined inventory and\n",
+    "2. processing in polar regions is shown.\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "**Requirements**\n",
+    "\n",
+    "- a PC/Mac with at least 16GB of RAM\n",
+    "- about 75 GB of free disk space\n",
+    "- a Copernicus Open Data Hub user account, valid for at least 7 days (https://scihub.copernicus.eu)\n",
+    "--------\n",
+    "\n",
+    "**NOTE:** all cells that have an * after its number can be executed without changing any code. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 0\\* - Install OST and dependencies \n",
+    "\n",
+    "**NOTE:** Applies only if you haven't fully installed OST yet, e.g. on Google Colab, "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {
+     "base_uri": "https://localhost:8080/",
+     "height": 1000
+    },
+    "colab_type": "code",
+    "executionInfo": {
+     "elapsed": 129719,
+     "status": "ok",
+     "timestamp": 1594827783444,
+     "user": {
+      "displayName": "Andreas Vollrath",
+      "photoUrl": "",
+      "userId": "16529617641432137889"
+     },
+     "user_tz": -120
+    },
+    "id": "JdI1jtExWkWH",
+    "outputId": "2c0c26ee-4137-41d2-8c4c-b389833401e6"
+   },
+   "outputs": [],
+   "source": [
+    "# !apt-get -y install wget\n",
+    "# !wget https://raw.githubusercontent.com/ESA-PhiLab/OST_Notebooks/master/install_ost.sh\n",
+    "# !bash install_ost.sh"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "3cZq5gYiWFBY"
+   },
+   "source": [
+    "### 1* - Import of libraries"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "executionInfo": {
+     "elapsed": 1205,
+     "status": "ok",
+     "timestamp": 1594828366359,
+     "user": {
+      "displayName": "Andreas Vollrath",
+      "photoUrl": "",
+      "userId": "16529617641432137889"
+     },
+     "user_tz": -120
+    },
+    "id": "6LgvmzL0WFBZ"
+   },
+   "outputs": [],
+   "source": [
+    "from pathlib import Path\n",
+    "from pprint import pprint\n",
+    "\n",
+    "from ost import Sentinel1Batch\n",
+    "from ost.helpers import vector, raster\n",
+    "\n",
+    "# ---------------------------------------------------\n",
+    "# for plotting purposes we use this iPython magic\n",
+    "%matplotlib inline\n",
+    "%pylab inline\n",
+    "pylab.rcParams[\"figure.figsize\"] = (19, 19)\n",
+    "# ---------------------------------------------------"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "Q8fB1i_6WFBd"
+   },
+   "source": [
+    "### 2 - Set up the project \n",
+    "\n",
+    "This follows the logic of the prior OST Tutorial notebooks."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {
+     "base_uri": "https://localhost:8080/",
+     "height": 275
+    },
+    "colab_type": "code",
+    "id": "FyKptPZJWFBe",
+    "outputId": "47e445ea-a4b9-4270-ebca-6c93f11f9f95"
+   },
+   "outputs": [],
+   "source": [
+    "# define a project directory\n",
+    "home = Path.home()\n",
+    "# create a processing directory\n",
+    "project_dir = home / \"OST_Tutorials\" / \"4b\"\n",
+    "\n",
+    "# define aoi with helper function, i.e. get a buffered area around point coordinates\n",
+    "lat, lon = \"-67\", \"-61\"\n",
+    "aoi = vector.latlon_to_wkt(lat, lon, buffer_degree=1.5, envelope=True)\n",
+    "\n",
+    "# define the start and end date\n",
+    "start = \"2017-06-30\"\n",
+    "end = \"2017-08-31\"\n",
+    "\n",
+    "# initialize the class to s1_grd instance\n",
+    "s1_grd = Sentinel1Batch(\n",
+    "    project_dir=project_dir, aoi=aoi, start=start, end=end, product_type=\"GRD\"\n",
+    ")\n",
+    "\n",
+    "# trigger the search\n",
+    "s1_grd.search()\n",
+    "s1_grd.plot_inventory()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "mr8nkbgRWFBi"
+   },
+   "source": [
+    "### 3 - Search Refinement\n",
+    "\n",
+    "In order to create a time-series of multiple tracks, a pre-condition is that all tracks feature the same amount of acquistions within our Time of Interest.\n",
+    "Let's use some pandas syntax to see if this is the case:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = s1_grd.inventory.pivot_table(\n",
+    "    index=[\"relativeorbit\", \"acquisitiondate\"], aggfunc=\"size\"\n",
+    ").reset_index()\n",
+    "df.pivot_table(index=\"relativeorbit\", aggfunc=\"size\").reset_index()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "As in most cases, we do not fulfill this pre-condition. By considering all tracks, our time-series would need to be reduced to 5 acquisitions. However, images taken over track 9 are not necessary, since our AOI is fully covered by the other 2 tracks.\n",
+    "\n",
+    "As already mentioned in OST Tutorial 2, the *refine_inventory* method takes care of those issues and prepares the inventory in a way that it is suitable for across-track mosaic time-series. This includes the splitting of images by orbit direction and polarzation mode in the first place. In addition, it checks if some tracks can be excluded because all the others fully overlap the AOI. In this way we reduce the amount of images to process, while optimising for our later time-series processing. See OST Tutorial 2 for full explanation and arguments."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# do the refinement\n",
+    "s1_grd.refine_inventory()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The output of the refinement procedure gives some infos, e.g. the exclusion of track 9. At the very end it summarises the information. Since in our case we only have imagery acquired in descending orbit and HH polarization, we see that 10 mosaics in time can be created. Another **important** infomation is the **key** defiend by orbit direction and polarisation, i.e. **DESCENDING_HH**. We will need this to select the refined inventory stored in the *refined_inventory_dict* attribute of our class instance as follows:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {
+     "base_uri": "https://localhost:8080/",
+     "height": 1000
+    },
+    "colab_type": "code",
+    "executionInfo": {
+     "elapsed": 1461,
+     "status": "ok",
+     "timestamp": 1594828707720,
+     "user": {
+      "displayName": "Andreas Vollrath",
+      "photoUrl": "",
+      "userId": "16529617641432137889"
+     },
+     "user_tz": -120
+    },
+    "id": "FnlnX_l8WFBj",
+    "outputId": "836e7ec2-eaf1-4ec1-a5db-d4fefa41ef28"
+   },
+   "outputs": [],
+   "source": [
+    "# select the key from output of refinement command\n",
+    "key = \"DESCENDING_HH\"\n",
+    "\n",
+    "# we wrap the information of the length of our refined inventory in a print statement\n",
+    "print(\n",
+    "    f\"The refined inventory holds {len(s1_grd.refined_inventory_dict[key])} acquisitions to process.\"\n",
+    ")\n",
+    "\n",
+    "# we plot the full Inventory on a map\n",
+    "s1_grd.plot_inventory(s1_grd.refined_inventory_dict[key], transparency=0.05)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 3 - Data download\n",
+    "\n",
+    "Here we download the data. It is best to use the ASF data mirror. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "WV9i2KwRWFBs"
+   },
+   "outputs": [],
+   "source": [
+    "s1_grd.download(s1_grd.refined_inventory_dict[key], concurrent=10)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 5* - Customise ARD parameters\n",
+    "\n",
+    "1. For data reduction we lower the resolution to 100 meters. \n",
+    "2. This will also reduce speckle, so we do not need it neither. \n",
+    "3. We do not care about Layover and Shadow for this example, since there is anyway no high-resolution DEM for Antarctica that could provide sufficient inforamtion.\n",
+    "4. Our time-series output will be scaled to dB scale for better stretching and visualisation\n",
+    "5. We further reduce the amount of data by converting from 32-bit float to unsigned 8-bit integer data type\n",
+    "6. Our AOI is only a rough seletion for the Area of Interest. We do not want to cut it to the boundaries to see the full data provided by the selected imagery."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "Eu5GYUasWFBw"
+   },
+   "outputs": [],
+   "source": [
+    "s1_grd.ard_parameters[\"single_ARD\"][\"resolution\"] = 100\n",
+    "s1_grd.ard_parameters[\"single_ARD\"][\"remove_mt_speckle\"] = False\n",
+    "s1_grd.ard_parameters[\"single_ARD\"][\"create_ls_mask\"] = False\n",
+    "s1_grd.ard_parameters[\"time-series_ARD\"][\"to_db\"] = True\n",
+    "s1_grd.ard_parameters[\"time-series_ARD\"][\"dtype_output\"] = \"uint8\"\n",
+    "s1_grd.ard_parameters[\"mosaic\"][\"cut_to_aoi\"] = False\n",
+    "\n",
+    "pprint(s1_grd.ard_parameters)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 6* - Produce Timeseries Mosaics\n",
+    "\n",
+    "Now the creation of our mosaic time-series is just a single command away."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "GYgdavVDWFBz"
+   },
+   "outputs": [],
+   "source": [
+    "s1_grd.grds_to_ards(\n",
+    "    inventory_df=s1_grd.refined_inventory_dict[key],\n",
+    "    timeseries=True,\n",
+    "    timescan=False,\n",
+    "    mosaic=True,\n",
+    "    overwrite=False,\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "Iat7lGGTWFB2"
+   },
+   "source": [
+    "### 7* - Creation of an animated GIF of a timeseries\n",
+    "\n",
+    "**Note:** This needs imagemagick installed, which is not a default requirement by OST.\n",
+    "You can install it on e.g. Ubuntu by typing:\n",
+    "sudo apt-get install magick"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "6OF9NCoXWFB2"
+   },
+   "outputs": [],
+   "source": [
+    "from ost.helpers import raster\n",
+    "\n",
+    "# define the timeseries folder for which the animation should be created\n",
+    "ts_folder = s1_grd.processing_dir / \"Mosaic\" / \"Timeseries\"\n",
+    "\n",
+    "raster.create_timeseries_animation(\n",
+    "    ts_folder,\n",
+    "    [\"bs.HH\"],\n",
+    "    s1_grd.processing_dir,\n",
+    "    shrink_factor=15,\n",
+    "    add_dates=True,\n",
+    "    duration=0.25,\n",
+    ")"
+   ]
+  }
+ ],
+ "metadata": {
+  "colab": {
+   "collapsed_sections": [],
+   "name": "4b - Sentinel-1 GRD Batch processing -  Timeseries.ipynb",
+   "provenance": [],
+   "toc_visible": true
+  },
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  },
+  "toc": {
+   "base_numbering": 1,
+   "nav_menu": {},
+   "number_sections": false,
+   "sideBar": true,
+   "skip_h1_title": true,
+   "title_cell": "Table of Contents",
+   "title_sidebar": "Contents",
+   "toc_cell": false,
+   "toc_position": {},
+   "toc_section_display": true,
+   "toc_window_display": true
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs/source/example/S1_GRD_batch_subset.ipynb b/docs/source/example/S1_GRD_batch_subset.ipynb
new file mode 100644
index 00000000..d83a9341
--- /dev/null
+++ b/docs/source/example/S1_GRD_batch_subset.ipynb
@@ -0,0 +1,338 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<font face=\"Calibri\" size=\"2\"> <i>Open SAR Toolkit - Tutorial 4a, version 1.2, June 2020. Andreas Vollrath, ESA/ESRIN phi-lab</i>\n",
+    "</font>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "![title](auxiliary/header_image.PNG)\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "# OST Tutorial IV-A\n",
+    "## How to create near-daily timeseries over Vienna. Introduction to GRD Batch Processing part I.\n",
+    "\n",
+    "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ESA-PhiLab/OST_Notebooks/blob/master/4a%20-%20Sentinel-1%20GRD%20Batch%20-%20Subset.ipynb)\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "**Short description**\n",
+    "\n",
+    "This notebook provides an introduction to the batch processing of Sentinel-1 GRD data using OST's *Sentinel1Batch* class. This is a subclass of the *Sentinel1* class, and thus inherits all the functionalities of the *Generic* and the *Sentinel1* classes for the generation of a project as well as data search and refinement as presented in the OST Tutorial II notebook. The *Sentinel1Batch* class holds functions for the batch processing of single calibrated backscatter products. Furthermore, time-series processing and the generation of multi-temporal statistics, referred to as timescans, are introduced. \n",
+    "\n",
+    "Within the given example, time-series for 4 different overlapping tracks are going to be produced over the city of Vienna, Austria. The notebook demonstrates:\n",
+    "\n",
+    "1. the reduction of data processing by automatically subsetting the data,\n",
+    "2. time-series processing and the corresponding ARD types,\n",
+    "3. merging the track specific time-series into a unique time-series with almost daily coverage,  \n",
+    "4. creation of a timeseries animation for outreach purposes.\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "**Requirements**\n",
+    "\n",
+    "- a PC/Mac with at least 16GB of RAM\n",
+    "- about 25 GB of free disk space\n",
+    "- a Copernicus Open Data Hub user account, ideally valid for at least 7 days (https://scihub.copernicus.eu)\n",
+    "--------\n",
+    "\n",
+    "**NOTE:** all cells that have an * after its number can be executed without changing any code. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 0\\* - Install OST and dependencies \n",
+    "\n",
+    "**NOTE:** Applies only if you haven't fully installed OST yet, e.g. on Google Colab, "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# !apt-get -y install wget\n",
+    "# !wget https://raw.githubusercontent.com/ESA-PhiLab/OST_Notebooks/master/install_ost.sh\n",
+    "# !bash install_ost.sh"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 1* - Import of Libraries\n",
+    "\n",
+    "In this step we import some standard python libraries for OS independent path handling as well as the *Sentinel1_GRDBatch* class thta handles the full workflow from search, download and processing of multiple GRD scenes. In addition, the OST helper module *vector* is loaded to create an AOI based on Point coordinates, and the *raster* module for creating a time-series animation."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# this is for the path handling and especially useful if you are on Windows\n",
+    "from pathlib import Path\n",
+    "from pprint import pprint\n",
+    "\n",
+    "\n",
+    "# this is the s1Project class, that basically handles all the workflow from beginning to the end\n",
+    "from ost import Sentinel1Batch\n",
+    "from ost.helpers import vector, raster"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 2* - Set up the project \n",
+    "\n",
+    "Here you going to initialize the *Sentinel1Batch* class by determining the project folder, the AOI and the start and end date. In addition we determine the image product type (i.e. GRD) and the ARD type that we use for processing. In this cas we choose the OST-RTC that produces Radiometrically Terrain Corrected products, i.e. the images are corrected for radiometric distortions along mountainous slopes. This type of ARD is advised when doing land cover and land use studiesover rugged terrain."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# define the project directory\n",
+    "project_dir = Path.home() / \"OST_Tutorials\" / \"Tutorial_4a\"\n",
+    "\n",
+    "# define aoi with a 2 point coordinates and create a buffer with 20km radius\n",
+    "lat, lon = \"48.25\", \"16.4\"  # Vienna\n",
+    "aoi = vector.latlon_to_wkt(lat, lon, buffer_meter=17500, envelope=True)\n",
+    "\n",
+    "# define the start and end date\n",
+    "start = \"2020-05-01\"\n",
+    "end = \"2020-05-31\"\n",
+    "\n",
+    "# initialize the class to s1_grd instance\n",
+    "s1_grd = Sentinel1Batch(\n",
+    "    project_dir=project_dir,\n",
+    "    aoi=aoi,\n",
+    "    start=start,\n",
+    "    end=end,\n",
+    "    product_type=\"GRD\",\n",
+    "    ard_type=\"OST-RTC\",\n",
+    ")\n",
+    "\n",
+    "# do the search\n",
+    "if not s1_grd.inventory_file:\n",
+    "    s1_grd.search()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 3* - Plot refined data inventory\n",
+    "\n",
+    "The resultant dataframe from the search inventory is visualised. We do not do a refinement step here, since all images are fully overlapping the AOI. This allows us to create a combined, almost daily time-series of all images."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# ---------------------------------------------------\n",
+    "# for plotting purposes we use this iPython magic\n",
+    "%matplotlib inline\n",
+    "%pylab inline\n",
+    "pylab.rcParams[\"figure.figsize\"] = (19, 19)\n",
+    "# ---------------------------------------------------\n",
+    "\n",
+    "# plot the inventory\n",
+    "s1_grd.plot_inventory(s1_grd.inventory, transparency=0.1)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 4* - Download of GRD scenes\n",
+    "\n",
+    "As already shown in Tutorial II, you will download the scenes based on the refined inventory dataframe for the respective produckt key."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "s1_grd.download(s1_grd.inventory, concurrent=10)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 5* - Set ARD parameters\n",
+    "\n",
+    "Similar to the *Sentinel1Scene* class (Tutorial I and III), the *Sentinel1Batch* class handles the defintion of ARD types in a hierarchical dictionary structure. You can use the same types and steps to customize as for the *Sentinel1Scene* class. For our example, we already intialised the class instance with the OST-RTC ARD type, in order to remove the backscatter distortion on mountainous slopes. This applies to all single image processing in the first step. The subsequent time-series processing will bring all the imagery to a common grid and apply a multitemporal speckle filter, that is much more efficient than speckle filtering applied on a single image. Since speckle filters a conceptionalised to work on the raw power data of SAR imagery, the conversion to the dB scale is only applied after the multi-temporal speckle filtering. In addition, all images are clipped to the very same extent, that is defined by the common data coverage of all images per track as well as the AOI.\n",
+    "\n",
+    "Note that it is possible to change the datatype of the output to either unsigned 16 or 8-bit integer. The backscatter data is therefore linearly stretched between -30 to 5 dB. This has the advantage to reduce the necessary storage by a factor of 2 for 16-bit uint and a factor of 4 for 8-bit uint data.\n",
+    "\n",
+    "The exact processing steps are as follows and depend on the ARD type:\n",
+    "\n",
+    "|               ARD Types          | OST-GTC      | OST-RTC         | CEOS           | Earth-Engine |\n",
+    "| :-------------                   | :----------: | :-----------:   | :----------:   | -----------: |\n",
+    "| **Time-series processing steps**                                                          |        \n",
+    "|  Create stack                    |       x      |       x         |       x        |      x       |\n",
+    "|  Multi-temporal speckle filter   |       x      |       x         |       -        |      -       |\n",
+    "|  dB conversion                   |       x      |       x         |       x        |      -       |\n",
+    "|  Layover/Shadow mask creation    |       x      |       x         |       x        |      x       |\n",
+    "|  Datatype conversion             |       -      |       -         |       -        |      -       |\n",
+    "|  Clip to common extent           |       x      |       x         |       x        |      x       |"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "print(\"Time-series processing parameters hold in the configuration file:\")\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "print(\"\")\n",
+    "pprint(s1_grd.ard_parameters[\"time-series_ARD\"])\n",
+    "\n",
+    "# custimze some single scene ARD parameters\n",
+    "s1_grd.ard_parameters[\"single_ARD\"][\n",
+    "    \"resolution\"\n",
+    "] = 50  # reduce for processing time and disk space\n",
+    "s1_grd.ard_parameters[\"time-series_ARD\"][\"dtype_output\"] = \"uint8\""
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 6* - Run the batch routine\n",
+    "\n",
+    "To process all the data, including time-series and timescans is as easy as one command. All the complexity is handled by OST in the back, and you just have to wait, since processing can take quite a while. Note the keywords to aly higher level tim-series and timescan generation. Mosaicking refers toacross track mosaicking, which for this example is not the case. The *overwrite* argument tells OST if it should start processing from scratch (i.e. set to **True**), or process from where it stopped. The latter comes in handy when wokng on cloud instances that crash or automatically shutdown every once in a while. \n",
+    "\n",
+    "**Note** that subsetting is set automatically to **True** when all tracks hold in the inventory fully overlap the AOI."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "s1_grd.grds_to_ards(\n",
+    "    inventory_df=s1_grd.inventory,\n",
+    "    timeseries=True,\n",
+    "    timescan=True,\n",
+    "    mosaic=False,\n",
+    "    overwrite=False,\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 7* - Merge single per-track time-series to one single time-series\n",
+    "\n",
+    "By using a little helper function from OST's raster module, combining the 4 time-series to a unique one is as easy the following command. Within your processing directory, a new foler called combined is created. If multi-temporal statistics for this new time-series should be created, set the timescan argument to **True**."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "raster.combine_timeseries(\n",
+    "    processing_dir=s1_grd.processing_dir, config_dict=s1_grd.config_dict, timescan=True\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 8* - Create a time-series animation\n",
+    "\n",
+    "Finally, a time-series animation is created. therefore we need to pass the time-series folder to the command. product_list expects a list of 1 to 3 elements. For GRD data this is either a single polarisation, or both bands. OST will calculate the power ratio of band 1 and 2 for a 3-band RGB composite. A shrink factor can be set to reduce image resolution and memory needs.\n",
+    "\n",
+    "**Note:** This needs imagemagick installed, which is not a default requirement by OST.\n",
+    "You can install it on e.g. Ubuntu by typing:\n",
+    "sudo apt-get install magick"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# define Time-series folder\n",
+    "ts_folder = s1_grd.processing_dir / \"combined\" / \"Timeseries\"\n",
+    "\n",
+    "# create the animation\n",
+    "raster.create_timeseries_animation(\n",
+    "    timeseries_folder=ts_folder,\n",
+    "    product_list=[\"bs.VV\", \"bs.VH\"],\n",
+    "    out_folder=s1_grd.processing_dir,\n",
+    "    shrink_factor=3,\n",
+    "    add_dates=True,\n",
+    ")"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  },
+  "toc": {
+   "base_numbering": 1,
+   "nav_menu": {},
+   "number_sections": false,
+   "sideBar": true,
+   "skip_h1_title": true,
+   "title_cell": "Table of Contents",
+   "title_sidebar": "Contents",
+   "toc_cell": false,
+   "toc_position": {},
+   "toc_section_display": true,
+   "toc_window_display": true
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs/source/example/S1_GRD_batch_tscan.ipynb b/docs/source/example/S1_GRD_batch_tscan.ipynb
new file mode 100644
index 00000000..f636a9e7
--- /dev/null
+++ b/docs/source/example/S1_GRD_batch_tscan.ipynb
@@ -0,0 +1,294 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<font face=\"Calibri\" size=\"2\"> <i>Open SAR Toolkit - Tutorial 4c, version 1.2, August 2020. Andreas Vollrath, ESA/ESRIN phi-lab</i>\n",
+    "</font>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "![title](auxiliary/header_image.PNG)\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "# OST Tutorial IV - C\n",
+    "## Create country-wide mosaic time-series and timescan. Introduction to GRD Batch processing part III.\n",
+    "\n",
+    "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ESA-PhiLab/OST_Notebooks/blob/master/4c%20-%20Sentinel-1%20GRD%20Batch%20-%20Timescan.ipynb)\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "**Short description**\n",
+    "\n",
+    "This notebook is very similar to the Tutorial IVa, with the difference that you will process GRD data over a larger area and create time-series and timescan mosaics. It therefore represents the workflow for large-scale processing.\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "**Requirements**\n",
+    "\n",
+    "- a PC/Mac with at least 16GB of RAM\n",
+    "- about 150GB of free disk space\n",
+    "- a Copernicus Open Data Hub user account, valid for at least 7 days (https://scihub.copernicus.eu)\n",
+    "--------\n",
+    "\n",
+    "**NOTE:** all cells that have an * after its number can be executed without changing any code. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 0\\* - Install OST and dependencies \n",
+    "\n",
+    "**NOTE:** Applies only if you haven't fully installed OST and its dependencies yet, e.g. on Google Colab, so uncomment the lines in this case."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# !apt-get -y install wget\n",
+    "# !wget https://raw.githubusercontent.com/ESA-PhiLab/OST_Notebooks/master/install_ost.sh\n",
+    "# !bash install_ost.sh"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 1* - Import of Libraries\n",
+    "\n",
+    "In this step we import some standard python libraries for OS independent path handling as well as the *Sentinel1_GRDBatch* class thta handles the full workflow from search, download and processing of multiple GRD scenes. In addition, the OST helper module *vector* is loaded to create an AOI based on Point coordinates, and the *raster* module for creating a time-series animation."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from pathlib import Path\n",
+    "from pprint import pprint\n",
+    "\n",
+    "from ost import Sentinel1Batch\n",
+    "from ost.helpers import vector, raster"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 2* - Set up the project \n",
+    "\n",
+    "Here you going to initialize the *Sentinel1_GRDBatch* class by determining the project folder, the AOI and the start and end date. Since you should be already familiar with the *search* and *refine* functions, we execute them within the same cell."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# define a project directory\n",
+    "home = Path.home()\n",
+    "# create a processing directory\n",
+    "project_dir = home / \"OST_Tutorials\" / \"Tutorial_4c\"\n",
+    "\n",
+    "# define aoi with helper function, i.e. get a buffered area around point coordinates\n",
+    "aoi = \"IRL\"\n",
+    "\n",
+    "# define the start and end date\n",
+    "start = \"2019-02-01\"\n",
+    "end = \"2019-04-30\"\n",
+    "\n",
+    "# initialize the class to s1_grd instance\n",
+    "s1_grd = Sentinel1Batch(\n",
+    "    project_dir=project_dir, aoi=aoi, start=start, end=end, product_type=\"GRD\"\n",
+    ")\n",
+    "\n",
+    "# trigger the search\n",
+    "s1_grd.search()\n",
+    "\n",
+    "# optional: once you did the search the first time, you can load\n",
+    "# the full inventory uncommenting the follwoing 2 lines\n",
+    "# and commenting the search command\n",
+    "# s1_grd.inventory_file = s1_grd.inventory_dir/\"full.inventory.gpkg\"\n",
+    "# s1_grd.read_inventory()\n",
+    "\n",
+    "# do the refinement\n",
+    "s1_grd.refine_inventory()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 3* - Plot refined data inventory\n",
+    "\n",
+    "Here you will visualize the resultant dataframes from the refined search inventory based on the product key."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# ---------------------------------------------------\n",
+    "# for plotting purposes we use this iPython magic\n",
+    "%matplotlib inline\n",
+    "%pylab inline\n",
+    "pylab.rcParams[\"figure.figsize\"] = (19, 19)\n",
+    "# ---------------------------------------------------\n",
+    "\n",
+    "# search command\n",
+    "key = \"ASCENDING_VVVH\"\n",
+    "print(\n",
+    "    f\"Our refined inventory holds {len(s1_grd.refined_inventory_dict[key])} frames to process.\"\n",
+    ")\n",
+    "# we plot the full Inventory on a map\n",
+    "s1_grd.plot_inventory(s1_grd.refined_inventory_dict[key], transparency=0.1)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 4* - Download of GRD scenes\n",
+    "\n",
+    "As already shown in Tutorial II, you will download the scenes based on the refined inventory dataframe for the respective produckt key."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "s1_grd.download(s1_grd.refined_inventory_dict[key])"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 5* - Set ARD parameters\n",
+    "\n",
+    "Similar to the *Sentinel1-Scene* class (Tutorial I and III), the *Sentinel1-GRDBatch* class handles the defintion of ARD types in a hierarchical dictionary structure. You can use the same types and steps to customize as for the *Sentinel1-Scene* class."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# single scene ARD parameters\n",
+    "s1_grd.ard_parameters[\"single_ARD\"][\"resolution\"] = 50\n",
+    "s1_grd.ard_parameters[\"single_ARD\"][\"product_type\"] = \"GTC-gamma0\"\n",
+    "s1_grd.ard_parameters[\"single_ARD\"][\"create_ls_mask\"] = False\n",
+    "\n",
+    "# time-series ARD\n",
+    "s1_grd.ard_parameters[\"time-series_ARD\"][\"remove_mt_speckle\"] = False\n",
+    "\n",
+    "# our borders for Ireland are quite rough. We therefore won't clip the final mosaics\n",
+    "s1_grd.ard_parameters[\"mosaic\"][\"cut_to_aoi\"] = False\n",
+    "\n",
+    "# s1_grd.config_dict['temp_dir'] = '/ram'\n",
+    "pprint(s1_grd.ard_parameters)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 6* - Run the batch routine\n",
+    "\n",
+    "To process all the data, including time-series and timescans is as easy as one command. All the complexity is handled behind, and you just have to wait, since processing can take quite a while."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "s1_grd.grds_to_ards(\n",
+    "    inventory_df=s1_grd.refined_inventory_dict[key],\n",
+    "    timeseries=True,\n",
+    "    timescan=True,\n",
+    "    overwrite=False,\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 7* - Create a time-series animation\n",
+    "\n",
+    "For interactive presentations it is nice to have animated \"movies\". The following command allows you to create animated time-series of oyur processed data."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# define Time-series folder\n",
+    "ts_folder = s1_grd.processing_dir / \"23\" / \"Timeseries\"\n",
+    "\n",
+    "# create the animation\n",
+    "raster.create_timeseries_animation(\n",
+    "    timeseries_folder=ts_folder,\n",
+    "    product_list=[\"bs.VV\", \"bs.VH\"],\n",
+    "    out_folder=s1_grd.processing_dir,\n",
+    "    shrink_factor=10,\n",
+    "    add_dates=True,\n",
+    ")"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  },
+  "toc": {
+   "base_numbering": 1,
+   "nav_menu": {},
+   "number_sections": false,
+   "sideBar": true,
+   "skip_h1_title": true,
+   "title_cell": "Table of Contents",
+   "title_sidebar": "Contents",
+   "toc_cell": false,
+   "toc_position": {},
+   "toc_section_display": true,
+   "toc_window_display": true
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs/source/example/data_search_&_access.ipynb b/docs/source/example/data_search_&_access.ipynb
new file mode 100644
index 00000000..b193b694
--- /dev/null
+++ b/docs/source/example/data_search_&_access.ipynb
@@ -0,0 +1,480 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<font face=\"Calibri\" size=\"2\"> <i>Open SAR Toolkit - Tutorial 2, version 1.1, July 2020. Andreas Vollrath, ESA/ESRIN phi-lab</i>\n",
+    "</font>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "![title](auxiliary/header_image.PNG)\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "# OST Tutorial II \n",
+    "## How to access and download Sentinel-1 data with OST\n",
+    "\n",
+    "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ESA-PhiLab/OST_Notebooks/blob/master/2%20-%20Data%20Search%20and%20Access.ipynb)\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "**Short description**\n",
+    "\n",
+    "This notebook introduces you to OST's main class *Generic*, and its subclass *Sentinel1*. The *Generic* class handles the basic structure of any OST batch processing project, while the *Sentinel1* class provides methods to search, refine and download sets of acquisitions for the EU Copernicus Sentinel-1 mission.\n",
+    "\n",
+    "This notebook is of interest for those users who like to only search and download Sentinel-1 data in an efficient way.\n",
+    "\n",
+    "- **I:** Get to know the *Generic* main class for setting up a OST Project\n",
+    "- **II:** Get to know the *Sentinel1* subclass, that features functions for data search and access\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "**Requirements**\n",
+    "\n",
+    "- a PC/Mac\n",
+    "- about 100MB of free disk space\n",
+    "- a Copernicus Open Data Hub user account, valid for at least 7 days (https://scihub.copernicus.eu)\n",
+    "--------\n",
+    "\n",
+    "**NOTE:** all cells that have an * after its number can be executed without changing any code. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 0\\* - Install OST and dependencies \n",
+    "\n",
+    "**NOTE:** Applies only if you haven't fully installed OST and its dependencies yet, e.g. on Google Colab, so uncomment the lines in this case."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# !apt-get -y install wget\n",
+    "# !wget https://raw.githubusercontent.com/ESA-PhiLab/OST_Notebooks/master/install_ost.sh\n",
+    "# !bash install_ost.sh"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### I-1* - Import python libraries necessary for processing"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# this imports we need to handle the folders, independent of the OS\n",
+    "from pathlib import Path\n",
+    "from pprint import pprint\n",
+    "\n",
+    "# this is the Generic class, that basically handles all the workflow from beginning to the end\n",
+    "from ost import Generic"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### I-2 - Data selection parameters\n",
+    "\n",
+    "In order to define your project you need to define 3 main attributes. \n",
+    "\n",
+    "**1 Area of Interest:** \n",
+    "\n",
+    "The Area of Interest can be defined in different ways:\n",
+    "\n",
+    "1. One possibility is to use the low resolution layer of country boundaries from geopandas. To select a specific country you need to specify its ISO3 code. You can find a collection of all ISO3 codes [here](https://unstats.un.org/unsd/tradekb/knowledgebase/country-code).\n",
+    "\n",
+    "2. Another possibility is to provide a Well-Known Text formatted string, which is the format OST uses internally. \n",
+    "\n",
+    "3. A third possibility is to provide a path to a valid vector file supported by OGR (e.g. GeoJSON, GeoPackage, KML, Esri Shapefile). Try to keep that as simple as possible. If your layer contains lots of different entries (e.g. crop fields), create a convex hull beforehand and use this.\n",
+    "\n",
+    "**2 Time of Interest:**\n",
+    "\n",
+    "The time of interest is defined by a *start* and *end* date. The date is defined by a string in the format 'YYYY-MM-DD'. If none of the two parameters are defined, both parameters will use default values, which is *2014-10-01* for *start*, and *today* for the end of the TOI.\n",
+    "\n",
+    "**3 Project directory**\n",
+    "\n",
+    "Here we set a high-level directory where all of the project-related data (i.e. inventory, download, processed files) will be stored or created. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# ----------------------------\n",
+    "# Area of interest\n",
+    "# ----------------------------\n",
+    "\n",
+    "# Here we can either point to a shapefile, an ISO3 country code, or a WKT string\n",
+    "aoi = \"AUT\"  # AUT is the ISO3 country code of Austria\n",
+    "\n",
+    "# ----------------------------\n",
+    "# Time of interest\n",
+    "# ----------------------------\n",
+    "# we set only the start date to today - 30 days\n",
+    "start = \"2019-06-01\"\n",
+    "end = \"2019-08-31\"\n",
+    "\n",
+    "# ----------------------------\n",
+    "# Project folder\n",
+    "# ----------------------------\n",
+    "\n",
+    "# get home folder\n",
+    "home = Path.home()\n",
+    "\n",
+    "# create a processing directory\n",
+    "project_dir = home / \"OST_Tutorials\" / \"Tutorial_2\"\n",
+    "\n",
+    "# ------------------------------\n",
+    "# Print out AOI and start date\n",
+    "# ------------------------------\n",
+    "print(\"AOI: \", aoi)\n",
+    "print(\"TOI start: \", start)\n",
+    "print(\"TOI end: \", end)\n",
+    "print(\"Project Directory: \", project_dir)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### I-3* - Initialize the *Generic* class\n",
+    "\n",
+    "The above defined variables are used to initialize the class with its main attributes."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# create an OST Generic class instance\n",
+    "ost_generic = Generic(project_dir=project_dir, aoi=aoi, start=start, end=end)\n",
+    "\n",
+    "# Uncomment below to see the list of folders inside the project directory (UNIX only):\n",
+    "print(\"\")\n",
+    "print(\n",
+    "    \"We use the linux ls command for listing the directories inside our project folder:\"\n",
+    ")\n",
+    "!ls {project_dir}"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### I-4* Customise project parameters\n",
+    "\n",
+    "The initialisation of the class creates a config file, where all project attributes are stored. This includes for example the lcoation of the download or the processing folder. Those can be customised as shown below. Also note that independent of the input format of the AOI, it will be stored as Well Known Text string. The possible input formats for AOI defintion will be covered in later tutorials."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Default config as created by the class initialisation\n",
+    "print(\" Before customisation\")\n",
+    "print(\"---------------------------------------------------------------------\")\n",
+    "pprint(ost_generic.config_dict)\n",
+    "print(\"---------------------------------------------------------------------\")\n",
+    "\n",
+    "# customisation\n",
+    "ost_generic.config_dict[\"download_dir\"] = \"/download\"\n",
+    "ost_generic.config_dict[\"temp_dir\"] = \"/tmp\"\n",
+    "\n",
+    "print(\"\")\n",
+    "print(\" After customisation (note the change in download_dir and temp_dir)\")\n",
+    "print(\"---------------------------------------------------------------------\")\n",
+    "pprint(ost_generic.config_dict)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### II-1* - The *Sentinel1* class\n",
+    "\n",
+    "The *Sentinel1* class, as a subclass of the Generic class, inherts all the attributes and methods from the Generic class, and adds specific new ones for search and download of data.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# the import of the Sentinel1 class\n",
+    "from ost import Sentinel1"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### II-2* Initialize the *Sentinel1* class\n",
+    "\n",
+    "In addition to the AOI, TOI and project directory parameters needed for the initialization of the *Generic* class, three more Sentinel-1 specific attributes can be defined\n",
+    "\n",
+    "1. product_type: this can be either RAW, SLC, GRD or OCN (default is '*' for all)\n",
+    "2. the beam mode: this can be either IW, SM, EW or WV (default is '*' for all)\n",
+    "3. polarisation: This can be either VV, VH, HV, HH or a combination, e.g. VV, VH or HH, HV (default is '*' for all)\n",
+    "\n",
+    "Have a look at https://sentinel.esa.int/web/sentinel/user-guides/sentinel-1-sar/acquisition-modes for further information on Sentinel-1 acquisition modes and https://sentinel.esa.int/web/sentinel/missions/sentinel-1/observation-scenario for information of the observation scenario globally."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# initialize the Sentinel1 class\n",
+    "ost_s1 = Sentinel1(\n",
+    "    project_dir=project_dir,\n",
+    "    aoi=aoi,\n",
+    "    start=start,\n",
+    "    end=end,\n",
+    "    product_type=\"SLC\",\n",
+    "    beam_mode=\"IW\",\n",
+    "    polarisation=\"*\",\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### II-3* Searching for data\n",
+    "\n",
+    "\n",
+    "The search method of our *Sentinel1* class instance will trigger a search query on the scihub catalogue and get the results back in 2 ways: \n",
+    "\n",
+    "- write it into a shapefile (inside your inventory directory).\n",
+    "- store it as an instance attribute in form of a Geopandas GeoDataFrame that can be called by ost_s1.inventory\n",
+    "\n",
+    "You will need a valid scihub account to do this step.\n",
+    "In case you do not have a scihub account yet, please go [here](https://scihub.copernicus.eu/dhus/#/home) to register.\n",
+    "\n",
+    "**IMPORTANT** OST, by default, queries the Copernicus Apihub (i.e. a different server than the one you access over your web browser), for which user credentials will be transfered only after a week of registration to the standard open scihub ([more info here](https://scihub.copernicus.eu/twiki/do/view/SciHubWebPortal/APIHubDescription)). In case this is an issue, use the commented line with the specified base_url and comment out the standard search command.\n",
+    "\n",
+    "So you may need to wait a couple of days after first registration before it works. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# ---------------------------------------------------\n",
+    "# for plotting purposes we use this iPython magic\n",
+    "%matplotlib inline\n",
+    "%pylab inline\n",
+    "pylab.rcParams[\"figure.figsize\"] = (13, 13)\n",
+    "# ---------------------------------------------------\n",
+    "\n",
+    "# search command\n",
+    "ost_s1.search()\n",
+    "\n",
+    "# uncomment in case you have issues with the registration procedure\n",
+    "# ost_s1.search(base_url='https://scihub.copernicus.eu/dhus')\n",
+    "\n",
+    "# we plot the full Inventory on a map\n",
+    "ost_s1.plot_inventory(transparency=0.1)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### II-4* The inventory attribute\n",
+    "\n",
+    "The results of the search are stored in the *inventory* attribute of the class instance *ost_s1*. This is actually a [Geopandas](http://geopandas.org) Dataframe that stores all the available metadata from the scihub catalogue. Therefore all (geo)pandas functionality can be applied for filtering, plotting and selection. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "print(\n",
+    "    \" INFO: We found a total of {} products for our project definition\".format(\n",
+    "        len(ost_s1.inventory)\n",
+    "    )\n",
+    ")\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "print(\"\")\n",
+    "# combine OST class attribute with pandas head command to print out the first 5 rows of the\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "print(\"The columns of our inventory:\")\n",
+    "print(\"\")\n",
+    "print(ost_s1.inventory.columns)\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "\n",
+    "print(\"\")\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "print(\" The last 5 rows of our inventory:\")\n",
+    "print(ost_s1.inventory.tail(5))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### II-5* Search Refinement\n",
+    "\n",
+    "The results returned by the search algorithm on Copernicus scihub might not be 100% appropriate to what we are looking for. In this step we refine the results adressing possible issues and reduce later processing needs.\n",
+    "\n",
+    "A first step **splits the data** by **orbit direction** (i.e. ascending and descending) and **polarization mode** (i.e. VV, VV/VH, HH, HH/HV). For each combination the routine then checks the coverage for the resulting combinations (e.g. descending VV/VH polarization). If one combination results in a non-full overlap to the AOI, all further steps are disregarded. In case a full coverage is possbile further refinement steps are taken: \n",
+    "\n",
+    "1. Some of the acquisition frames might have been processed and/or stored **more than once** in the ESA ground segment. Therefore they appear twice, with the scene identifier that only changes for the last 4 digits. It is necessary to identify those scenes in order to avoid redundancy. We therefore take the ones with the latest ingestion date to assure the use of the latest processor. \n",
+    "\n",
+    "2. Some of the scenes returned by the search query are actually **not overlapping the AOI**. This is because the search algorithm will actually check for data within a square defined by the outer bounds of the AOI geometry and not the AOI itself. The refinement only takes those frames overlapping with the AOI in order to reduce unnecassary processing later on.\n",
+    "\n",
+    "3. In the case of **ascending tracks that cross the equator**, the **orbit number** of the frames will **increase by 1** even though they are practically from the same acquisition. During processing the frames need to be merged and the relative orbit numbers (i.e. tracks) should be the same. The metadata in the inventory is therefore updated in order to normalize the relative orbit number for the project.\n",
+    "\n",
+    "4. (optional) The tracks of Sentinel-1 overlap to a certain degree. The data inventory might return tracks that only **marginally cross the AOI**, but there AOI overlap is already covered by the adjacent track. Thus, if tracks do not contribute to the overall overlap of the AOI, they are disregarded.\n",
+    "\n",
+    "5. (optional) Some acquisitions might **not cross the entire AOI**. For the subsequent time-series/timescan processing this becomes problematic, since the generation of the time-series will only consider the overlapping region for all acquisitions per track.\n",
+    "\n",
+    "6. A similar issue appears when one track **crosses the AOI twice**. In other words some of the frames in the middle of the track are not overlapping the AOI and are already disregarded by step 2. The assembling of the non-subsequent frames during processing would result in a failure. The metadata in the inventory is consequently updated, where the first part of the relative orbit number will be renamed to XXX.1, the second part to XXX.2 and so on. During processing those acquistions will be handled as 2 different tracks, and only merged during the final mosaicking.\n",
+    "\n",
+    "7. (optional) A last step is needed to assure that for one mosaic in time that consists of different tracks, is only covered once by each track. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ost_s1.refine_inventory()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### II-6* - Selecting the right data\n",
+    "\n",
+    "The results of the refinement are stored in a new attribute called **refined_inventory_dict**.\n",
+    "This is a python dictionary with the mosaic keys as dictionary keys, whereas the respective items are the refined Geodataframes."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pylab.rcParams[\"figure.figsize\"] = (19, 19)\n",
+    "\n",
+    "key = \"ASCENDING_VVVH\"\n",
+    "ost_s1.refined_inventory_dict[key]\n",
+    "ost_s1.plot_inventory(ost_s1.refined_inventory_dict[key], 0.1)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### II-7* Downloading the data\n",
+    "\n",
+    "Now that we have a refined selection of the scenes we want to download, we have different data mirrors as options.\n",
+    "By executing the following cell, OST will ask you from which data portal you want to download.\n",
+    "\n",
+    "#### ESA's Scihub catalogue\n",
+    "The main entry point is the offcial scihub catalogue from ESA. It is however limited to 2 concurrent donwloads at the same time. Also note that it is a rolling archive, so for historical data, a special procedure has to applied to download this data (see Tips and Tricks notebook).\n",
+    "\n",
+    "#### Alternative I - Alaska Satellite Facility:\n",
+    "\n",
+    "A good alternative is the download mirror from the Alaska Satellite Facility, which provides the full archive of Sentinel-1 data. In order to get registered, go on their [data portal](https://vertex.daac.asf.alaska.edu) and register. If you already have a NASA Earthdata account, make sure you signed the specific EULA needed to access the Copernicus data. A good practice is to try a download directly from the vertex data protal, to assure everything works. \n",
+    "\n",
+    "#### Alternative II - PEPS server from CNES:\n",
+    "\n",
+    "Another good alternative is the Peps server from the French Space Agency CNES. While it is also a rolling archive, copies of historic data are stored on tape and can be easily transferred to the online available storage. OST takes care of that automatically. You can register for an account [here](https://peps.cnes.fr/rocket/)\n",
+    "\n",
+    "#### Alternative III - ONDA DIAS by Serco:\n",
+    "\n",
+    "Another good alternative is the free data access portal from the ONDA DIAS. This is especially well suited for SLC data for which it holds the full archive. GRD data is accessible by a rolling archive. You can register for an account [here](https://www.onda-dias.eu/cms/).\n",
+    "\n",
+    "**NOTE** While for scihub there is a limit of 2 concurrent downloads, ASF, PEPS and ONDA do not have such strict limits. For ASF the limit is 10, and we can set this with the keyword concurrent."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ost_s1.download(ost_s1.refined_inventory_dict[key], concurrent=10)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  },
+  "toc": {
+   "base_numbering": 1,
+   "nav_menu": {},
+   "number_sections": false,
+   "sideBar": true,
+   "skip_h1_title": true,
+   "title_cell": "Table of Contents",
+   "title_sidebar": "Contents",
+   "toc_cell": false,
+   "toc_position": {},
+   "toc_section_display": true,
+   "toc_window_display": true
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs/source/example/first_S1_scene.ipynb b/docs/source/example/first_S1_scene.ipynb
new file mode 100644
index 00000000..c78d9efa
--- /dev/null
+++ b/docs/source/example/first_S1_scene.ipynb
@@ -0,0 +1,553 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "1PV8FN2-x2wj"
+   },
+   "source": [
+    "<font face=\"Calibri\" size=\"2\"> <i>Open SAR Toolkit - Tutorial 1, version 1.3,  July 2020. Andreas Vollrath, ESA/ESRIN phi-lab</i>\n",
+    "</font>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "kOn6Dorgx2wk"
+   },
+   "source": [
+    "![title](auxiliary/header_image.PNG)\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "# OST Tutorial I \n",
+    "\n",
+    "\n",
+    "\n",
+    "\n",
+    "## Pre-processing your first Sentinel-1 image with OST\n",
+    "\n",
+    "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ESA-PhiLab/OST_Notebooks/blob/master/1%20-%20The%20first%20S1%20scene.ipynb)\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "**Short description**\n",
+    "\n",
+    "This notebook introduces you to the *Sentinel1Scene* class of the Open SAR Toolkit and demonstrates how it can be used to download, extract metadata and pre-process a single Sentinel-1 scene.\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "**Requirements**\n",
+    "\n",
+    "- a PC/Mac with at least 16GB of RAM\n",
+    "- about 4 GB of free disk space (or more if you want to process more scenes)\n",
+    "- a NASA Earthdata account with signed EULA for use of https://search.asf.alaska.edu (just register directly there)\n",
+    "--------\n",
+    "\n",
+    "**NOTE:** all cells that have an * after its number can be executed without changing any code. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "o4gN2FtIx2wl"
+   },
+   "source": [
+    "### 0\\* - Install OST and dependencies \n",
+    "\n",
+    "**NOTE:** Applies only if you haven't fully installed OST and its dependencies yet, e.g. on Google Colab, so uncomment the lines in this case."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "KqtRYFnhx2wm"
+   },
+   "outputs": [],
+   "source": [
+    "# !apt-get -y install wget\n",
+    "# !wget https://raw.githubusercontent.com/ESA-PhiLab/OST_Notebooks/master/install_ost.sh\n",
+    "# !bash install_ost.sh"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "keC_14irx2wq"
+   },
+   "source": [
+    "### 1\\* - Import the OST *Sentinel1Scene* class"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "l1WpZ6RSx2wr"
+   },
+   "outputs": [],
+   "source": [
+    "# these imports we need to handle the folders, independent of the OS\n",
+    "from pathlib import Path\n",
+    "from pprint import pprint\n",
+    "\n",
+    "# this is the Sentinel1Scene class, that basically handles all the workflow from beginning to the end\n",
+    "from ost import Sentinel1Scene\n",
+    "\n",
+    "# from ost.helpers.settings import set_log_level\n",
+    "# import logging\n",
+    "# set_log_level(logging.DEBUG)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "hpm4Ii9gx2wu"
+   },
+   "source": [
+    "### 2* - Create a folder for our outputs\n",
+    "\n",
+    "By executing this cell, a new folder will be created and the path will be written to the *output_dir* variable"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "KDfHogBVx2wv"
+   },
+   "outputs": [],
+   "source": [
+    "# get home folder\n",
+    "home = Path.home()\n",
+    "\n",
+    "# create a processing directory\n",
+    "output_dir = home / \"OST_Tutorials\" / \"Tutorial_1\"\n",
+    "output_dir.mkdir(parents=True, exist_ok=True)\n",
+    "print(str(output_dir))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "9eR0Uy4nx2wy"
+   },
+   "source": [
+    "### 3* - Choose scene ID and display some metadata\n",
+    "\n",
+    "In order to initialize an instance of the *Sentinel1Scene* class, all we need is a valid scene id of a Sentinel-1 product. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "km3z40Kjx2wz"
+   },
+   "outputs": [],
+   "source": [
+    "# create a S1Scene class instance based on the scene identifier of the first ever Dual-Pol Sentinel-1 IW product\n",
+    "\n",
+    "# ---------------------------------------------------\n",
+    "# Some scenes to choose from\n",
+    "\n",
+    "# very first IW (VV/VH) S1 image available over Istanbul/Turkey\n",
+    "# NOTE:only available via ASF data mirror\n",
+    "scene_id = \"S1A_IW_GRDH_1SDV_20141003T040550_20141003T040619_002660_002F64_EC04\"\n",
+    "\n",
+    "# other scenes with different scene types to process (uncomment)\n",
+    "# IW scene (dual-polarised HH/HV) over Norway/Spitzbergen\n",
+    "# scene_id = 'S1B_IW_GRDH_1SDH_20200325T150411_20200325T150436_020850_02789D_2B85'\n",
+    "\n",
+    "# IW scene (single-polarised VV) over Ecuadorian Amazon\n",
+    "# scene_id = 'S1A_IW_GRDH_1SSV_20150205T232009_20150205T232034_004494_00583A_1C80'\n",
+    "\n",
+    "# EW scene (dual-polarised VV/VH) over Azores (needs a different DEM, see cell of ARD parameters below)\n",
+    "# scene_id = 'S1B_EW_GRDM_1SDV_20200303T193150_20200303T193250_020532_026E82_5CE9'\n",
+    "\n",
+    "# EW scene (dual-polarised HH/HV) over Greenland\n",
+    "# scene_id = 'S1B_EW_GRDM_1SDH_20200511T205319_20200511T205419_021539_028E4E_697E'\n",
+    "\n",
+    "# Stripmap mode S5 scene (dual-polarised VV/VH) over Germany\n",
+    "# scene_id = 'S1B_S5_GRDH_1SDV_20170104T052519_20170104T052548_003694_006587_86AB'\n",
+    "# ---------------------------------------------------\n",
+    "\n",
+    "# create an S1Scene instance\n",
+    "s1 = Sentinel1Scene(scene_id)\n",
+    "\n",
+    "# print summarising infos about the scene\n",
+    "s1.info()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "4KaMauPDx2w3"
+   },
+   "source": [
+    "### 4* Download scene\n",
+    "\n",
+    "The first step is to download the selected scene. In our case we chose the first regular Sentinel-1 IW product acquired in the dual-polarisation VV/VH acquired the 3rd of October 2014. \n",
+    "OST provides download from 3 different mirrors, ESA's scihub, CNES PEPS and Alaska Satellite Facility (NASA Earthdata). Since ESA's official scihub became a rolling archive, and so is PEPS, best is to download from the fantastic **Alaska Satellite Facility** mirror (selection 2), which holds the full Sentinel-1 archive online. \n",
+    "\n",
+    "**Note I:** You can interrupt the download and restart it. The download will continue from where it stopped.\n",
+    "\n",
+    "**Note II:** After the actual download, the file is checked for inconsistency. This assures that the download went fine and we can use it for processing. In addition, OST will magically remember that this file has been successfully downloaded (just run the cell again to see the behaviour)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "pcdvGAS5x2w3"
+   },
+   "outputs": [],
+   "source": [
+    "s1.download(output_dir)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "cs_Ps0Agx2w7"
+   },
+   "source": [
+    "### 5* - Define our ARD product\n",
+    "\n",
+    "ARD stands for Analysis Ready Data and is interpreted differently by different persons. OST provides various pre-defined flavours which can be used instantly.\n",
+    "\n",
+    "The following table shows the ARD types and corresponding processing steps applied for GRD data. \n",
+    "\n",
+    "|               ARD Types  | OST-GTC      | OST-RTC         | CEOS           | Earth-Engine |\n",
+    "| :-------------           | :----------: | :-----------:   | :----------:   | -----------: |\n",
+    "| **Scene processing steps**                                                                |        \n",
+    "|  Update Orbit File       |       x      |       x         |       x        |      x       |\n",
+    "|  Thermal Noise Removal   |       x      |       x         |       x        |      x       |\n",
+    "|  GRD Border Noise        |       x      |       x         |       x        |      -       |\n",
+    "|  Calibration             | $\\gamma^0$   |   $\\beta^0$     |  $\\beta^0$     |  $\\sigma^0$  |\n",
+    "|  Multi-look              |       x      |       x         |       -        |      -       |\n",
+    "|  Speckle-Filter          |       -      |       -         |       -        |      -       |\n",
+    "|  Terrain Flattening      |       -      | x ($\\gamma^0_f$)|x ($\\gamma^0_f$)|      -       |\n",
+    "|  Layover-Shadow Mask     |       -      |       x         |       x        |      -       |\n",
+    "|  dB conversion           |       -      |       -         |       -        |      x       |\n",
+    "|  Terrain Correction      |       x      |       x         |       x        |      x       |\n",
+    "|  Resolution (in m)       |       20     |       20        |       10       |      10      |\n",
+    "\n",
+    "The default ARD type is *'OST-GTC'*, referring to a Geometrically Terrain Corrected product which is calibrated to the ellipsoid correceted $\\gamma^0$ backscatter coefficient at 20m resolution. Other pre-defined ARD types are available, but it is also possible to customise single ARD parameters via a dictionary where all parameters are stored (as demonstrated in the cell below). Note how the resolution and the resampling of the image during terrain correction is changed at the bottom. In this way, actually all relevant parameters for processing are customisable. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "HZfdAYg3x2w7"
+   },
+   "outputs": [],
+   "source": [
+    "# Default ARD parameter\n",
+    "\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "print(\n",
+    "    \"Our ARD parameters dictionary contains 4 keys. For the moment, only single_ARD is relevant.\"\n",
+    ")\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "pprint(s1.ard_parameters.keys())\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "print(\"\")\n",
+    "\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "print(\"Dictionary of our default OST ARD parameters for single scene processing:\")\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "pprint(s1.ard_parameters[\"single_ARD\"])\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "print(\"\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "HZfdAYg3x2w7"
+   },
+   "outputs": [],
+   "source": [
+    "# Template ARD parameters\n",
+    "\n",
+    "# we change ARD type\n",
+    "# possible choices are:\n",
+    "# 'OST_GTC', 'OST-RTC', 'CEOS', 'Earth Engine'\n",
+    "s1.update_ard_parameters(\"Earth-Engine\")\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "print(\"Dictionary of Earth Engine ARD parameters:\")\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "pprint(s1.ard_parameters[\"single_ARD\"])\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "HZfdAYg3x2w7"
+   },
+   "outputs": [],
+   "source": [
+    "# Customised ARD parameters\n",
+    "\n",
+    "# we cusomize the resolution and image resampling\n",
+    "s1.ard_parameters[\"single_ARD\"][\"resolution\"] = 100  # set output resolution to 100m\n",
+    "s1.ard_parameters[\"single_ARD\"][\"remove_speckle\"] = False  # apply a speckle filter\n",
+    "s1.ard_parameters[\"single_ARD\"][\"dem\"][\n",
+    "    \"image_resampling\"\n",
+    "] = \"BILINEAR_INTERPOLATION\"  # BICUBIC_INTERPOLATION is default\n",
+    "\n",
+    "# s1.ard_parameters['single_ARD']['product_type'] = 'RTC-gamma0'\n",
+    "\n",
+    "# uncomment this for the Azores EW scene\n",
+    "# s1.ard_parameters['single_ARD']['dem']['dem_name'] = 'GETASSE30'\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "print(\"Dictionary of our customised ARD parameters for the final scene processing:\")\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")\n",
+    "pprint(s1.ard_parameters[\"single_ARD\"])\n",
+    "print(\n",
+    "    \"-----------------------------------------------------------------------------------------------------------\"\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "IavR8qQmx2w_"
+   },
+   "source": [
+    "### 6* - Create an ARD product\n",
+    "\n",
+    "Our *Sentinel1Scene* class comes with the build-in method *create_ard* to produce a standardised ARD product based on the ARD dictionary above. \n",
+    "\n",
+    "To run the command we just have to provide: \n",
+    "- the path to the downloaded file. We can use the *get_path* method in conjunction with the download directory provided\n",
+    "- a directory where the output files will be written to\n",
+    "- a filename prefix (the output format will be the standard SNAP Dimap format, consisting of the dim-file and the data directory)\n",
+    "- and a directory for storing temporary files (can not be the same as the output folder)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "BsSOUvVUx2w_"
+   },
+   "outputs": [],
+   "source": [
+    "s1.create_ard(infile=s1.get_path(output_dir), out_dir=output_dir, overwrite=True)\n",
+    "\n",
+    "print(\" The path to our newly created ARD product can be obtained the following way:\")\n",
+    "s1.ard_dimap"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "Umkg9vgux2xC"
+   },
+   "source": [
+    "### 6* - Create a RGB color composite\n",
+    "\n",
+    "Sentinel-1 scenes usually consist of two polarisation bands. In order to create a 3 channel RGB composite a ratio between the Co- (VV or HH) and the Cross-polarised (VH or HV) band is added. The *create_rgb* method takes the *ard_dimap* file and converts it to a 3-channel GeoTiff."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "bT5tNXs1x2xD"
+   },
+   "outputs": [],
+   "source": [
+    "s1.create_rgb(outfile=output_dir / f\"{s1.start_date}.tif\")\n",
+    "\n",
+    "print(\" The path to our newly created RGB product can be obtained the following way:\")\n",
+    "s1.ard_rgb"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "Y_FbFahox2xF"
+   },
+   "source": [
+    "### 7* - Visualise the RGB composite\n",
+    "\n",
+    "We can plot the newly created RGB image with the *visualise_rgb* method. A *shrink_factor* can be set, which reduces resolution in favour of memory requirements for plotting."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "PQzl1epox2xG"
+   },
+   "outputs": [],
+   "source": [
+    "# ---------------------------------------------------\n",
+    "# for plotting purposes we use this iPython magic\n",
+    "%matplotlib inline\n",
+    "%pylab inline\n",
+    "pylab.rcParams[\"figure.figsize\"] = (23, 23)\n",
+    "# ---------------------------------------------------\n",
+    "s1.visualise_rgb(shrink_factor=2)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "4QaGAsf8x2xJ"
+   },
+   "source": [
+    "### 8* - Create thumbnail image\n",
+    "\n",
+    "For some it might be interesting to create a small thumbnail image in Jpeg format. The *create_rgb_thumbnail* method allows for this. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "bj6RWSpox2xK"
+   },
+   "outputs": [],
+   "source": [
+    "# define a filename for our thumbnail image\n",
+    "path_to_thumbnail = output_dir / f\"{s1.start_date}.TN.jpg\"\n",
+    "\n",
+    "# create the thumbnail image\n",
+    "s1.create_rgb_thumbnail(outfile=str(path_to_thumbnail))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "efxTV4Zox2xN"
+   },
+   "outputs": [],
+   "source": [
+    "import imageio\n",
+    "\n",
+    "img = imageio.imread(path_to_thumbnail)\n",
+    "!ls -sh {path_to_thumbnail}\n",
+    "plt.imshow(img)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 9* - Play around\n",
+    "\n",
+    "You can try out the different images and also check the difference in backscatter for RTC products (uncomment the line of product type in the customisable ARD parameters)"
+   ]
+  }
+ ],
+ "metadata": {
+  "colab": {
+   "collapsed_sections": [],
+   "name": "1 - The first S1 scene.ipynb",
+   "provenance": [],
+   "toc_visible": true
+  },
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  },
+  "toc": {
+   "base_numbering": 1,
+   "nav_menu": {},
+   "number_sections": false,
+   "sideBar": true,
+   "skip_h1_title": true,
+   "title_cell": "Table of Contents",
+   "title_sidebar": "Contents",
+   "toc_cell": false,
+   "toc_position": {},
+   "toc_section_display": true,
+   "toc_window_display": true
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs/source/example/index.rst b/docs/source/example/index.rst
new file mode 100644
index 00000000..eb89f5c8
--- /dev/null
+++ b/docs/source/example/index.rst
@@ -0,0 +1,20 @@
+Examples
+========
+
+examples on how to run OST.
+
+.. toctree::
+    :maxdepth: 1
+    
+    first_S1_scene
+    data_search_&_access
+    latest_S1_scenes
+    S1_GRD_batch_subset
+    S1_GRD_batch_TS
+    S1_GRD_batch_tscan
+    tips_&_tricks
+    
+
+
+
+    
\ No newline at end of file
diff --git a/docs/source/example/tips_&_tricks.ipynb b/docs/source/example/tips_&_tricks.ipynb
new file mode 100644
index 00000000..c4c5035e
--- /dev/null
+++ b/docs/source/example/tips_&_tricks.ipynb
@@ -0,0 +1,318 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<font face=\"Calibri\" size=\"2\"> <i>Open SAR Toolkit - Tips and Tricks, version 1.0, September 2019. Andreas Vollrath, ESA/ESRIN phi-lab</i>\n",
+    "</font>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "![title](auxiliary/header_image.PNG)\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "# OST Tips and Tricks\n",
+    "\n",
+    "This notebook provides code snippets that might be useful for specific OST usage.\n",
+    "\n",
+    "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ESA-PhiLab/OST_Notebooks/blob/master/Tips%20and%20Tricks.ipynb)\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "**Short description**\n",
+    "\n",
+    "This notebook shows some useful low level functionality of OST, as well as some tricks that can be helpful for certain projects. \n",
+    "\n",
+    "- **1:** Create a squared AOI from point coordinates\n",
+    "- **2:** Create a OST confrom download directory from already downloaded files\n",
+    "- **3:** Create the Time of Interest using python's datatime class\n",
+    "- **4:** Load an already created inventory file into a OST class instance.\n",
+    "- **5:** How to download an offline scene from ESA scihub archive\n",
+    "- **6:** Speed up processing by using a ram disk for temporary file storage\n",
+    "--------"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 1 - Create a squared AOI from Lat/Lon point coordinates\n",
+    "\n",
+    "In case you do not have a shapefile of your Area Of Interest (AOI), but rather want to define it by Latitude and Longitude, considering a buffer, there is a helper function that let you do exactly this. \n",
+    "\n",
+    "**Note** that there are 2 buffer options, in meter and in degree, respectively. The buffer in meter does the transform from Lat/Lon into meters based on a equidistant projection. This may result in non-sqaured rectangles towards the poles when plotting on Lat/Lon grid (see second cell)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# import of of vector helper functions of ost\n",
+    "from ost.helpers import vector\n",
+    "\n",
+    "# define point by lat/lon coordinates\n",
+    "lat, lon = \"78\", \"12\"\n",
+    "\n",
+    "# apply function with buffer in meters\n",
+    "wkt1 = vector.latlon_to_wkt(lat, lon, buffer_degree=0.5, envelope=True)\n",
+    "wkt2 = vector.latlon_to_wkt(lat, lon, buffer_meter=10000, envelope=True)\n",
+    "print(wkt1)\n",
+    "print(wkt2)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# we plot the wkt with geopandas and matplotlib\n",
+    "import geopandas as gpd\n",
+    "import matplotlib.pyplot as plt\n",
+    "\n",
+    "# load world borders for background\n",
+    "world = gpd.read_file(gpd.datasets.get_path(\"naturalearth_lowres\"))\n",
+    "# import aoi as gdf\n",
+    "aoi1 = vector.wkt_to_gdf(wkt1)\n",
+    "aoi2 = vector.wkt_to_gdf(wkt2)\n",
+    "# get bounds of AOI\n",
+    "bounds = aoi1.geometry.bounds\n",
+    "# get world map as base\n",
+    "base = world.plot(color=\"lightgrey\", edgecolor=\"white\")\n",
+    "# plot aoi\n",
+    "aoi1.plot(ax=base, color=\"None\", edgecolor=\"black\")\n",
+    "aoi2.plot(ax=base, color=\"None\", edgecolor=\"red\")\n",
+    "\n",
+    "# set bounds\n",
+    "plt.xlim([bounds.minx.min() - 2, bounds.maxx.max() + 2])\n",
+    "plt.ylim([bounds.miny.min() - 2, bounds.maxy.max() + 2])\n",
+    "plt.grid(color=\"grey\", linestyle=\"-\", linewidth=0.1)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 2 Create a OST conform download directory\n",
+    "\n",
+    "OST stores downloaded data in a certain directory hierarchy. In case you already downloaded your Sentinel-1 data by yourself, you can automatically create an OST conform directory, where all scenes found in the input directory, will be moved into its hierarchical structure. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from ost.s1 import download\n",
+    "\n",
+    "input_directory = \"/path/to/files/already/downloaded\"\n",
+    "output_directory = \"/path/to/OST/download/directory\"\n",
+    "download.restore_download_dir(input_directory, output_directory)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "\n",
+    "### 3 Create the Time of Interest using python's datetime class\n",
+    "\n",
+    "Sometimes it is wanted to create dynamic timestamps for the defintion of time of interest. Here we show python's datetime library is used in combination with the OST format needed for class instantion. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### 3.1. Last 30 days from today onwards.\n",
+    "\n",
+    "**Note**, we do not need to set an end date, since this is by default today."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from datetime import datetime, timedelta\n",
+    "\n",
+    "start_date = (datetime.today() - timedelta(days=30)).strftime(\"%Y-%m-%d\")\n",
+    "print(start_date)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### 3.2. Target day (create date range around a certain date)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# we set only the start date to today - 30 days\n",
+    "target_day = \"2018-11-28\"\n",
+    "delta_days = 60\n",
+    "\n",
+    "# we set start and end 60 days before, repsectively after event\n",
+    "start = (\n",
+    "    datetime.strptime(target_day, \"%Y-%m-%d\") - timedelta(days=delta_days)\n",
+    ").strftime(\"%Y-%m-%d\")\n",
+    "end = (datetime.strptime(target_day, \"%Y-%m-%d\") + timedelta(days=delta_days)).strftime(\n",
+    "    \"%Y-%m-%d\"\n",
+    ")\n",
+    "\n",
+    "print(start, end)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 4 Load an already created inventory file into a OST class instance. \n",
+    "\n",
+    "Sometimes you need ot re-initialize the one of the batch processing classes. This results in an empty inventory atttriubte. In order ot skip the search on scihub you can load an already created inventory shapefile and set it as attribute in the following way:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "s1_instance = Sentinel1Batch(args)\n",
+    "s1_instance.inventory_file = s1_instance / \"full_inventory.gpkg\"\n",
+    "s1_instance.read_inventory()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 5 How to download an offline scene from ESA scihub archive\n",
+    "\n",
+    "ESA's scihub catalogue features an rolling archive of Sentinel-1 data meaning that older products are offline and need to be produced on demand. OST provides the functionality to do that in a programmatic way."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from ost import Sentinel1_Scene\n",
+    "from ost.helpers.scihub import connect\n",
+    "\n",
+    "# create instance\n",
+    "s1 = Sentinel1_Scene(\n",
+    "    \"S1A_IW_GRDH_1SDV_20141004T230354_20141004T230423_002686_002FFD_062B\"\n",
+    ")\n",
+    "# connection to Scihub\n",
+    "opener = connect()\n",
+    "# heck online status\n",
+    "print(\"The scene's online status is: \", s1.scihub_online_status(opener))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys\n",
+    "\n",
+    "# trigger production\n",
+    "s1.scihub_trigger_production(opener)\n",
+    "\n",
+    "# let's run a loop until the scene is online\n",
+    "while status is False:\n",
+    "    sys.sleep(60)  # just to not ask every millisecond, production can take a while\n",
+    "    status = s1.scihub_online_status(opener)\n",
+    "    print(status)\n",
+    "\n",
+    "s1.download(\"/path/to/download\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 6 Speed up processing by using a ram disk for temporary filestorage\n",
+    "\n",
+    "On UNIX systems it is possible to mount part of your RAM as a hard disk. If you have enough RAM, you can use this as a directory for temporary file storage. Since the RAM is very fast in terms of read/write operations, processing can accelareted quite a bit. \n",
+    "\n",
+    "**Note** that you need to run those commands on the command line and you will need administrative or superuser privilegues. \n",
+    "\n",
+    "Here is an example for a 30 GB big ramdisk mounted at /ramdisk:\n",
+    "```bash \n",
+    "sudo mkdir /ramdisk\n",
+    "sudo mount -t tmpfs -o size=30G tmpfs /ramdisk\n",
+    "```\n",
+    "After that you can set your temp_dir to the mount point as in the following example (adjusting the other keywords for the class initialization of course)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from ost import Sentinel1Batch\n",
+    "\n",
+    "project = Sentinel1Batch(\n",
+    "    project_dir=\"/your/project/dir\",\n",
+    "    aoi=\"your/aoi\",\n",
+    "    start=\"2019/01/01\",\n",
+    "    end=\"2019-12-31\",\n",
+    ")\n",
+    "project.config_dict[\"temp_dir\"] = \"/ramdisk\""
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  },
+  "toc": {
+   "base_numbering": 1,
+   "nav_menu": {},
+   "number_sections": false,
+   "sideBar": true,
+   "skip_h1_title": true,
+   "title_cell": "Table of Contents",
+   "title_sidebar": "Contents",
+   "toc_cell": false,
+   "toc_position": {},
+   "toc_section_display": true,
+   "toc_window_display": true
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs/source/index.rst b/docs/source/index.rst
new file mode 100644
index 00000000..3bd66d46
--- /dev/null
+++ b/docs/source/index.rst
@@ -0,0 +1,9 @@
+.. include:: ../../README.rst
+
+.. toctree::
+    :maxdepth: 3
+    :hidden:
+    
+    setup/index
+    modules/ost
+    example/index
diff --git a/docs/source/modules/ost.Project.Generic.rst b/docs/source/modules/ost.Project.Generic.rst
new file mode 100644
index 00000000..5325d8be
--- /dev/null
+++ b/docs/source/modules/ost.Project.Generic.rst
@@ -0,0 +1,19 @@
+ost.Project.Generic
+===================
+
+.. autoclass:: ost.Project.Generic
+
+    .. rubric:: Attributes
+
+    .. autosummary::
+
+       ~Generic.project_dir
+       ~Generic.download_dir
+       ~Generic.inventory_dir
+       ~Generic.processing_dir
+       ~Generic.temp_dir
+       ~Generic.aoi
+       ~Generic.start
+       ~Generic.end
+       ~Generic.data_mount
+       ~Generic.config_dict
\ No newline at end of file
diff --git a/docs/source/modules/ost.Project.Sentinel1.rst b/docs/source/modules/ost.Project.Sentinel1.rst
new file mode 100644
index 00000000..7359e6e5
--- /dev/null
+++ b/docs/source/modules/ost.Project.Sentinel1.rst
@@ -0,0 +1,56 @@
+ost.Project.Sentinel1
+=====================
+
+.. autoclass:: ost.Project.Sentinel1
+
+    .. rubric:: Attributes
+
+    .. autosummary::
+
+       ~Sentinel1.product_type
+       ~Sentinel1.beam_mode
+       ~Sentinel1.polarisation
+       ~Sentinel1.inventory_file
+       ~Sentinel1.inventory
+       ~Sentinel1.refined_inventory_dict
+       ~Sentinel1.coverages
+       ~Sentinel1.burst_inventory
+       ~Sentinel1.burst_inventory_file
+       ~Sentinel1.scihub_uname
+       ~Sentinel1.scihub_pword
+       ~Sentinel1.asf_uname
+       ~Sentinel1.asf_pword
+       ~Sentinel1.peps_uname
+       ~Sentinel1.peps_pword
+       ~Sentinel1.onda_uname
+       ~Sentinel1.onda_pword
+
+    .. rubric:: Methods
+
+    .. autosummary::
+        :nosignatures:
+
+        ~Sentinel1.search
+        ~Sentinel1.read_inventory
+        ~Sentinel1.download_size
+        ~Sentinel1.refine_inventory
+        ~Sentinel1.download
+        ~Sentinel1.create_burst_inventory
+        ~Sentinel1.read_burst_inventory
+        ~Sentinel1.plot_inventory
+
+.. automethod:: ost.Project.Sentinel1.search
+
+.. automethod:: ost.Project.Sentinel1.read_inventory
+
+.. automethod:: ost.Project.Sentinel1.download_size
+
+.. automethod:: ost.Project.Sentinel1.refine_inventory
+
+.. automethod:: ost.Project.Sentinel1.download
+
+.. automethod:: ost.Project.Sentinel1.create_burst_inventory
+
+.. automethod:: ost.Project.Sentinel1.read_burst_inventory
+
+.. automethod:: ost.Project.Sentinel1.plot_inventory
\ No newline at end of file
diff --git a/docs/source/modules/ost.Project.Sentinel1Batch.rst b/docs/source/modules/ost.Project.Sentinel1Batch.rst
new file mode 100644
index 00000000..2da85c05
--- /dev/null
+++ b/docs/source/modules/ost.Project.Sentinel1Batch.rst
@@ -0,0 +1,41 @@
+ost.Project.Sentinel1Batch
+==========================
+
+.. autoclass:: ost.Project.Sentinel1Batch
+
+    .. rubric:: Attributes
+
+    .. autosummary::
+
+       ~Sentinel1Batch.ard_type
+       ~Sentinel1Batch.ard_parameters
+
+
+    .. rubric:: Methods
+
+    .. autosummary::
+        :nosignatures:
+
+        ~Sentinel1.get_ard_parameters
+        ~Sentinel1.update_ard_parameters
+        ~Sentinel1.set_external_dem
+        ~Sentinel1.pre_download_srtm
+        ~Sentinel1.bursts_to_ards
+        ~Sentinel1.create_timeseries_animation
+        ~Sentinel1.grds_to_ards
+
+
+.. automethod:: ost.Project.Sentinel1Batch.get_ard_parameters
+
+.. automethod:: ost.Project.Sentinel1Batch.update_ard_parameters
+
+.. automethod:: ost.Project.Sentinel1Batch.set_external_dem
+
+.. automethod:: ost.Project.Sentinel1Batch.pre_download_srtm
+
+.. automethod:: ost.Project.Sentinel1Batch.bursts_to_ards
+
+.. automethod:: ost.Project.Sentinel1Batch.create_timeseries_animation
+
+.. automethod:: ost.Project.Sentinel1Batch.grds_to_ards
+
diff --git a/docs/source/modules/ost.Project.rst b/docs/source/modules/ost.Project.rst
new file mode 100644
index 00000000..bec50467
--- /dev/null
+++ b/docs/source/modules/ost.Project.rst
@@ -0,0 +1,23 @@
+﻿ost.Project
+===========
+
+.. automodule:: ost.Project
+
+   .. rubric:: Classes
+
+   .. autosummary::
+      :recursive:
+      :nosignatures:
+   
+      Generic
+      Sentinel1
+      Sentinel1Batch
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.generic.ard_to_ts.rst b/docs/source/modules/ost.generic.ard_to_ts.rst
new file mode 100644
index 00000000..5a14ebfd
--- /dev/null
+++ b/docs/source/modules/ost.generic.ard_to_ts.rst
@@ -0,0 +1,31 @@
+ost.generic.ard\_to\_ts
+=======================
+
+.. automodule:: ost.generic.ard_to_ts
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      ard_to_ts
+      gd_ard_to_ts
+
+.. automethod:: ost.generic.ard_to_ts.ard_to_ts
+
+.. automethod:: ost.generic.ard_to_ts.gd_ard_to_ts
+
+
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.generic.common_wrappers.rst b/docs/source/modules/ost.generic.common_wrappers.rst
new file mode 100644
index 00000000..f09b0385
--- /dev/null
+++ b/docs/source/modules/ost.generic.common_wrappers.rst
@@ -0,0 +1,45 @@
+ost.generic.common\_wrappers
+============================
+
+.. automodule:: ost.generic.common_wrappers
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      create_stack
+      linear_to_db
+      ls_mask
+      mt_speckle_filter
+      speckle_filter
+      terrain_correction
+      terrain_flattening
+
+.. automethod:: ost.generic.common_wrappers.create_stack
+
+.. automethod:: ost.generic.common_wrappers.linear_to_db
+
+.. automethod:: ost.generic.common_wrappers.ls_mask
+
+.. automethod:: ost.generic.common_wrappers.mt_speckle_filter
+
+.. automethod:: ost.generic.common_wrappers.speckle_filter
+
+.. automethod:: ost.generic.common_wrappers.terrain_correction
+
+.. automethod:: ost.generic.common_wrappers.terrain_flattening
+
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.generic.mosaic.rst b/docs/source/modules/ost.generic.mosaic.rst
new file mode 100644
index 00000000..c8374b72
--- /dev/null
+++ b/docs/source/modules/ost.generic.mosaic.rst
@@ -0,0 +1,38 @@
+ost.generic.mosaic
+==================
+
+.. automodule:: ost.generic.mosaic
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      create_timeseries_mosaic_vrt
+      gd_mosaic
+      gd_mosaic_slc_acquisition
+      mosaic
+      mosaic_slc_acquisition
+
+.. automethod:: ost.generic.mosaic.create_timeseries_mosaic_vrt
+
+.. automethod:: ost.generic.mosaic.gd_mosaic
+
+.. automethod:: ost.generic.mosaic.gd_mosaic_slc_acquisition
+
+.. automethod:: ost.generic.mosaic.mosaic
+
+.. automethod:: ost.generic.mosaic.mosaic_slc_acquisition
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.generic.rst b/docs/source/modules/ost.generic.rst
new file mode 100644
index 00000000..0fc7d95d
--- /dev/null
+++ b/docs/source/modules/ost.generic.rst
@@ -0,0 +1,18 @@
+﻿ost.generic
+===========
+
+.. automodule:: ost.generic
+
+   .. rubric:: Modules
+
+   .. autosummary::
+      :toctree:
+      :recursive:
+      :nosignatures:
+
+      ost.generic.ard_to_ts
+      ost.generic.common_wrappers
+      ost.generic.mosaic
+      ost.generic.timescan
+      ost.generic.ts_extent
+      ost.generic.ts_ls_mask
\ No newline at end of file
diff --git a/docs/source/modules/ost.generic.timescan.rst b/docs/source/modules/ost.generic.timescan.rst
new file mode 100644
index 00000000..4f42cc5a
--- /dev/null
+++ b/docs/source/modules/ost.generic.timescan.rst
@@ -0,0 +1,44 @@
+ost.generic.timescan
+====================
+
+.. automodule:: ost.generic.timescan
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      date_as_float
+      deseasonalize
+      difference_in_years
+      gd_mt_metrics
+      mt_metrics
+      nan_percentile
+      remove_outliers
+
+.. automethod:: ost.generic.timescan.date_as_float
+
+.. automethod:: ost.generic.timescan.deseasonalize
+
+.. automethod:: ost.generic.timescan.difference_in_years
+
+.. automethod:: ost.generic.timescan.gd_mt_metrics
+
+.. automethod:: ost.generic.timescan.mt_metrics
+
+.. automethod:: ost.generic.timescan.nan_percentile
+
+.. automethod:: ost.generic.timescan.remove_outliers
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.generic.ts_extent.rst b/docs/source/modules/ost.generic.ts_extent.rst
new file mode 100644
index 00000000..9aa3d420
--- /dev/null
+++ b/docs/source/modules/ost.generic.ts_extent.rst
@@ -0,0 +1,26 @@
+ost.generic.ts\_extent
+======================
+
+.. automodule:: ost.generic.ts_extent
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      mt_extent
+
+.. automethod:: ost.generic.ts_extent.mt_extent
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.generic.ts_ls_mask.rst b/docs/source/modules/ost.generic.ts_ls_mask.rst
new file mode 100644
index 00000000..42eb4ef3
--- /dev/null
+++ b/docs/source/modules/ost.generic.ts_ls_mask.rst
@@ -0,0 +1,13 @@
+ost.generic.ts\_ls\_mask
+========================
+
+.. automodule:: ost.generic.ts_ls_mask
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      mt_layover
+
+.. automethod:: ost.generic.ts_ls_mask.mt_layover
diff --git a/docs/source/modules/ost.helper.db.pgConnect.rst b/docs/source/modules/ost.helper.db.pgConnect.rst
new file mode 100644
index 00000000..62a71739
--- /dev/null
+++ b/docs/source/modules/ost.helper.db.pgConnect.rst
@@ -0,0 +1,41 @@
+ost.helpers.db.pgConnect
+========================
+
+.. autoclass:: ost.helpers.db.pgConnect
+
+    .. rubric:: Attributes
+
+    .. autosummary::
+
+      ~pgConnect.connection
+      ~pgConnect.cursor
+
+   .. rubric:: Methods
+
+   .. autosummary::
+      :nosignatures:
+
+      ~pgConnect.pgCreateS1
+      ~pgConnect.pgGetUUID
+      ~pgConnect.pgDrop
+      ~pgConnect.pgInsert
+      ~pgConnect.pgSQL
+      ~pgConnect.pgSQLnoResp
+      ~pgConnect.shpGeom2pg
+      ~pgConnect.pgDateline
+
+.. automethod:: ost.helpers.db.pgConnect.pgCreateS1
+
+.. automethod:: ost.helpers.db.pgConnect.pgGetUUID
+
+.. automethod:: ost.helpers.db.pgConnect.pgDrop
+
+.. automethod:: ost.helpers.db.pgConnect.pgInsert
+
+.. automethod:: ost.helpers.db.pgConnect.pgSQL
+
+.. automethod:: ost.helpers.db.pgConnect.pgSQLnoResp
+
+.. automethod:: ost.helpers.db.pgConnect.shpGeom2pg
+
+.. automethod:: ost.helpers.db.pgConnect.pgDateline
\ No newline at end of file
diff --git a/docs/source/modules/ost.helpers.asf.rst b/docs/source/modules/ost.helpers.asf.rst
new file mode 100644
index 00000000..3d2f6d46
--- /dev/null
+++ b/docs/source/modules/ost.helpers.asf.rst
@@ -0,0 +1,38 @@
+ost.helpers.asf
+===============
+
+.. automodule:: ost.helpers.asf
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+
+      asf_download
+      asf_download_parallel
+      ask_credentials
+      batch_download
+      check_connection
+
+.. automethod:: ost.helpers.asf.asf_download
+
+.. automethod:: ost.helpers.asf.asf_download_parallel
+
+.. automethod:: ost.helpers.asf.ask_credentials
+
+.. automethod:: ost.helpers.asf.batch_download
+
+.. automethod:: ost.helpers.asf.check_connection
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.helpers.asf_wget.SessionWithHeaderRedirection.rst b/docs/source/modules/ost.helpers.asf_wget.SessionWithHeaderRedirection.rst
new file mode 100644
index 00000000..49bb6a9c
--- /dev/null
+++ b/docs/source/modules/ost.helpers.asf_wget.SessionWithHeaderRedirection.rst
@@ -0,0 +1,19 @@
+ost.helpers.asf_wget.SessionWithHeaderRedirection
+=================================================
+
+.. autoclass:: ost.helpers.asf_wget.SessionWithHeaderRedirection
+
+   .. rubric:: Attributes
+
+   .. autosummary::
+
+      ~SessionWithHeaderRedirection.auth
+
+   .. rubric:: Methods
+
+   .. autosummary::
+      :nosignatures:
+
+      ~SessionWithHeaderRedirection.rebuild_auth
+
+.. automethod:: ost.helpers.asf_wget.SessionWithHeaderRedirection.rebuild_auth
\ No newline at end of file
diff --git a/docs/source/modules/ost.helpers.asf_wget.rst b/docs/source/modules/ost.helpers.asf_wget.rst
new file mode 100644
index 00000000..d1b33e71
--- /dev/null
+++ b/docs/source/modules/ost.helpers.asf_wget.rst
@@ -0,0 +1,35 @@
+ost.helpers.asf\_wget
+=====================
+
+.. automodule:: ost.helpers.asf_wget
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      batch_download
+      check_connection
+      s1_download
+   
+   .. rubric:: Classes
+
+   .. autosummary:: 
+      :nosignatures:
+
+      SessionWithHeaderRedirection
+
+.. automethod:: ost.helpers.asf_wget.batch_download
+
+.. automethod:: ost.helpers.asf_wget.check_connection
+
+.. automethod:: ost.helpers.asf_wget.s1_download
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.helpers.db.rst b/docs/source/modules/ost.helpers.db.rst
new file mode 100644
index 00000000..88a38fb6
--- /dev/null
+++ b/docs/source/modules/ost.helpers.db.rst
@@ -0,0 +1,41 @@
+ost.helpers.db
+==============
+
+.. automodule:: ost.helpers.db
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      pgHandler
+
+   .. rubric:: Classes
+
+   .. autosummary::
+      :nosignatures:
+
+      pgConnect
+
+.. automethod:: ost.helpers.db.pgHandler
+
+
+
+
+
+
+   
+   
+
+   
+   
+
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.helpers.errors.rst b/docs/source/modules/ost.helpers.errors.rst
new file mode 100644
index 00000000..996a204f
--- /dev/null
+++ b/docs/source/modules/ost.helpers.errors.rst
@@ -0,0 +1,26 @@
+ost.helpers.errors
+==================
+
+.. automodule:: ost.helpers.errors
+
+   .. rubric:: Exceptions
+
+   .. autosummary::
+      :nosignatures:
+   
+      DownloadError
+      GPTRuntimeError
+      NotValidFileError
+
+.. automethod:: ost.helpers.errors.DownloadError
+
+.. automethod:: ost.helpers.errors.GPTRuntimeError
+
+.. automethod:: ost.helpers.errors.NotValidFileError
+
+
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.helpers.helpers.rst b/docs/source/modules/ost.helpers.helpers.rst
new file mode 100644
index 00000000..32706b24
--- /dev/null
+++ b/docs/source/modules/ost.helpers.helpers.rst
@@ -0,0 +1,53 @@
+ost.helpers.helpers
+===================
+
+.. automodule:: ost.helpers.helpers
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      check_out_dimap
+      check_out_tiff
+      check_zipfile
+      delete_dimap
+      delete_shapefile
+      move_dimap
+      remove_folder_content
+      resolution_in_degree
+      run_command
+      timer
+
+.. automethod:: ost.helpers.helpers.check_out_dimap
+
+.. automethod:: ost.helpers.helpers.check_out_tiff
+
+.. automethod:: ost.helpers.helpers.check_zipfile
+
+.. automethod:: ost.helpers.helpers.delete_dimap
+
+.. automethod:: ost.helpers.helpers.delete_shapefile
+
+.. automethod:: ost.helpers.helpers.move_dimap
+
+.. automethod:: ost.helpers.helpers.remove_folder_content
+
+.. automethod:: ost.helpers.helpers.resolution_in_degree
+
+.. automethod:: ost.helpers.helpers.run_command
+
+.. automethod:: ost.helpers.helpers.timer
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.helpers.onda.rst b/docs/source/modules/ost.helpers.onda.rst
new file mode 100644
index 00000000..8dd97032
--- /dev/null
+++ b/docs/source/modules/ost.helpers.onda.rst
@@ -0,0 +1,38 @@
+ost.helpers.onda
+================
+
+.. automodule:: ost.helpers.onda
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      ask_credentials
+      batch_download
+      check_connection
+      connect
+      onda_download
+
+.. automethod:: ost.helpers.onda.ask_credentials
+
+.. automethod:: ost.helpers.onda.batch_download
+
+.. automethod:: ost.helpers.onda.check_connection
+
+.. automethod:: ost.helpers.onda.connect
+
+.. automethod:: ost.helpers.onda.onda_download
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.helpers.peps.rst b/docs/source/modules/ost.helpers.peps.rst
new file mode 100644
index 00000000..d7716661
--- /dev/null
+++ b/docs/source/modules/ost.helpers.peps.rst
@@ -0,0 +1,38 @@
+ost.helpers.peps
+================
+
+.. automodule:: ost.helpers.peps
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      ask_credentials
+      batch_download
+      check_connection
+      connect
+      peps_download
+
+.. automethod:: ost.helpers.peps.ask_credentials
+
+.. automethod:: ost.helpers.peps.batch_download
+
+.. automethod:: ost.helpers.peps.check_connection
+
+.. automethod:: ost.helpers.peps.connect
+
+.. automethod:: ost.helpers.peps.peps_download
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.helpers.raster.rst b/docs/source/modules/ost.helpers.raster.rst
new file mode 100644
index 00000000..a01009df
--- /dev/null
+++ b/docs/source/modules/ost.helpers.raster.rst
@@ -0,0 +1,84 @@
+ost.helpers.raster
+==================
+
+.. automodule:: ost.helpers.raster
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      calc_max
+      calc_min
+      combine_timeseries
+      convert_to_db
+      create_rgb_jpeg
+      create_timeseries_animation
+      create_tscan_vrt
+      fill_internal_nans
+      get_max
+      get_min
+      image_bounds
+      mask_by_shape
+      norm
+      outline
+      polygonize_bounds
+      polygonize_ls
+      rescale_to_float
+      scale_to_int
+      stretch_to_8bit
+      visualise_rgb
+
+.. automethod:: ost.helpers.raster.calc_max
+
+.. automethod:: ost.helpers.raster.calc_min
+
+.. automethod:: ost.helpers.raster.combine_timeseries
+
+.. automethod:: ost.helpers.raster.convert_to_db
+
+.. automethod:: ost.helpers.raster.create_rgb_jpeg
+
+.. automethod:: ost.helpers.raster.create_timeseries_animation
+
+.. automethod:: ost.helpers.raster.create_tscan_vrt
+
+.. automethod:: ost.helpers.raster.fill_internal_nans
+
+.. automethod:: ost.helpers.raster.get_max
+
+.. automethod:: ost.helpers.raster.get_min
+
+.. automethod:: ost.helpers.raster.image_bounds
+
+.. automethod:: ost.helpers.raster.mask_by_shape
+
+.. automethod:: ost.helpers.raster.norm
+
+.. automethod:: ost.helpers.raster.outline
+
+.. automethod:: ost.helpers.raster.polygonize_bounds
+
+.. automethod:: ost.helpers.raster.polygonize_ls
+
+.. automethod:: ost.helpers.raster.rescale_to_float
+
+.. automethod:: ost.helpers.raster.scale_to_int
+
+.. automethod:: ost.helpers.raster.stretch_to_8bit
+
+.. automethod:: ost.helpers.raster.visualise_rgb
+
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.helpers.rst b/docs/source/modules/ost.helpers.rst
new file mode 100644
index 00000000..59f98e68
--- /dev/null
+++ b/docs/source/modules/ost.helpers.rst
@@ -0,0 +1,25 @@
+﻿ost.helpers
+===========
+
+.. automodule:: ost.helpers
+
+.. rubric:: Modules
+
+.. autosummary::
+   :toctree:
+   :recursive:
+   :nosignatures:
+
+   ost.helpers.asf
+   ost.helpers.asf_wget
+   ost.helpers.db
+   ost.helpers.errors
+   ost.helpers.helpers
+   ost.helpers.onda
+   ost.helpers.peps
+   ost.helpers.raster
+   ost.helpers.scihub
+   ost.helpers.settings
+   ost.helpers.srtm
+   ost.helpers.vector
+
diff --git a/docs/source/modules/ost.helpers.scihub.rst b/docs/source/modules/ost.helpers.scihub.rst
new file mode 100644
index 00000000..e8de0e6f
--- /dev/null
+++ b/docs/source/modules/ost.helpers.scihub.rst
@@ -0,0 +1,57 @@
+ost.helpers.scihub
+==================
+
+.. automodule:: ost.helpers.scihub
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      ask_credentials
+      batch_download
+      check_connection
+      connect
+      create_aoi_str
+      create_s1_product_specs
+      create_satellite_string
+      create_toi_str
+      next_page
+      s1_download
+      s1_download_parallel
+
+.. automethod:: ost.helpers.scihub.ask_credentials
+
+.. automethod:: ost.helpers.scihub.batch_download
+
+.. automethod:: ost.helpers.scihub.check_connection
+
+.. automethod:: ost.helpers.scihub.connect
+
+.. automethod:: ost.helpers.scihub.create_aoi_str
+
+.. automethod:: ost.helpers.scihub.create_s1_product_specs
+
+.. automethod:: ost.helpers.scihub.create_satellite_string
+
+.. automethod:: ost.helpers.scihub.create_toi_str
+
+.. automethod:: ost.helpers.scihub.next_page
+
+.. automethod:: ost.helpers.scihub.s1_download
+
+.. automethod:: ost.helpers.scihub.s1_download_parallel
+
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.helpers.settings.rst b/docs/source/modules/ost.helpers.settings.rst
new file mode 100644
index 00000000..88a62c89
--- /dev/null
+++ b/docs/source/modules/ost.helpers.settings.rst
@@ -0,0 +1,42 @@
+ost.helpers.settings
+====================
+
+.. automodule:: ost.helpers.settings
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      check_ard_parameters
+      check_value
+      exception_handler
+      generate_access_file
+      get_gpt
+      set_log_level
+      setup_logfile
+
+.. automethod:: ost.helpers.settings.check_ard_parameters
+
+.. automethod:: ost.helpers.settings.check_value
+
+.. automethod:: ost.helpers.settings.exception_handler
+
+.. automethod:: ost.helpers.settings.generate_access_file
+
+.. automethod:: ost.helpers.settings.get_gpt
+
+.. automethod:: ost.helpers.settings.set_log_level
+
+.. automethod:: ost.helpers.settings.setup_logfile
+
+
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.helpers.srtm.rst b/docs/source/modules/ost.helpers.srtm.rst
new file mode 100644
index 00000000..c3b1b4d1
--- /dev/null
+++ b/docs/source/modules/ost.helpers.srtm.rst
@@ -0,0 +1,29 @@
+ost.helpers.srtm
+================
+
+.. automodule:: ost.helpers.srtm
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      download_srtm
+      download_srtm_tile
+
+.. automethod:: ost.helpers.srtm.download_srtm
+
+.. automethod:: ost.helpers.srtm.download_srtm_tile
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.helpers.vector.rst b/docs/source/modules/ost.helpers.vector.rst
new file mode 100644
index 00000000..ddab21f8
--- /dev/null
+++ b/docs/source/modules/ost.helpers.vector.rst
@@ -0,0 +1,72 @@
+ost.helpers.vector
+==================
+
+.. automodule:: ost.helpers.vector
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      aoi_to_wkt
+      buffer_shape
+      difference
+      epsg_to_wkt_projection
+      exterior
+      gdf_to_json_geometry
+      geodesic_point_buffer
+      get_epsg
+      get_proj4
+      latlon_to_shp
+      latlon_to_wkt
+      plot_inventory
+      reproject_geometry
+      set_subset
+      shp_to_wkt
+      wkt_manipulations
+      wkt_to_gdf
+
+.. automethod:: ost.helpers.vector.aoi_to_wkt
+
+.. automethod:: ost.helpers.vector.buffer_shape
+
+.. automethod:: ost.helpers.vector.difference
+
+.. automethod:: ost.helpers.vector.epsg_to_wkt_projection
+
+.. automethod:: ost.helpers.vector.exterior
+
+.. automethod:: ost.helpers.vector.gdf_to_json_geometry
+
+.. automethod:: ost.helpers.vector.geodesic_point_buffer
+
+.. automethod:: ost.helpers.vector.get_epsg
+
+.. automethod:: ost.helpers.vector.get_proj4
+
+.. automethod:: ost.helpers.vector.latlon_to_shp
+
+.. automethod:: ost.helpers.vector.plot_inventory
+
+.. automethod:: ost.helpers.vector.reproject_geometry
+
+.. automethod:: ost.helpers.vector.set_subset
+
+.. automethod:: ost.helpers.vector.shp_to_wkt
+
+.. automethod:: ost.helpers.vector.wkt_manipulations
+
+.. automethod:: ost.helpers.vector.wkt_to_gdf
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.rst b/docs/source/modules/ost.rst
new file mode 100644
index 00000000..724c8d75
--- /dev/null
+++ b/docs/source/modules/ost.rst
@@ -0,0 +1,15 @@
+Content
+=======
+
+.. rubric:: Modules
+ 
+.. autosummary::
+    :nosignatures:
+    :toctree:
+    :recursive:
+
+    ost.generic
+    ost.helpers
+    ost.s1
+    ost.Project
+    
\ No newline at end of file
diff --git a/docs/source/modules/ost.s1.burst_batch.rst b/docs/source/modules/ost.s1.burst_batch.rst
new file mode 100644
index 00000000..c178cf89
--- /dev/null
+++ b/docs/source/modules/ost.s1.burst_batch.rst
@@ -0,0 +1,41 @@
+ost.s1.burst\_batch
+===================
+
+.. automodule:: ost.s1.burst_batch
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      ards_to_timeseries
+      bursts_to_ards
+      mosaic_timescan
+      mosaic_timescan_old
+      mosaic_timeseries
+      timeseries_to_timescan
+
+.. automethod:: ost.s1.burst_batch.ards_to_timeseries
+
+.. automethod:: ost.s1.burst_batch.bursts_to_ards
+
+.. automethod:: ost.s1.burst_batch.mosaic_timescan
+
+.. automethod:: ost.s1.burst_batch.mosaic_timescan_old
+
+.. automethod:: ost.s1.burst_batch.mosaic_timeseries
+
+.. automethod:: ost.s1.burst_batch.timeseries_to_timescan
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.s1.burst_inventory.rst b/docs/source/modules/ost.s1.burst_inventory.rst
new file mode 100644
index 00000000..2daaac91
--- /dev/null
+++ b/docs/source/modules/ost.s1.burst_inventory.rst
@@ -0,0 +1,35 @@
+ost.s1.burst\_inventory
+=======================
+
+.. automodule:: ost.s1.burst_inventory
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      burst_extract
+      burst_inventory
+      prepare_burst_inventory
+      refine_burst_inventory
+
+.. automethod:: ost.s1.burst_inventory.burst_extract
+
+.. automethod:: ost.s1.burst_inventory.burst_inventory
+
+.. automethod:: ost.s1.burst_inventory.prepare_burst_inventory
+
+.. automethod:: ost.s1.burst_inventory.refine_burst_inventory
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.s1.burst_to_ard.rst b/docs/source/modules/ost.s1.burst_to_ard.rst
new file mode 100644
index 00000000..cd86578c
--- /dev/null
+++ b/docs/source/modules/ost.s1.burst_to_ard.rst
@@ -0,0 +1,35 @@
+ost.s1.burst\_to\_ard
+=====================
+
+.. automodule:: ost.s1.burst_to_ard
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      burst_to_ard
+      create_backscatter_layers
+      create_coherence_layers
+      create_polarimetric_layers
+
+.. automethod:: ost.s1.burst_to_ard.burst_to_ard
+
+.. automethod:: ost.s1.burst_to_ard.create_backscatter_layers
+
+.. automethod:: ost.s1.burst_to_ard.create_coherence_layers
+
+.. automethod:: ost.s1.burst_to_ard.create_polarimetric_layers
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.s1.download.rst b/docs/source/modules/ost.s1.download.rst
new file mode 100644
index 00000000..2dfa75b6
--- /dev/null
+++ b/docs/source/modules/ost.s1.download.rst
@@ -0,0 +1,28 @@
+ost.s1.download
+===============
+
+.. automodule:: ost.s1.download
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      download_sentinel1
+      restore_download_dir
+
+.. automethod:: ost.s1.download.download_sentinel1
+
+.. automethod:: ost.s1.download.restore_download_dir
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.s1.grd_batch.rst b/docs/source/modules/ost.s1.grd_batch.rst
new file mode 100644
index 00000000..418c19ed
--- /dev/null
+++ b/docs/source/modules/ost.s1.grd_batch.rst
@@ -0,0 +1,43 @@
+ost.s1.grd\_batch
+=================
+
+.. automodule:: ost.s1.grd_batch
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      ards_to_timeseries
+      create_processed_df
+      grd_to_ard_batch
+      mosaic_timescan
+      mosaic_timeseries
+      timeseries_to_timescan
+
+.. automethod:: ost.s1.grd_batch.ards_to_timeseries
+
+.. automethod:: ost.s1.grd_batch.create_processed_df
+
+.. automethod:: ost.s1.grd_batch.grd_to_ard_batch
+
+.. automethod:: ost.s1.grd_batch.mosaic_timescan
+
+.. automethod:: ost.s1.grd_batch.mosaic_timeseries
+
+.. automethod:: ost.s1.grd_batch.timeseries_to_timescan
+
+
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.s1.grd_to_ard.rst b/docs/source/modules/ost.s1.grd_to_ard.rst
new file mode 100644
index 00000000..38d2852b
--- /dev/null
+++ b/docs/source/modules/ost.s1.grd_to_ard.rst
@@ -0,0 +1,29 @@
+ost.s1.grd\_to\_ard
+===================
+
+.. automodule:: ost.s1.grd_to_ard
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      ard_to_rgb
+      grd_to_ard
+
+.. automethod:: ost.s1.grd_to_ard.ard_to_rgb
+
+.. automethod:: ost.s1.grd_to_ard.grd_to_ard
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.s1.grd_wrappers.rst b/docs/source/modules/ost.s1.grd_wrappers.rst
new file mode 100644
index 00000000..aa46a049
--- /dev/null
+++ b/docs/source/modules/ost.s1.grd_wrappers.rst
@@ -0,0 +1,43 @@
+ost.s1.grd\_wrappers
+====================
+
+.. automodule:: ost.s1.grd_wrappers
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      calibration
+      grd_frame_import
+      grd_remove_border
+      grd_subset_georegion
+      multi_look
+      slice_assembly
+
+.. automethod:: ost.s1.grd_wrappers.calibration
+
+.. automethod:: ost.s1.grd_wrappers.grd_frame_import
+
+.. automethod:: ost.s1.grd_wrappers.grd_remove_border
+
+.. automethod:: ost.s1.grd_wrappers.grd_subset_georegion
+
+.. automethod:: ost.s1.grd_wrappers.multi_look
+
+.. automethod:: ost.s1.grd_wrappers.slice_assembly
+
+
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.s1.refine_inventory.rst b/docs/source/modules/ost.s1.refine_inventory.rst
new file mode 100644
index 00000000..c099d0b2
--- /dev/null
+++ b/docs/source/modules/ost.s1.refine_inventory.rst
@@ -0,0 +1,26 @@
+ost.s1.refine\_inventory
+========================
+
+.. automodule:: ost.s1.refine_inventory
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      search_refinement
+
+.. automethod:: ost.s1.refine_inventory.search_refinement
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.s1.rst b/docs/source/modules/ost.s1.rst
new file mode 100644
index 00000000..af3e801b
--- /dev/null
+++ b/docs/source/modules/ost.s1.rst
@@ -0,0 +1,24 @@
+﻿ost.s1
+======
+
+.. automodule:: ost.s1
+
+.. rubric:: Modules
+
+.. autosummary::
+   :toctree:
+   :recursive:
+
+   ost.s1.burst_batch
+   ost.s1.burst_inventory
+   ost.s1.burst_to_ard
+   ost.s1.download
+   ost.s1.grd_batch
+   ost.s1.grd_to_ard
+   ost.s1.grd_wrappers
+   ost.s1.refine_inventory
+   ost.s1.s1scene
+   ost.s1.search
+   ost.s1.slc_wrappers
+   ost.s1.ts
+
diff --git a/docs/source/modules/ost.s1.s1scene.rst b/docs/source/modules/ost.s1.s1scene.rst
new file mode 100644
index 00000000..5a0c2269
--- /dev/null
+++ b/docs/source/modules/ost.s1.s1scene.rst
@@ -0,0 +1,21 @@
+ost.s1.s1scene
+==============
+
+.. automodule:: ost.s1.s1scene
+
+   .. rubric:: Classes
+
+   .. autosummary::
+      :nosignatures:
+      :recursive:
+   
+      Sentinel1Scene
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.s1.s1scenes.Sentinel1Scene.rst b/docs/source/modules/ost.s1.s1scenes.Sentinel1Scene.rst
new file mode 100644
index 00000000..eb3fa4c4
--- /dev/null
+++ b/docs/source/modules/ost.s1.s1scenes.Sentinel1Scene.rst
@@ -0,0 +1,116 @@
+ost.s1.s1scene.Sentinel1Scene
+==============================
+
+.. autoclass:: ost.s1.s1scene.Sentinel1Scene
+
+    .. rubric:: Attributes
+
+    .. autosummary::
+
+        ~Sentinel1Scene.scene_id = scene_id
+        ~Sentinel1Scene.mission_id = scene_id[0:3]
+        ~Sentinel1Scene.mode_beam = scene_id[4:6]
+        ~Sentinel1Scene.product_type = scene_id[7:10]
+        ~Sentinel1Scene.resolution_class = scene_id[10]
+        ~Sentinel1Scene.proc_level = scene_id[12]
+        ~Sentinel1Scene.pol_mode = scene_id[14:16]
+        ~Sentinel1Scene.start_date = scene_id[17:25]
+        ~Sentinel1Scene.start_time = scene_id[26:32]
+        ~Sentinel1Scene.stop_date = scene_id[33:41]
+        ~Sentinel1Scene.stop_time = scene_id[42:48]
+        ~Sentinel1Scene.abs_orbit = scene_id[49:55]
+        ~Sentinel1Scene.data_take_id = scene_id[57:62]
+        ~Sentinel1Scene.unique_id = scene_id[63:]
+        ~Sentinel1Scene.year = scene_id[17:21]
+        ~Sentinel1Scene.month = scene_id[21:23]
+        ~Sentinel1Scene.day = scene_id[23:25]
+        ~Sentinel1Scene.onda_class = scene_id[4:14]
+        ~Sentinel1Scene.orbit_offset
+        ~Sentinel1Scene.satellite
+        ~Sentinel1Scene.rel_orbit
+        ~Sentinel1Scene.acq_mode
+        ~Sentinel1Scene.p_type
+        ~Sentinel1Scene.product_dl_path
+        ~Sentinel1Scene.ard_dimap
+        ~Sentinel1Scene.ard_rgb
+        ~Sentinel1Scene.rgb_thumbnail
+        ~Sentinel1Scene.config_dict
+        ~Sentinel1Scene.config_file
+
+    .. rubric:: Methods
+
+    .. autosummary::
+        :nosignatures:
+
+        ~Sentinel1Scene.info
+        ~Sentinel1Scene.info_dict
+        ~Sentinel1Scene.download
+        ~Sentinel1Scene.download_path
+        ~Sentinel1Scene.get_path
+        ~Sentinel1Scene.scihub_uuid
+        ~Sentinel1Scene.scihub_url
+        ~Sentinel1Scene.scihub_md5
+        ~Sentinel1Scene.scihub_online_status
+        ~Sentinel1Scene.scihub_trigger_production
+        ~Sentinel1Scene.scihub_annotation_get
+        ~Sentinel1Scene.zip_annotation_get
+        ~Sentinel1Scene.safe_annotation_get
+        ~Sentinel1Scene.ondadias_uuid
+        ~Sentinel1Scene.asf_url
+        ~Sentinel1Scene.peps_uuid
+        ~Sentinel1Scene.peps_online_status
+        ~Sentinel1Scene.get_ard_parameters
+        ~Sentinel1Scene.update_ard_parameters
+        ~Sentinel1Scene.set_external_dem
+        ~Sentinel1Scene.create_ard
+        ~Sentinel1Scene.create_rgb
+        ~Sentinel1Scene.create_rgb_thumbnail
+        ~Sentinel1Scene.visualise_rgb
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.info
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.info_dict
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.download
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.download_path
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.get_path
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.scihub_uuid
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.scihub_url
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.scihub_md5
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.scihub_online_status
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.scihub_trigger_production
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.scihub_annotation_get
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.zip_annotation_get
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.safe_annotation_get
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.ondadias_uuid
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.asf_url
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.peps_uuid
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.peps_online_status
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.get_ard_parameters
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.update_ard_parameters
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.set_external_dem
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.create_ard
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.create_rgb
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.create_rgb_thumbnail
+
+.. automethod:: ost.s1.s1scene.Sentinel1Scene.visualise_rgb
diff --git a/docs/source/modules/ost.s1.search.rst b/docs/source/modules/ost.s1.search.rst
new file mode 100644
index 00000000..3afdd25e
--- /dev/null
+++ b/docs/source/modules/ost.s1.search.rst
@@ -0,0 +1,29 @@
+ost.s1.search
+=============
+
+.. automodule:: ost.s1.search
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      check_availability
+      scihub_catalogue
+
+.. automethod:: ost.s1.search.check_availability
+
+.. automethod:: ost.s1.search.scihub_catalogue
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.s1.slc_wrappers.rst b/docs/source/modules/ost.s1.slc_wrappers.rst
new file mode 100644
index 00000000..350b8d36
--- /dev/null
+++ b/docs/source/modules/ost.s1.slc_wrappers.rst
@@ -0,0 +1,39 @@
+ost.s1.slc\_wrappers
+====================
+
+.. automodule:: ost.s1.slc_wrappers
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      burst_import
+      calibration
+      coherence
+      coreg
+      coreg2
+      ha_alpha
+
+.. automethod:: ost.s1.slc_wrappers.burst_import
+
+.. automethod:: ost.s1.slc_wrappers.calibration
+
+.. automethod:: ost.s1.slc_wrappers.coherence
+
+.. automethod:: ost.s1.slc_wrappers.coreg
+
+.. automethod:: ost.s1.slc_wrappers.coreg2
+
+.. automethod:: ost.s1.slc_wrappers.ha_alpha
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/modules/ost.s1.ts.rst b/docs/source/modules/ost.s1.ts.rst
new file mode 100644
index 00000000..60a6df7e
--- /dev/null
+++ b/docs/source/modules/ost.s1.ts.rst
@@ -0,0 +1,31 @@
+ost.s1.ts
+=========
+
+.. automodule:: ost.s1.ts
+
+   .. rubric:: Functions
+
+   .. autosummary::
+      :nosignatures:
+   
+      create_datelist
+      create_ts_animation
+
+.. automethod:: ost.s1.ts.create_datelist
+
+.. automethod:: ost.s1.ts.create_ts_animation
+
+
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/setup/contribute.rst b/docs/source/setup/contribute.rst
new file mode 100644
index 00000000..85ae61e5
--- /dev/null
+++ b/docs/source/setup/contribute.rst
@@ -0,0 +1,10 @@
+Contribute
+==========
+
+.. warning::
+
+    This documentation page is under construction.
+
+.. tip::
+
+    For specific help, please open an issue on our repository by clicking on this `link <https://github.com/ESA-PhiLab/OpenSarToolkit/issues>`__.
\ No newline at end of file
diff --git a/docs/source/setup/contributors.rst b/docs/source/setup/contributors.rst
new file mode 100644
index 00000000..fd3e4c7f
--- /dev/null
+++ b/docs/source/setup/contributors.rst
@@ -0,0 +1,10 @@
+Contributors
+============
+
+.. warning::
+
+    This documentation page is under construction.
+
+.. tip::
+
+    For specific help, please open an issue on our repository by clicking on this `link <https://github.com/ESA-PhiLab/OpenSarToolkit/issues>`__.
\ No newline at end of file
diff --git a/docs/source/setup/index.rst b/docs/source/setup/index.rst
new file mode 100644
index 00000000..d0fba1f2
--- /dev/null
+++ b/docs/source/setup/index.rst
@@ -0,0 +1,18 @@
+Getting started
+===============
+
+.. warning::
+
+    This documentation page is under construction.
+
+.. tip::
+
+    For specific help, please open an issue on our repository by clicking on this `link <https://github.com/ESA-PhiLab/OpenSarToolkit/issues>`__.
+
+.. toctree::
+    :maxdepth: 2
+    
+    readme <readme>
+    installation
+    contribute
+    contributors
\ No newline at end of file
diff --git a/docs/source/setup/installation.rst b/docs/source/setup/installation.rst
new file mode 100644
index 00000000..ebb23155
--- /dev/null
+++ b/docs/source/setup/installation.rst
@@ -0,0 +1,10 @@
+Installation
+============
+
+.. warning::
+
+    This documentation page is under construction.
+
+.. tip::
+
+    For specific help, please open an issue on our repository by clicking on this `link <https://github.com/ESA-PhiLab/OpenSarToolkit/issues>`__.
\ No newline at end of file
diff --git a/docs/source/setup/readme.rst b/docs/source/setup/readme.rst
new file mode 100644
index 00000000..ec7f5245
--- /dev/null
+++ b/docs/source/setup/readme.rst
@@ -0,0 +1,1 @@
+.. include:: ../../../README.rst
\ No newline at end of file
diff --git a/ost/Project.py b/ost/Project.py
index bb5ae037..59a07979 100644
--- a/ost/Project.py
+++ b/ost/Project.py
@@ -36,19 +36,18 @@
 
 # global variables
 OST_DATEFORMAT = "%Y-%m-%d"
-OST_INVENTORY_FILE = 'full.inventory.gpkg'
+OST_INVENTORY_FILE = "full.inventory.gpkg"
 
 
 class Generic:
-
     def __init__(
-            self,
-            project_dir,
-            aoi,
-            start='1978-06-28',
-            end=datetime.today().strftime(OST_DATEFORMAT),
-            data_mount=None,
-            log_level=logging.INFO
+        self,
+        project_dir,
+        aoi,
+        start="1978-06-28",
+        end=datetime.today().strftime(OST_DATEFORMAT),
+        data_mount=None,
+        log_level=logging.INFO,
     ):
 
         # ------------------------------------------
@@ -65,40 +64,34 @@ def __init__(
         # create project directory if not existent
         try:
             self.project_dir.mkdir(parents=True, exist_ok=True)
-            logger.info(f'Created project directory at {self.project_dir}')
+            logger.info(f"Created project directory at {self.project_dir}")
         except FileExistsError:
-            logger.info('Project directory already exists. '
-                        'No data has been deleted at this point but '
-                        'make sure you really want to use this folder.')
+            logger.info(
+                "Project directory already exists. "
+                "No data has been deleted at this point but "
+                "make sure you really want to use this folder."
+            )
 
         # define project sub-directories if not set, and create folders
-        self.download_dir = self.project_dir.joinpath('download')
+        self.download_dir = self.project_dir / "download"
         self.download_dir.mkdir(parents=True, exist_ok=True)
-        logger.info(
-            f'Downloaded data will be stored in: {self.download_dir}.'
-        )
+        logger.info(f"Downloaded data will be stored in: {self.download_dir}.")
 
-        self.inventory_dir = self.project_dir.joinpath('inventory')
+        self.inventory_dir = self.project_dir / "inventory"
         self.inventory_dir.mkdir(parents=True, exist_ok=True)
-        logger.info(
-            f'Inventory files will be stored in: {self.inventory_dir}.'
-        )
+        logger.info(f"Inventory files will be stored in: {self.inventory_dir}.")
 
-        self.processing_dir = self.project_dir.joinpath('processing')
+        self.processing_dir = self.project_dir / "processing"
         self.processing_dir.mkdir(parents=True, exist_ok=True)
-        logger.info(
-            f'Processed data will be stored in: {self.processing_dir}.'
-        )
+        logger.info(f"Processed data will be stored in: {self.processing_dir}.")
 
-        self.temp_dir = self.project_dir.joinpath('temp')
+        self.temp_dir = self.project_dir / "temp"
         self.temp_dir.mkdir(parents=True, exist_ok=True)
-        logger.info(
-            f'Using {self.temp_dir} as directory for temporary files.'
-        )
+        logger.info(f"Using {self.temp_dir} as directory for temporary files.")
 
         # ------------------------------------------
         # 3 create a standard logfile
-        setup_logfile(self.project_dir.joinpath('.processing.log'))
+        setup_logfile(self.project_dir / ".processing.log")
 
         # ------------------------------------------
         # 4 handle AOI (read and get back WKT)
@@ -110,15 +103,17 @@ def __init__(
             datetime.strptime(start, OST_DATEFORMAT)
             self.start = start
         except ValueError:
-            raise ValueError("Incorrect date format for start date. "
-                             "It should be YYYY-MM-DD")
+            raise ValueError(
+                "Incorrect date format for start date. " "It should be YYYY-MM-DD"
+            )
 
         try:
             datetime.strptime(end, OST_DATEFORMAT)
             self.end = end
         except ValueError:
-            raise ValueError("Incorrect date format for end date. "
-                             "It should be YYYY-MM-DD")
+            raise ValueError(
+                "Incorrect date format for end date. " "It should be YYYY-MM-DD"
+            )
 
         # ------------------------------------------
         # 6 Check data mount
@@ -126,41 +121,41 @@ def __init__(
             if Path(data_mount).exists():
                 self.data_mount = Path(data_mount)
             else:
-                raise NotADirectoryError(f'{data_mount} is not a directory.')
+                raise NotADirectoryError(f"{data_mount} is not a directory.")
         else:
             self.data_mount = None
 
         # ------------------------------------------
         # 7 put all parameters in a dictionary
         self.config_dict = {
-            'project_dir': str(self.project_dir),
-            'download_dir': str(self.download_dir),
-            'inventory_dir': str(self.inventory_dir),
-            'processing_dir': str(self.processing_dir),
-            'temp_dir': str(self.temp_dir),
-            'data_mount': str(self.data_mount),
-            'aoi': self.aoi,
-            'start_date': self.start,
-            'end_date': self.end
+            "project_dir": str(self.project_dir),
+            "download_dir": str(self.download_dir),
+            "inventory_dir": str(self.inventory_dir),
+            "processing_dir": str(self.processing_dir),
+            "temp_dir": str(self.temp_dir),
+            "data_mount": str(self.data_mount),
+            "aoi": self.aoi,
+            "start_date": self.start,
+            "end_date": self.end,
         }
 
 
 class Sentinel1(Generic):
-    """ A Sentinel-1 specific subclass of the Generic OST class
+    """A Sentinel-1 specific subclass of the Generic OST class
     This subclass creates a Sentinel-1 specific
     """
 
     def __init__(
-            self,
-            project_dir,
-            aoi,
-            start='2014-10-01',
-            end=datetime.today().strftime(OST_DATEFORMAT),
-            data_mount=None,
-            product_type='*',
-            beam_mode='*',
-            polarisation='*',
-            log_level=logging.INFO
+        self,
+        project_dir,
+        aoi,
+        start="2014-10-01",
+        end=datetime.today().strftime(OST_DATEFORMAT),
+        data_mount=None,
+        product_type="*",
+        beam_mode="*",
+        polarisation="*",
+        log_level=logging.INFO,
     ):
 
         # ------------------------------------------
@@ -169,38 +164,34 @@ def __init__(
 
         # ------------------------------------------
         # 2 Check and set product type
-        if product_type in ['*', 'RAW', 'SLC', 'GRD']:
+        if product_type in ["*", "RAW", "SLC", "GRD"]:
             self.product_type = product_type
         else:
-            raise ValueError(
-                "Product type must be one out of '*', 'RAW', 'SLC', 'GRD'"
-            )
+            raise ValueError("Product type must be one out of '*', 'RAW', 'SLC', 'GRD'")
 
         # ------------------------------------------
         # 3 Check and set beam mode
-        if beam_mode in ['*', 'IW', 'EW', 'SM']:
+        if beam_mode in ["*", "IW", "EW", "SM"]:
             self.beam_mode = beam_mode
         else:
             raise ValueError("Beam mode must be one out of 'IW', 'EW', 'SM'")
 
         # ------------------------------------------
         # 4 Check and set polarisations
-        possible_pols = ['*', 'VV', 'VH', 'HV', 'HH', 'VV VH', 'HH HV']
+        possible_pols = ["*", "VV", "VH", "HV", "HH", "VV VH", "HH HV"]
         if polarisation in possible_pols:
             self.polarisation = polarisation
         else:
-            raise ValueError(
-                f"Polarisation must be one out of {possible_pols}"
-            )
+            raise ValueError(f"Polarisation must be one out of {possible_pols}")
 
         # ------------------------------------------
         # 5 Initialize the inventory file
-        inventory_file = self.inventory_dir.joinpath(OST_INVENTORY_FILE)
+        inventory_file = self.inventory_dir / OST_INVENTORY_FILE
         if inventory_file.exists():
             self.inventory_file = inventory_file
             logging.info(
-                'Found an existing inventory file. This can be overwritten '
-                'by re-executing the search.'
+                "Found an existing inventory file. This can be overwritten "
+                "by re-executing the search."
             )
             self.read_inventory()
         else:
@@ -233,8 +224,12 @@ def __init__(
 
     # ------------------------------------------
     # methods
-    def search(self, outfile=OST_INVENTORY_FILE, append=False,
-               base_url='https://apihub.copernicus.eu/apihub'):
+    def search(
+        self,
+        outfile=OST_INVENTORY_FILE,
+        append=False,
+        base_url="https://apihub.copernicus.eu/apihub",
+    ):
         """High Level search function
 
         :param outfile:
@@ -256,7 +251,7 @@ def search(self, outfile=OST_INVENTORY_FILE, append=False,
 
         # construct the final query
         query = urllib.parse.quote(
-            f'Sentinel-1 AND {product_specs} AND {aoi} AND {toi}'
+            f"Sentinel-1 AND {product_specs} AND {aoi} AND {toi}"
         )
 
         if not self.scihub_uname or not self.scihub_pword:
@@ -265,22 +260,24 @@ def search(self, outfile=OST_INVENTORY_FILE, append=False,
 
         # do the search
         if outfile == OST_INVENTORY_FILE:
-            self.inventory_file = self.inventory_dir.joinpath(
-                OST_INVENTORY_FILE
-            )
+            self.inventory_file = self.inventory_dir / OST_INVENTORY_FILE
         else:
             self.inventory_file = outfile
 
         search.scihub_catalogue(
-            query, self.inventory_file, append,
-            self.scihub_uname, self.scihub_pword, base_url
+            query,
+            self.inventory_file,
+            append,
+            self.scihub_uname,
+            self.scihub_pword,
+            base_url,
         )
 
         if self.inventory_file.exists():
             # read inventory into the inventory attribute
             self.read_inventory()
         else:
-            logger.info('No images found in the AOI for this date range')
+            logger.info("No images found in the AOI for this date range")
 
     def read_inventory(self):
         """Read the Sentinel-1 data inventory from a OST invetory shapefile
@@ -289,14 +286,29 @@ def read_inventory(self):
 
         # define column names of inventory file (since in shp they are
         # truncated)
-        column_names = ['id', 'identifier', 'polarisationmode',
-                        'orbitdirection', 'acquisitiondate', 'relativeorbit',
-                        'orbitnumber', 'product_type', 'slicenumber', 'size',
-                        'beginposition', 'endposition',
-                        'lastrelativeorbitnumber', 'lastorbitnumber',
-                        'uuid', 'platformidentifier', 'missiondatatakeid',
-                        'swathidentifier', 'ingestiondate',
-                        'sensoroperationalmode', 'geometry']
+        column_names = [
+            "id",
+            "identifier",
+            "polarisationmode",
+            "orbitdirection",
+            "acquisitiondate",
+            "relativeorbit",
+            "orbitnumber",
+            "product_type",
+            "slicenumber",
+            "size",
+            "beginposition",
+            "endposition",
+            "lastrelativeorbitnumber",
+            "lastorbitnumber",
+            "uuid",
+            "platformidentifier",
+            "missiondatatakeid",
+            "swathidentifier",
+            "ingestiondate",
+            "sensoroperationalmode",
+            "geometry",
+        ]
 
         geodataframe = gpd.read_file(self.inventory_file)
         geodataframe.columns = column_names
@@ -314,85 +326,83 @@ def download_size(self, inventory_df=None):
         :return:
         """
         if inventory_df is None:
-            download_size = self.inventory[
-                'size'].str.replace(' GB', '').astype('float32').sum()
+            download_size = (
+                self.inventory["size"].str.replace(" GB", "").astype("float32").sum()
+            )
         else:
-            download_size = inventory_df[
-                'size'].str.replace(' GB', '').astype('float32').sum()
+            download_size = (
+                inventory_df["size"].str.replace(" GB", "").astype("float32").sum()
+            )
 
-        logger.info(
-            f'There are about {download_size} GB need to be downloaded.'
-        )
+        logger.info(f"There are about {download_size} GB need to be downloaded.")
 
-    def refine_inventory(self,
-                         exclude_marginal=True,
-                         full_aoi_crossing=True,
-                         mosaic_refine=True,
-                         area_reduce=0.05,
-                         complete_coverage=True):
-
-        self.refined_inventory_dict, self.coverages = (
-            refine_inventory.search_refinement(
-                self.aoi,
-                self.inventory,
-                self.inventory_dir,
-                exclude_marginal=exclude_marginal,
-                full_aoi_crossing=full_aoi_crossing,
-                mosaic_refine=mosaic_refine,
-                area_reduce=area_reduce,
-                complete_coverage=complete_coverage
-            )
+    def refine_inventory(
+        self,
+        exclude_marginal=True,
+        full_aoi_crossing=True,
+        mosaic_refine=True,
+        area_reduce=0.05,
+        complete_coverage=True,
+    ):
+
+        (
+            self.refined_inventory_dict,
+            self.coverages,
+        ) = refine_inventory.search_refinement(
+            self.aoi,
+            self.inventory,
+            self.inventory_dir,
+            exclude_marginal=exclude_marginal,
+            full_aoi_crossing=full_aoi_crossing,
+            mosaic_refine=mosaic_refine,
+            area_reduce=area_reduce,
+            complete_coverage=complete_coverage,
         )
 
         # summing up information
-        print('--------------------------------------------')
-        print(' Summing up the info about mosaics')
-        print('--------------------------------------------')
+        print("--------------------------------------------")
+        print(" Summing up the info about mosaics")
+        print("--------------------------------------------")
         for key in self.refined_inventory_dict:
-            print('')
-            print(f' {self.coverages[key]} mosaics for mosaic key {key}')
+            print("")
+            print(f" {self.coverages[key]} mosaics for mosaic key {key}")
 
-    def download(self, inventory_df, mirror=None, concurrent=2,
-                 uname=None, pword=None):
+    def download(self, inventory_df, mirror=None, concurrent=2, uname=None, pword=None):
 
         # if an old inventory exists drop download_path
-        if 'download_path' in inventory_df:
-            inventory_df.drop('download_path', axis=1)
+        if "download_path" in inventory_df:
+            inventory_df.drop("download_path", axis=1)
 
         # check if scenes exist
         inventory_df = search.check_availability(
-            inventory_df, self.download_dir, self.data_mount)
+            inventory_df, self.download_dir, self.data_mount
+        )
 
         # extract only those scenes that need to be downloaded
-        download_df = inventory_df[inventory_df.download_path == 'None']
+        download_df = inventory_df[inventory_df.download_path == "None"]
 
         # to download or not ot download - that is here the question
         if download_df.empty:
-            logger.info('All scenes are ready for being processed.')
+            logger.info("All scenes are ready for being processed.")
         else:
-            logger.info('One or more scene(s) need(s) to be downloaded.')
+            logger.info("One or more scene(s) need(s) to be downloaded.")
             download.download_sentinel1(
                 download_df,
                 self.download_dir,
                 mirror=mirror,
                 concurrent=concurrent,
                 uname=uname,
-                pword=pword
+                pword=pword,
             )
 
     def create_burst_inventory(
-            self,
-            inventory_df=None,
-            refine=True,
-            outfile=None,
-            uname=None,
-            pword=None
+        self, inventory_df=None, refine=True, outfile=None, uname=None, pword=None
     ):
 
         # assert SLC product type
-        if not self.product_type == 'SLC':
+        if not self.product_type == "SLC":
             raise ValueError(
-                'Burst inventory is only possible for the SLC product type'
+                "Burst inventory is only possible for the SLC product type"
             )
 
         # in case a custom inventory is given (e.g. a refined inventory)
@@ -400,14 +410,14 @@ def create_burst_inventory(
             inventory_df = self.inventory
         else:
             # assert that all products are SLCs
-            if not inventory_df.product_type.unique() == 'SLC':
+            if not inventory_df.product_type.unique() == "SLC":
                 raise ValueError(
-                    'The inventory dataframe can only contain SLC products '
-                    'for the burst inventory '
+                    "The inventory dataframe can only contain SLC products "
+                    "for the burst inventory "
                 )
 
         if not outfile:
-            outfile = self.inventory_dir.joinpath('burst_inventory.gpkg')
+            outfile = self.inventory_dir / "burst_inventory.gpkg"
 
         # run the burst inventory
         self.burst_inventory = burst_inventory.burst_inventory(
@@ -415,14 +425,14 @@ def create_burst_inventory(
             outfile,
             download_dir=self.download_dir,
             data_mount=self.data_mount,
-            uname=uname, pword=pword
+            uname=uname,
+            pword=pword,
         )
 
         # refine the burst inventory
         if refine:
             self.burst_inventory = burst_inventory.refine_burst_inventory(
-                self.aoi, self.burst_inventory,
-                f'{str(outfile)[:-5]}.refined.gpkg'
+                self.aoi, self.burst_inventory, f"{str(outfile)[:-5]}.refined.gpkg"
             )
 
     def read_burst_inventory(self, burst_file=None):
@@ -433,28 +443,31 @@ def read_burst_inventory(self, burst_file=None):
         """
         if not burst_file:
 
-            non_ref = self.inventory_dir.joinpath('burst_inventory.gpkg')
-            refined = self.inventory_dir.joinpath(
-                'burst_inventory.refined.gpkg'
-            )
+            non_ref = self.inventory_dir / "burst_inventory.gpkg"
+            refined = self.inventory_dir / "burst_inventory.refined.gpkg"
 
             if refined.exists():
-                logger.info(
-                    f'Importing refined burst inventory file {str(refined)}.')
+                logger.info(f"Importing refined burst inventory file {str(refined)}.")
                 burst_file = refined
             elif non_ref.exists():
-                logger.info(
-                    f'Importing refined burst inventory file {str(non_ref)}.')
+                logger.info(f"Importing refined burst inventory file {str(non_ref)}.")
                 burst_file = non_ref
             else:
                 raise FileNotFoundError(
-                    'No previously created burst inventory file '
-                    'has been found.'
+                    "No previously created burst inventory file " "has been found."
                 )
         # define column names of file (since in shp they are truncated)
         # create column names for empty data frame
-        column_names = ['SceneID', 'Track', 'Direction', 'Date', 'SwathID',
-                        'AnxTime', 'BurstNr', 'geometry']
+        column_names = [
+            "SceneID",
+            "Track",
+            "Direction",
+            "Date",
+            "SwathID",
+            "AnxTime",
+            "BurstNr",
+            "geometry",
+        ]
 
         geodataframe = gpd.read_file(burst_file)
         geodataframe = geodataframe[column_names]
@@ -462,107 +475,124 @@ def read_burst_inventory(self, burst_file=None):
 
         return geodataframe
 
-    def plot_inventory(self, inventory_df=None, transparency=0.05,
-                       annotate=False):
+    def plot_inventory(self, inventory_df=None, transparency=0.05, annotate=False):
 
         if inventory_df is None:
-            vec.plot_inventory(
-                self.aoi, self.inventory, transparency, annotate
-            )
+            vec.plot_inventory(self.aoi, self.inventory, transparency, annotate)
         else:
             vec.plot_inventory(self.aoi, inventory_df, transparency, annotate)
 
 
 class Sentinel1Batch(Sentinel1):
-    """ A Sentinel-1 specific subclass of the Generic OST class
+    """A Sentinel-1 specific subclass of the Generic OST class
     This subclass creates a Sentinel-1 specific
     """
 
     def __init__(
-            self,
-            project_dir,
-            aoi,
-            start='2014-10-01',
-            end=datetime.today().strftime(OST_DATEFORMAT),
-            data_mount=None,
-            product_type='SLC',
-            beam_mode='IW',
-            polarisation='*',
-            ard_type='OST-GTC',
-            snap_cpu_parallelism=cpu_count(),
-            max_workers=1,
-            log_level=logging.INFO
+        self,
+        project_dir,
+        aoi,
+        start="2014-10-01",
+        end=datetime.today().strftime(OST_DATEFORMAT),
+        data_mount=None,
+        product_type="SLC",
+        beam_mode="IW",
+        polarisation="*",
+        ard_type="OST-GTC",
+        snap_cpu_parallelism=cpu_count(),
+        max_workers=1,
+        log_level=logging.INFO,
     ):
         # ------------------------------------------
         # 1 Initialize super class
         super().__init__(
-            project_dir, aoi, start, end, data_mount,
-            product_type, beam_mode, polarisation, log_level
+            project_dir,
+            aoi,
+            start,
+            end,
+            data_mount,
+            product_type,
+            beam_mode,
+            polarisation,
+            log_level,
         )
 
         # ---------------------------------------
         # 1 Check and set ARD type
 
         # possible ard types to select from for GRD
-        if product_type == 'GRD':
-            ard_types_grd = ['CEOS', 'Earth-Engine', 'OST-GTC', 'OST-RTC']
+        if product_type == "GRD":
+            ard_types_grd = ["CEOS", "Earth-Engine", "OST-GTC", "OST-RTC"]
             if ard_type in ard_types_grd:
                 self.ard_type = ard_type
             else:
-                raise ValueError('No valid ARD type for product type GRD.'
-                                 f'Select from {ard_types_grd}')
+                raise ValueError(
+                    "No valid ARD type for product type GRD."
+                    f"Select from {ard_types_grd}"
+                )
 
         # possible ard types to select from for GRD
-        elif product_type == 'SLC':
+        elif product_type == "SLC":
 
-            ard_types_slc = ['OST-GTC', 'OST-RTC', 'OST-COH', 'OST-RTCCOH',
-                             'OST-POL', 'OST-ALL']
+            ard_types_slc = [
+                "OST-GTC",
+                "OST-RTC",
+                "OST-COH",
+                "OST-RTCCOH",
+                "OST-POL",
+                "OST-ALL",
+            ]
 
             if ard_type in ard_types_slc:
                 self.ard_type = ard_type
             else:
-                raise ValueError('No valid ARD type for product type GRD.'
-                                 f'Select from {ard_types_slc}')
+                raise ValueError(
+                    "No valid ARD type for product type GRD."
+                    f"Select from {ard_types_slc}"
+                )
 
         # otherwise the product type is not supported
         else:
-            raise ValueError(f'Product type {self.product_type} not '
-                             f'supported for processing. Only GRD and SLC '
-                             f'are supported.')
+            raise ValueError(
+                f"Product type {self.product_type} not "
+                f"supported for processing. Only GRD and SLC "
+                f"are supported."
+            )
 
         # ---------------------------------------
         # 2 Check beam mode
-        if not beam_mode == 'IW':
+        if not beam_mode == "IW":
             raise ValueError("Only 'IW' beam mode supported for processing.")
 
         # ---------------------------------------
         # 3 Add snap_cpu_parallelism
-        self.config_dict['snap_cpu_parallelism'] = snap_cpu_parallelism
-        self.config_dict['max_workers'] = max_workers
-        self.config_dict['executor_type'] = 'billiard'
+        self.config_dict["snap_cpu_parallelism"] = snap_cpu_parallelism
+        self.config_dict["max_workers"] = max_workers
+        self.config_dict["executor_type"] = "billiard"
 
         # ---------------------------------------
         # 4 Set up project JSON
-        self.config_file = self.project_dir.joinpath('config.json')
+        self.config_file = self.project_dir / "config.json"
         self.ard_parameters = self.get_ard_parameters(ard_type)
 
         # re-create config dict with update ard parameters
-        self.config_dict.update(
-            processing=self.ard_parameters
-        )
+        self.config_dict.update(processing=self.ard_parameters)
 
     # ---------------------------------------
     # methods
     def get_ard_parameters(self, ard_type):
 
         # find respective template for selected ARD type
-        template_file = OST_ROOT.joinpath(
-            f"graphs/ard_json/{self.product_type.lower()}"
-            f".{ard_type.replace('-', '_').lower()}.json"
+        template_file = (
+            OST_ROOT
+            / "graphs"
+            / "ard_json"
+            / f"{self.product_type.lower()}.{ard_type.replace('-', '_').lower()}.json"
         )
+
         # open and load parameters
-        with open(template_file, 'r') as ard_file:
-            self.ard_parameters = json.load(ard_file)['processing']
+        with open(template_file, "r") as ard_file:
+            self.ard_parameters = json.load(ard_file)["processing"]
 
         # returning ard_parameters
         return self.ard_parameters
@@ -577,53 +607,50 @@ def update_ard_parameters(self, ard_type=None):
         check_ard_parameters(self.ard_parameters)
 
         # re-create project dict with update ard parameters
-        self.config_dict.update(
-            processing=self.ard_parameters
-        )
+        self.config_dict.update(processing=self.ard_parameters)
 
         # dump to json file
-        with open(self.config_file, 'w') as outfile:
+        with open(self.config_file, "w") as outfile:
             json.dump(self.config_dict, outfile, indent=4)
 
     def set_external_dem(self, dem_file, ellipsoid_correction=True):
 
         # check if file exists
         if not Path(dem_file).exists():
-            raise FileNotFoundError(f'No file found at {dem_file}.')
+            raise FileNotFoundError(f"No file found at {dem_file}.")
 
         # get no data value
         with rasterio.open(dem_file) as file:
             dem_nodata = int(file.nodata)
 
         # get resampling adn projection‚
-        img_res = self.ard_parameters['single_ARD']['dem']['image_resampling']
-        dem_res = self.ard_parameters['single_ARD']['dem']['dem_resampling']
-        projection = self.ard_parameters['single_ARD']['dem']['out_projection']
+        img_res = self.ard_parameters["single_ARD"]["dem"]["image_resampling"]
+        dem_res = self.ard_parameters["single_ARD"]["dem"]["dem_resampling"]
+        projection = self.ard_parameters["single_ARD"]["dem"]["out_projection"]
 
         # update ard parameters
-        dem_dict = dict({'dem_name': 'External DEM',
-                         'dem_file': dem_file,
-                         'dem_nodata': dem_nodata,
-                         'dem_resampling': dem_res,
-                         'image_resampling': img_res,
-                         'egm_correction': ellipsoid_correction,
-                         'out_projection': projection
-                         })
+        dem_dict = dict(
+            {
+                "dem_name": "External DEM",
+                "dem_file": dem_file,
+                "dem_nodata": dem_nodata,
+                "dem_resampling": dem_res,
+                "image_resampling": img_res,
+                "egm_correction": ellipsoid_correction,
+                "out_projection": projection,
+            }
+        )
 
         # update ard_parameters
-        self.ard_parameters['single_ARD']['dem'] = dem_dict
+        self.ard_parameters["single_ARD"]["dem"] = dem_dict
 
     def pre_download_srtm(self):
 
-        logger.info('Pre-downloading SRTM tiles')
+        logger.info("Pre-downloading SRTM tiles")
         srtm.download_srtm(self.aoi)
 
     def bursts_to_ards(
-            self,
-            timeseries=False,
-            timescan=False,
-            mosaic=False,
-            overwrite=False
+        self, timeseries=False, timescan=False, mosaic=False, overwrite=False
     ):
         """Batch processing function for full burst pre-processing workflow
 
@@ -653,42 +680,40 @@ def bursts_to_ards(
         center_lat = loads(self.aoi).centroid.y
         if float(center_lat) > 59 or float(center_lat) < -59:
 
-            if 'SRTM' in self.ard_parameters['single_ARD']['dem']['dem_name']:
+            if "SRTM" in self.ard_parameters["single_ARD"]["dem"]["dem_name"]:
                 logger.info(
-                    'Scene is outside SRTM coverage. Snap will therefore use '
-                    'the Copernicus 30m Global DEM. '
+                    "Scene is outside SRTM coverage. Snap will therefore use "
+                    "the Copernicus 30m Global DEM. "
                 )
 
-                self.ard_parameters['single_ARD']['dem'][
-                    'dem_name'] = 'Copernicus 30m Global DEM'
+                self.ard_parameters["single_ARD"]["dem"][
+                    "dem_name"
+                ] = "Copernicus 30m Global DEM"
 
-            if self.ard_parameters['single_ARD']['dem'][
-                'out_projection'
-            ] == 4326:
+            if self.ard_parameters["single_ARD"]["dem"]["out_projection"] == 4326:
 
                 logger.info(
-                    'The scene\'s location is towards the poles. '
-                    'Consider to use a stereographic projection.'
+                    "The scene's location is towards the poles. "
+                    "Consider to use a stereographic projection."
                 )
 
                 epsg = input(
-                    'Type an alternative EPSG code for the projection of the '
-                    'output data or just press enter for keeping Lat/Lon '
-                    'coordinate system (e.g. 3413 for NSIDC Sea Ice Polar '
-                    'Stereographic North projection, or 3976 for '
-                    'NSIDC Sea Ice Polar Stereographic South projection'
+                    "Type an alternative EPSG code for the projection of the "
+                    "output data or just press enter for keeping Lat/Lon "
+                    "coordinate system (e.g. 3413 for NSIDC Sea Ice Polar "
+                    "Stereographic North projection, or 3976 for "
+                    "NSIDC Sea Ice Polar Stereographic South projection"
                 )
 
                 if not epsg:
                     epsg = 4326
 
-                self.ard_parameters['single_ARD']['dem'][
-                    'out_projection'] = int(epsg)
+                self.ard_parameters["single_ARD"]["dem"]["out_projection"] = int(epsg)
 
         # --------------------------------------------
         # 3 subset determination
         # we need a check function that checks
-        self.config_dict['subset'] = False
+        self.config_dict["subset"] = False
         # This does not work at the moment, and maybe does not even make sense,
         # since for the co-registration we would need a sufficient
         # part of the image
@@ -705,20 +730,20 @@ def bursts_to_ards(
         # 1 delete data from previous runnings
         # delete data in temporary directory in case there is
         # something left from previous runs
-        h.remove_folder_content(self.config_dict['temp_dir'])
+        h.remove_folder_content(self.config_dict["temp_dir"])
 
         # in case we strat from scratch, delete all data
         # within processing folder
         if overwrite:
-            logger.info('Deleting processing folder to start from scratch')
-            h.remove_folder_content(self.config_dict['processing_dir'])
+            logger.info("Deleting processing folder to start from scratch")
+            h.remove_folder_content(self.config_dict["processing_dir"])
 
         # --------------------------------------------
         # 5 set resolution to degree
         # self.ard_parameters['resolution'] = h.resolution_in_degree(
         #    self.center_lat, self.ard_parameters['resolution'])
 
-        #if self.config_dict['max_workers'] > 1 and self.ard_parameters['single_ARD']['dem']['dem_name'] == 'SRTM 1Sec HGT':
+        # if self.config_dict['max_workers'] > 1 and self.ard_parameters['single_ARD']['dem']['dem_name'] == 'SRTM 1Sec HGT':
         #    self.pre_download_srtm()
 
         # --------------------------------------------
@@ -726,8 +751,7 @@ def bursts_to_ards(
         i = 1
         while i < 4:
             processed_bursts_df = burst_batch.bursts_to_ards(
-                self.burst_inventory,
-                self.config_file
+                self.burst_inventory, self.config_file
             )
 
             if False in processed_bursts_df.error.isnull().tolist():
@@ -736,83 +760,76 @@ def bursts_to_ards(
                 i = 5
 
         # write processed df to file
-        processing_dir = Path(self.config_dict['processing_dir'])
-        processed_bursts_df.to_pickle(
-            processing_dir.joinpath('processed_bursts.pickle')
-        )
+        processing_dir = Path(self.config_dict["processing_dir"])
+        processed_bursts_df.to_pickle(processing_dir / "processed_bursts.pickle")
 
         # if not all have been processed, raise an error to avoid
         # false time-series processing
         if i == 4:
-            raise RuntimeError(
-                'Not all all bursts have been successfully processed'
-            )
+            raise RuntimeError("Not all all bursts have been successfully processed")
 
         # --------------------------------------------
         # 6 run the timeseries creation
         if timeseries or timescan:
-            tseries_df = burst_batch.ards_to_timeseries(
-                self.burst_inventory, self.config_file
-            )
+            burst_batch.ards_to_timeseries(self.burst_inventory, self.config_file)
 
         # --------------------------------------------
         # 7 run the timescan creation
         if timescan:
-            burst_batch.timeseries_to_timescan(
-                self.burst_inventory, self.config_file
-            )
+            burst_batch.timeseries_to_timescan(self.burst_inventory, self.config_file)
 
         # --------------------------------------------
         # 8 mosaic the time-series
         if mosaic and timeseries:
-            burst_batch.mosaic_timeseries(
-                self.burst_inventory, self.config_file
-            )
+            burst_batch.mosaic_timeseries(self.burst_inventory, self.config_file)
 
         # --------------------------------------------
         # 9 mosaic the timescans
         if mosaic and timescan:
-            burst_batch.mosaic_timescan(
-                self.burst_inventory, self.config_file
-            )
+            burst_batch.mosaic_timescan(self.burst_inventory, self.config_file)
 
         # return tseries_df
 
     @staticmethod
     def create_timeseries_animation(
+        timeseries_dir,
+        product_list,
+        outfile,
+        shrink_factor=1,
+        resampling_factor=5,
+        duration=1,
+        add_dates=False,
+        prefix=False,
+    ):
+        ras.create_timeseries_animation(
             timeseries_dir,
             product_list,
             outfile,
-            shrink_factor=1,
-            resampling_factor=5,
-            duration=1,
-            add_dates=False,
-            prefix=False
-    ):
-        ras.create_timeseries_animation(timeseries_dir, product_list, outfile,
-                                        shrink_factor=shrink_factor,
-                                        duration=duration,
-                                        resampling_factor=resampling_factor,
-                                        add_dates=add_dates, prefix=prefix)
+            shrink_factor=shrink_factor,
+            duration=duration,
+            resampling_factor=resampling_factor,
+            add_dates=add_dates,
+            prefix=prefix,
+        )
 
     def grds_to_ards(
-            self,
-            inventory_df,
-            timeseries=False,
-            timescan=False,
-            mosaic=False,
-            overwrite=False,
-            max_workers=1,
-            executor_type='billiard'
+        self,
+        inventory_df,
+        timeseries=False,
+        timescan=False,
+        mosaic=False,
+        overwrite=False,
+        max_workers=1,
+        executor_type="billiard",
     ):
 
-        self.config_dict['max_workers'] = max_workers
-        self.config_dict['executor_type'] = executor_type
+        self.config_dict["max_workers"] = max_workers
+        self.config_dict["executor_type"] = executor_type
 
         # in case we start from scratch, delete all data
         # within processing folder
         if overwrite:
-            logger.info('Deleting processing folder to start from scratch')
+            logger.info("Deleting processing folder to start from scratch")
             h.remove_folder_content(self.processing_dir)
 
         # --------------------------------------------
@@ -822,47 +839,43 @@ def grds_to_ards(
         center_lat = loads(self.aoi).centroid.y
         if float(center_lat) > 59 or float(center_lat) < -59:
 
-            if 'SRTM' in self.ard_parameters['single_ARD']['dem']['dem_name']:
+            if "SRTM" in self.ard_parameters["single_ARD"]["dem"]["dem_name"]:
                 logger.info(
-                    'Scene is outside SRTM coverage. Snap will therefore use '
-                    'the Copernicus 30m Global DEM. '
+                    "Scene is outside SRTM coverage. Snap will therefore use "
+                    "the Copernicus 30m Global DEM. "
                 )
 
-                self.ard_parameters['single_ARD']['dem'][
-                    'dem_name'] = 'Copernicus 30m Global DEM'
+                self.ard_parameters["single_ARD"]["dem"][
+                    "dem_name"
+                ] = "Copernicus 30m Global DEM"
 
-            if self.ard_parameters['single_ARD']['dem'][
-                'out_projection'
-            ] == 4326:
+            if self.ard_parameters["single_ARD"]["dem"]["out_projection"] == 4326:
 
                 logger.info(
-                    'The scene\'s location is towards the poles. '
-                    'Consider to use a stereographic projection.'
+                    "The scene's location is towards the poles. "
+                    "Consider to use a stereographic projection."
                 )
 
                 epsg = input(
-                    'Type an alternative EPSG code for the projection of the '
-                    'output data or just press enter for keeping Lat/Lon '
-                    'coordinate system (e.g. 3413 for NSIDC Sea Ice Polar '
-                    'Stereographic North projection, or 3976 for '
-                    'NSIDC Sea Ice Polar Stereographic South projection'
+                    "Type an alternative EPSG code for the projection of the "
+                    "output data or just press enter for keeping Lat/Lon "
+                    "coordinate system (e.g. 3413 for NSIDC Sea Ice Polar "
+                    "Stereographic North projection, or 3976 for "
+                    "NSIDC Sea Ice Polar Stereographic South projection"
                 )
 
                 if not epsg:
                     epsg = 4326
 
-                self.ard_parameters['single_ARD']['dem'][
-                    'out_projection'] = int(epsg)
+                self.ard_parameters["single_ARD"]["dem"]["out_projection"] = int(epsg)
 
         # --------------------------------------------
         # 3 subset determination
         # we need a check function that checks
-        self.config_dict.update(
-            subset=vec.set_subset(self.aoi, inventory_df)
-        )
+        self.config_dict.update(subset=vec.set_subset(self.aoi, inventory_df))
 
         # dump to json file
-        with open(self.config_file, 'w') as outfile:
+        with open(self.config_file, "w") as outfile:
             json.dump(self.config_dict, outfile, indent=4)
 
         # --------------------------------------------
@@ -874,7 +887,7 @@ def grds_to_ards(
         # 1 delete data in case of previous runs
         # delete data in temporary directory in case there is
         # something left from aborted previous runs
-        h.remove_folder_content(self.config_dict['temp_dir'])
+        h.remove_folder_content(self.config_dict["temp_dir"])
 
         # --------------------------------------------
         # 5 set resolution in degree
@@ -889,9 +902,7 @@ def grds_to_ards(
         # --------------------------------------------
         # 5 set resolution in degree
         # the grd to ard batch routine
-        processing_df = grd_batch.grd_to_ard_batch(
-            inventory_df, self.config_file
-        )
+        processing_df = grd_batch.grd_to_ard_batch(inventory_df, self.config_file)
 
         # time-series part
         if timeseries or timescan:
diff --git a/ost/__init__.py b/ost/__init__.py
index 97853a17..f4eb2e99 100644
--- a/ost/__init__.py
+++ b/ost/__init__.py
@@ -1,4 +1,4 @@
 from ost.s1.s1scene import Sentinel1Scene
 from ost.Project import Generic, Sentinel1, Sentinel1Batch
 
-__all__ = ['Sentinel1Scene', 'Sentinel1Batch', 'Sentinel1', 'Generic']
+__all__ = ["Sentinel1Scene", "Sentinel1Batch", "Sentinel1", "Generic"]
diff --git a/ost/generic/ard_to_ts.py b/ost/generic/ard_to_ts.py
index ee7f0b3e..5a259ba0 100644
--- a/ost/generic/ard_to_ts.py
+++ b/ost/generic/ard_to_ts.py
@@ -7,14 +7,7 @@
 from tempfile import TemporaryDirectory
 
 from retrying import retry
-
-try:
-    import gdal
-except ModuleNotFoundError as e:
-    try:
-        from osgeo import gdal
-    except ModuleNotFoundError:
-        raise e
+from osgeo import gdal
 
 from ost.generic.common_wrappers import create_stack, mt_speckle_filter
 from ost.helpers import raster as ras, helpers as h
@@ -22,7 +15,7 @@
 
 logger = logging.getLogger(__name__)
 
-SNAP_DATEFORMAT = '%d%b%Y'
+SNAP_DATEFORMAT = "%d%b%Y"
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
@@ -31,55 +24,53 @@ def ard_to_ts(list_of_files, burst, product, pol, config_file):
     # -------------------------------------------
     # 1 unpack list of args
     # convert list of files readable for snap
-    list_of_files = f"\'{','.join(str(x) for x in list_of_files)}\'"
+    list_of_files = f"'{','.join(str(x) for x in list_of_files)}'"
 
     # -------------------------------------------
     # 2 read config file
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        processing_dir = Path(config_dict['processing_dir'])
-        ard = config_dict['processing']['single_ARD']
-        ard_mt = config_dict['processing']['time-series_ARD']
+        processing_dir = Path(config_dict["processing_dir"])
+        ard = config_dict["processing"]["single_ARD"]
+        ard_mt = config_dict["processing"]["time-series_ARD"]
 
     # -------------------------------------------
     # 3 get namespace of directories and check if already processed
     # get the burst directory
-    burst_dir = processing_dir.joinpath(burst)
+    burst_dir = processing_dir / burst
 
     # get timeseries directory and create if non existent
-    out_dir = burst_dir.joinpath('Timeseries')
+    out_dir = burst_dir / "Timeseries"
     Path.mkdir(out_dir, parents=True, exist_ok=True)
 
     # in case some processing has been done before, check if already processed
-    check_file = out_dir.joinpath(f'.{product}.{pol}.processed')
+    check_file = out_dir / f".{product}.{pol}.processed"
     if Path.exists(check_file):
         logger.info(
-            f'Timeseries of {burst} for {product} in {pol} '
-            f'polarisation already processed.'
+            f"Timeseries of {burst} for {product} in {pol} "
+            f"polarisation already processed."
         )
 
-        out_files = 'already_processed'
-        out_vrt = 'already_processed'
+        out_files = "already_processed"
+        out_vrt = "already_processed"
 
-        return (
-            burst, list_of_files, out_files, out_vrt, f'{product}.{pol}', None
-        )
+        return (burst, list_of_files, out_files, out_vrt, f"{product}.{pol}", None)
 
     # -------------------------------------------
     # 4 adjust processing parameters according to config
     # get the db scaling right
-    to_db = ard['to_db']
-    if to_db or product != 'bs':
+    to_db = ard["to_db"]
+    if to_db or product != "bs":
         to_db = False
-        logger.debug(f'Not converting to dB for {product}')
+        logger.debug(f"Not converting to dB for {product}")
     else:
-        to_db = ard_mt['to_db']
-        logger.debug(f'Converting to dB for {product}')
+        to_db = ard_mt["to_db"]
+        logger.debug(f"Converting to dB for {product}")
 
-    if ard_mt['apply_ls_mask']:
-        extent = burst_dir.joinpath(f'{burst}.valid.json')
+    if ard_mt["apply_ls_mask"]:
+        extent = burst_dir / f"{burst}.valid.json"
     else:
-        extent = burst_dir.joinpath(f'{burst}.min_bounds.json')
+        extent = burst_dir / f"{burst}.min_bounds.json"
 
     # -------------------------------------------
     # 5 SNAP processing
@@ -89,51 +80,46 @@ def ard_to_ts(list_of_files, burst, product, pol, config_file):
         temp = Path(temp)
 
         # create namespaces
-        temp_stack = temp.joinpath(f'{burst}_{product}_{pol}')
-        out_stack = temp.joinpath(f'{burst}_{product}_{pol}_mt')
-        stack_log = out_dir.joinpath(f'{burst}_{product}_{pol}_stack.err_log')
+        temp_stack = temp / f"{burst}_{product}_{pol}"
+        out_stack = temp / f"{burst}_{product}_{pol}_mt"
+        stack_log = out_dir / f"{burst}_{product}_{pol}_stack.err_log"
 
         # run stacking routine
-        if pol in ['Alpha', 'Anisotropy', 'Entropy']:
+        if pol in ["Alpha", "Anisotropy", "Entropy"]:
             logger.info(
-                f'Creating multi-temporal stack of images of burst/track '
-                f'{burst} for the {pol} band of the polarimetric '
-                f'H-A-Alpha decomposition.'
+                f"Creating multi-temporal stack of images of burst/track "
+                f"{burst} for the {pol} band of the polarimetric "
+                f"H-A-Alpha decomposition."
             )
             try:
                 create_stack(
-                    list_of_files, temp_stack, stack_log, config_dict,
-                    pattern=pol
+                    list_of_files, temp_stack, stack_log, config_dict, pattern=pol
                 )
             except (GPTRuntimeError, NotValidFileError) as error:
                 logger.info(error)
                 return None, None, None, None, None, error
         else:
             logger.info(
-                f'Creating multi-temporal stack of images of burst/track '
-                f'{burst} for {product} product in {pol} polarization.'
+                f"Creating multi-temporal stack of images of burst/track "
+                f"{burst} for {product} product in {pol} polarization."
             )
             try:
                 create_stack(
-                    list_of_files, temp_stack, stack_log, config_dict,
-                    polarisation=pol
+                    list_of_files, temp_stack, stack_log, config_dict, polarisation=pol
                 )
             except (GPTRuntimeError, NotValidFileError) as error:
                 logger.info(error)
                 return None, None, None, None, None, error
 
         # run mt speckle filter
-        if ard_mt['remove_mt_speckle'] is True:
+        if ard_mt["remove_mt_speckle"] is True:
 
-            speckle_log = out_dir.joinpath(
-                f'{burst}_{product}_{pol}_mt_speckle.err_log'
-            )
+            speckle_log = out_dir / f"{burst}_{product}_{pol}_mt_speckle.err_log"
 
-            logger.debug('Applying multi-temporal speckle filter')
+            logger.debug("Applying multi-temporal speckle filter")
             try:
                 mt_speckle_filter(
-                    temp_stack.with_suffix('.dim'), out_stack, speckle_log,
-                    config_dict
+                    temp_stack.with_suffix(".dim"), out_stack, speckle_log, config_dict
                 )
             except (GPTRuntimeError, NotValidFileError) as error:
                 logger.info(error)
@@ -148,30 +134,31 @@ def ard_to_ts(list_of_files, burst, product, pol, config_file):
         # 6 Conversion to GeoTiff
 
         # min max dict for stretching in case of 16 or 8 bit datatype
-        mm_dict = {'bs': {'min': -30, 'max': 5},
-                   'coh': {'min': 0.000001, 'max': 1},
-                   'Alpha': {'min': 0.000001, 'max': 90},
-                   'Anisotropy': {'min': 0.000001, 'max': 1},
-                   'Entropy': {'min': 0.000001, 'max': 1}
-                   }
-        stretch = pol if pol in ['Alpha', 'Anisotropy', 'Entropy'] else product
+        mm_dict = {
+            "bs": {"min": -30, "max": 5},
+            "coh": {"min": 0.000001, "max": 1},
+            "Alpha": {"min": 0.000001, "max": 90},
+            "Anisotropy": {"min": 0.000001, "max": 1},
+            "Entropy": {"min": 0.000001, "max": 1},
+        }
+        stretch = pol if pol in ["Alpha", "Anisotropy", "Entropy"] else product
 
-        if product == 'coh':
+        if product == "coh":
 
             # get slave and master dates from file names and sort them
-            mst_dates = sorted([
-                dt.strptime(
-                    file.name.split('_')[3].split('.')[0], SNAP_DATEFORMAT
-                )
-                for file in list(out_stack.with_suffix('.data').glob('*.img'))
-            ])
+            mst_dates = sorted(
+                [
+                    dt.strptime(file.name.split("_")[3].split(".")[0], SNAP_DATEFORMAT)
+                    for file in list(out_stack.with_suffix(".data").glob("*.img"))
+                ]
+            )
 
-            slv_dates = sorted([
-                dt.strptime(
-                    file.name.split('_')[4].split('.')[0], SNAP_DATEFORMAT
-                )
-                for file in list(out_stack.with_suffix('.data').glob('*.img'))
-            ])
+            slv_dates = sorted(
+                [
+                    dt.strptime(file.name.split("_")[4].split(".")[0], SNAP_DATEFORMAT)
+                    for file in list(out_stack.with_suffix(".data").glob("*.img"))
+                ]
+            )
 
             # write them back to string for following loop
             mst_dates = [dt.strftime(ts, SNAP_DATEFORMAT) for ts in mst_dates]
@@ -182,47 +169,50 @@ def ard_to_ts(list_of_files, burst, product, pol, config_file):
 
                 # re-construct namespace for input file
                 infile = list(
-                    out_stack.with_suffix('.data').glob(
-                        f'*{pol}*{mst}_{slv}*img'
-                    )
+                    out_stack.with_suffix(".data").glob(f"*{pol}*{mst}_{slv}*img")
                 )[0]
 
                 # rename dates to YYYYMMDD format
-                mst = dt.strftime(dt.strptime(mst, SNAP_DATEFORMAT), '%y%m%d')
-                slv = dt.strftime(dt.strptime(slv, SNAP_DATEFORMAT), '%y%m%d')
+                mst = dt.strftime(dt.strptime(mst, SNAP_DATEFORMAT), "%y%m%d")
+                slv = dt.strftime(dt.strptime(slv, SNAP_DATEFORMAT), "%y%m%d")
 
                 # create namespace for output file with renamed dates
-                outfile = out_dir.joinpath(
-                    f'{i+1:02d}.{mst}.{slv}.{product}.{pol}.tif'
-                )
+                outfile = out_dir / f"{i+1:02d}.{mst}.{slv}.{product}.{pol}.tif"
 
                 # fill internal values if any
-                #with rasterio.open(str(infile), 'r') as src:
+                # with rasterio.open(str(infile), 'r') as src:
                 #    meta = src.meta.copy()
                 #    filled = ras.fill_internal_nans(src.read())
 
-                #with rasterio.open(str(infile), 'w', **meta) as dest:
+                # with rasterio.open(str(infile), 'w', **meta) as dest:
                 #    dest.write(filled)
 
-                #print('filled')
+                # print('filled')
                 # produce final outputfile,
                 # including dtype conversion and ls mask
                 ras.mask_by_shape(
-                    infile, outfile, extent, to_db=to_db,
-                    datatype=ard_mt['dtype_output'],
-                    min_value=mm_dict[stretch]['min'],
-                    max_value=mm_dict[stretch]['max'],
-                    ndv=0.0, description=True)
+                    infile,
+                    outfile,
+                    extent,
+                    to_db=to_db,
+                    datatype=ard_mt["dtype_output"],
+                    min_value=mm_dict[stretch]["min"],
+                    max_value=mm_dict[stretch]["max"],
+                    ndv=0.0,
+                    description=True,
+                )
 
                 # add ot a list for subsequent vrt creation
                 out_files.append(str(outfile))
 
         else:
             # get the dates of the files
-            dates = sorted([dt.strptime(
-                file.name.split('_')[-1][:-4], SNAP_DATEFORMAT)
-                for file in list(out_stack.with_suffix('.data').glob('*.img'))
-            ])
+            dates = sorted(
+                [
+                    dt.strptime(file.name.split("_")[-1][:-4], SNAP_DATEFORMAT)
+                    for file in list(out_stack.with_suffix(".data").glob("*.img"))
+                ]
+            )
 
             # write them back to string for following loop
             dates = [dt.strftime(ts, "%d%b%Y") for ts in dates]
@@ -232,35 +222,35 @@ def ard_to_ts(list_of_files, burst, product, pol, config_file):
 
                 # re-construct namespace for input file
                 infile = list(
-                    out_stack.with_suffix('.data').glob(f'*{pol}*{date}*img')
+                    out_stack.with_suffix(".data").glob(f"*{pol}*{date}*img")
                 )[0]
 
                 # restructure date to YYMMDD
-                date = dt.strftime(
-                    dt.strptime(date, SNAP_DATEFORMAT), '%y%m%d'
-                )
+                date = dt.strftime(dt.strptime(date, SNAP_DATEFORMAT), "%y%m%d")
 
                 # create namespace for output file
-                outfile = out_dir.joinpath(
-                    f'{i+1:02d}.{date}.{product}.{pol}.tif'
-                )
+                outfile = out_dir / f"{i+1:02d}.{date}.{product}.{pol}.tif"
 
                 # fill internal nodata
-                #if ard['image_type'] == 'SLC':
-                    #with rasterio.open(str(infile), 'r') as src:
-                    #    meta = src.meta.copy()
-                        #filled = ras.fill_internal_nans(src.read())
-
-                    #with rasterio.open(str(infile), 'w', **meta) as dest:
-                    #    dest.write(filled)
-                    #print('filledbs')
+                # if ard['image_type'] == 'SLC':
+                # with rasterio.open(str(infile), 'r') as src:
+                #    meta = src.meta.copy()
+                # filled = ras.fill_internal_nans(src.read())
+
+                # with rasterio.open(str(infile), 'w', **meta) as dest:
+                #    dest.write(filled)
+                # print('filledbs')
                 # run conversion routine
-                ras.mask_by_shape(infile, outfile, extent,
-                                  to_db=to_db,
-                                  datatype=ard_mt['dtype_output'],
-                                  min_value=mm_dict[stretch]['min'],
-                                  max_value=mm_dict[stretch]['max'],
-                                  ndv=0.0)
+                ras.mask_by_shape(
+                    infile,
+                    outfile,
+                    extent,
+                    to_db=to_db,
+                    datatype=ard_mt["dtype_output"],
+                    min_value=mm_dict[stretch]["min"],
+                    max_value=mm_dict[stretch]["max"],
+                    ndv=0.0,
+                )
 
                 # add ot a list for subsequent vrt creation
                 out_files.append(str(outfile))
@@ -273,29 +263,22 @@ def ard_to_ts(list_of_files, burst, product, pol, config_file):
 
             for file_ in out_files:
                 Path(file_).unlink()
-                if Path(f'{file}.xml').exists():
-                    Path(f'{file}.xml').unlink()
+                if Path(f"{file}.xml").exists():
+                    Path(f"{file}.xml").unlink()
 
-            return (
-                burst, list_of_files, None, None,
-                f'{product}.{pol}', return_code
-            )
+            return (burst, list_of_files, None, None, f"{product}.{pol}", return_code)
 
     # write file, so we know this ts has been successfully processed
-    with open(str(check_file), 'w') as file:
-        file.write('passed all tests \n')
+    with open(str(check_file), "w") as file:
+        file.write("passed all tests \n")
 
     # -----------------------------------------------
     # 8 Create vrts
     vrt_options = gdal.BuildVRTOptions(srcNodata=0, separate=True)
-    out_vrt = str(out_dir.joinpath(f'Timeseries.{product}.{pol}.vrt'))
-    gdal.BuildVRT(
-        out_vrt,
-        out_files,
-        options=vrt_options
-    )
-
-    return burst, list_of_files, out_files, out_vrt, f'{product}.{pol}', None
+    out_vrt = str(out_dir / f"Timeseries.{product}.{pol}.vrt")
+    gdal.BuildVRT(out_vrt, out_files, options=vrt_options)
+
+    return burst, list_of_files, out_files, out_vrt, f"{product}.{pol}", None
 
 
 def gd_ard_to_ts(list_of_args, config_file):
diff --git a/ost/generic/common_wrappers.py b/ost/generic/common_wrappers.py
index 76e2b9dd..54e758fe 100644
--- a/ost/generic/common_wrappers.py
+++ b/ost/generic/common_wrappers.py
@@ -3,7 +3,6 @@
 
 import logging
 from retrying import retry
-from rasterio.crs import CRS
 
 from ost.helpers import helpers as h
 from ost.helpers.settings import GPT_FILE, OST_ROOT
@@ -28,26 +27,26 @@ def speckle_filter(infile, outfile, logfile, config_dict):
     """
 
     # get relevant config parameters
-    cpus = config_dict['snap_cpu_parallelism']
-    speckle_dict = config_dict['processing']['single_ARD']['speckle_filter']
+    cpus = config_dict["snap_cpu_parallelism"]
+    speckle_dict = config_dict["processing"]["single_ARD"]["speckle_filter"]
 
-    logger.debug('Applying speckle filtering.')
+    logger.debug("Applying speckle filtering.")
 
     # construct command string
     command = (
         f"{GPT_FILE} Speckle-Filter -x -q {2*cpus} "
-        f"-PestimateENL=\'{speckle_dict['estimate_ENL']}\' "
-        f"-PanSize=\'{speckle_dict['pan_size']}\' "
-        f"-PdampingFactor=\'{speckle_dict['damping']}\' "
-        f"-Penl=\'{speckle_dict['ENL']}\' "
-        f"-Pfilter=\'{speckle_dict['filter']}\' "
-        f"-PfilterSizeX=\'{speckle_dict['filter_x_size']}\' "
-        f"-PfilterSizeY=\'{speckle_dict['filter_y_size']}\' "
-        f"-PnumLooksStr=\'{speckle_dict['num_of_looks']}\' "
-        f"-PsigmaStr=\'{speckle_dict['sigma']}\' "
+        f"-PestimateENL='{speckle_dict['estimate_ENL']}' "
+        f"-PanSize='{speckle_dict['pan_size']}' "
+        f"-PdampingFactor='{speckle_dict['damping']}' "
+        f"-Penl='{speckle_dict['ENL']}' "
+        f"-Pfilter='{speckle_dict['filter']}' "
+        f"-PfilterSizeX='{speckle_dict['filter_x_size']}' "
+        f"-PfilterSizeY='{speckle_dict['filter_y_size']}' "
+        f"-PnumLooksStr='{speckle_dict['num_of_looks']}' "
+        f"-PsigmaStr='{speckle_dict['sigma']}' "
         f"-PtargetWindowSizeStr=\"{speckle_dict['target_window_size']}\" "
         f"-PwindowSize=\"{speckle_dict['window_size']}\" "
-        f"-t \'{str(outfile)}\' \'{str(infile)}\' "
+        f"-t '{str(outfile)}' '{str(infile)}' "
     )
 
     # run command and get return code
@@ -55,21 +54,19 @@ def speckle_filter(infile, outfile, logfile, config_dict):
 
     # handle errors and logs
     if return_code == 0:
-        logger.debug('Successfully applied speckle filtering.')
+        logger.debug("Successfully applied speckle filtering.")
     else:
         raise GPTRuntimeError(
-            f'Speckle filtering exited with error {return_code}. '
-            f'See {logfile} for Snap\'s error message.'
+            f"Speckle filtering exited with error {return_code}. "
+            f"See {logfile} for Snap's error message."
         )
 
     # do check routine
     return_code = h.check_out_dimap(outfile)
     if return_code == 0:
-        return str(outfile.with_suffix('.dim'))
+        return str(outfile.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
@@ -87,14 +84,14 @@ def linear_to_db(infile, outfile, logfile, config_dict):
     """
 
     # get relevant config parameters
-    cpus = config_dict['snap_cpu_parallelism']
+    cpus = config_dict["snap_cpu_parallelism"]
 
-    logger.debug('Converting calibrated power image to dB scale.')
+    logger.debug("Converting calibrated power image to dB scale.")
 
     # construct command string
     command = (
-        f'{GPT_FILE} LinearToFromdB -x -q {2*cpus} '
-        f'-t \'{str(outfile)}\' {str(infile)}'
+        f"{GPT_FILE} LinearToFromdB -x -q {2*cpus} "
+        f"-t '{str(outfile)}' {str(infile)}"
     )
 
     # run command and get return code
@@ -102,21 +99,19 @@ def linear_to_db(infile, outfile, logfile, config_dict):
 
     # handle errors and logs
     if return_code == 0:
-        logger.debug('Succesfully converted product to dB-scale.')
+        logger.debug("Succesfully converted product to dB-scale.")
     else:
         raise GPTRuntimeError(
-            f'dB Scaling exited with error {return_code}. '
-            f'See {logfile} for Snap\'s error message.'
+            f"dB Scaling exited with error {return_code}. "
+            f"See {logfile} for Snap's error message."
         )
 
     # do check routine
     return_code = h.check_out_dimap(outfile)
     if return_code == 0:
-        return str(outfile.with_suffix('.dim'))
+        return str(outfile.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
@@ -131,18 +126,18 @@ def terrain_flattening(infile, outfile, logfile, config_dict):
     """
 
     # get relevant config parameters
-    cpus = config_dict['snap_cpu_parallelism']
-    dem_dict = config_dict['processing']['single_ARD']['dem']
+    cpus = config_dict["snap_cpu_parallelism"]
+    dem_dict = config_dict["processing"]["single_ARD"]["dem"]
 
-    logger.debug('Applying terrain flattening to calibrated product.')
+    logger.debug("Applying terrain flattening to calibrated product.")
 
     command = (
         f"{GPT_FILE} Terrain-Flattening -x -q {2*cpus} "
-        f"-PdemName=\'{dem_dict['dem_name']}\' "
-        f"-PdemResamplingMethod=\'{dem_dict['dem_resampling']}\' "
-        f"-PexternalDEMFile=\'{dem_dict['dem_file']}\' "
+        f"-PdemName='{dem_dict['dem_name']}' "
+        f"-PdemResamplingMethod='{dem_dict['dem_resampling']}' "
+        f"-PexternalDEMFile='{dem_dict['dem_file']}' "
         f"-PexternalDEMNoDataValue={dem_dict['dem_nodata']} "
-        f"-t \'{str(outfile)}\' \'{str(infile)}\'"
+        f"-t '{str(outfile)}' '{str(infile)}'"
     )
 
     # run command and get return code
@@ -150,21 +145,19 @@ def terrain_flattening(infile, outfile, logfile, config_dict):
 
     # handle errors and logs
     if return_code == 0:
-        logger.debug('Succesfully terrain flattened product')
+        logger.debug("Succesfully terrain flattened product")
     else:
         raise GPTRuntimeError(
-            f'Terrain Flattening exited with error {return_code}. '
-            f'See {logfile} for Snap\'s error message.'
+            f"Terrain Flattening exited with error {return_code}. "
+            f"See {logfile} for Snap's error message."
         )
 
     # do check routine
     return_code = h.check_out_dimap(outfile)
     if return_code == 0:
-        return str(outfile.with_suffix('.dim'))
+        return str(outfile.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
@@ -183,77 +176,73 @@ def terrain_correction(infile, outfile, logfile, config_dict):
     """
 
     # get relevant config parameters
-    ard = config_dict['processing']['single_ARD']
-    dem_dict = ard['dem']
-    cpus = config_dict['snap_cpu_parallelism']
+    ard = config_dict["processing"]["single_ARD"]
+    dem_dict = ard["dem"]
+    cpus = config_dict["snap_cpu_parallelism"]
 
     # auto projections of snap
-    if 42001 <= dem_dict['out_projection'] <= 97002:
+    if 42001 <= dem_dict["out_projection"] <= 97002:
         projection = f"AUTO:{dem_dict['out_projection']}"
     # epsg codes
-    elif int(dem_dict['out_projection']) == 4326:
-        projection = 'WGS84(DD)'
+    elif int(dem_dict["out_projection"]) == 4326:
+        projection = "WGS84(DD)"
     else:
         projection = f"EPSG:{dem_dict['out_projection']}"
 
     #
-    logger.debug('Geocoding product.')
+    logger.debug("Geocoding product.")
 
-    if ard['geocoding'] == 'terrain':
+    if ard["geocoding"] == "terrain":
         command = (
             f"{GPT_FILE} Terrain-Correction -x -q {2*cpus} "
-            f"-PdemName=\'{dem_dict['dem_name']}\' "
-            f"-PdemResamplingMethod=\'{dem_dict['dem_resampling']}\' "
-            f"-PexternalDEMFile=\'{dem_dict['dem_file']}\' "
+            f"-PdemName='{dem_dict['dem_name']}' "
+            f"-PdemResamplingMethod='{dem_dict['dem_resampling']}' "
+            f"-PexternalDEMFile='{dem_dict['dem_file']}' "
             f"-PexternalDEMNoDataValue={dem_dict['dem_nodata']} "
             f"-PexternalDEMApplyEGM="
-            f"\'{str(dem_dict['egm_correction']).lower()}\' "
-            f"-PimgResamplingMethod=\'{dem_dict['image_resampling']}\' "
+            f"'{str(dem_dict['egm_correction']).lower()}' "
+            f"-PimgResamplingMethod='{dem_dict['image_resampling']}' "
             f"-PpixelSpacingInMeter={ard['resolution']} "
             f"-PalignToStandardGrid=true "
-            f"-PmapProjection=\'{projection}\' "
-            f"-t \'{str(outfile)}\' \'{str(infile)}\' "
+            f"-PmapProjection='{projection}' "
+            f"-t '{str(outfile)}' '{str(infile)}' "
         )
-    elif ard['geocoding'] == 'ellipsoid':
+    elif ard["geocoding"] == "ellipsoid":
         command = (
             f"{GPT_FILE} Ellipsoid-Correction-RD -x -q {2*cpus} "
-            f"-PdemName=\'{dem_dict['dem_name']}\' "
-            f"-PdemResamplingMethod=\'{dem_dict['dem_resampling']}\' "
-            f"-PexternalDEMFile=\'{dem_dict['dem_file']}\' "
+            f"-PdemName='{dem_dict['dem_name']}' "
+            f"-PdemResamplingMethod='{dem_dict['dem_resampling']}' "
+            f"-PexternalDEMFile='{dem_dict['dem_file']}' "
             f"-PexternalDEMNoDataValue={dem_dict['dem_nodata']} "
             f"-PexternalDEMApplyEGM="
-            f"\'{str(dem_dict['egm_correction']).lower()}\' "
-            f"-PimgResamplingMethod=\'{dem_dict['image_resampling']}\' "
+            f"'{str(dem_dict['egm_correction']).lower()}' "
+            f"-PimgResamplingMethod='{dem_dict['image_resampling']}' "
             f"-PpixelSpacingInMeter={ard['resolution']} "
             f"-PalignToStandardGrid=true "
-            f"-PmapProjection=\'{projection}\' "
-            f"-t \'{str(outfile)}\' \'{str(infile)}\' "
+            f"-PmapProjection='{projection}' "
+            f"-t '{str(outfile)}' '{str(infile)}' "
         )
     else:
-        raise ValueError(
-            'Geocoding method should be either \'terrain\' or \'ellipsoid\'.'
-        )
+        raise ValueError("Geocoding method should be either 'terrain' or 'ellipsoid'.")
 
     # run command and get return code
     return_code = h.run_command(command, logfile)
 
     # handle errors and logs
     if return_code == 0:
-        logger.debug('Succesfully geocoded product')
+        logger.debug("Succesfully geocoded product")
     else:
         raise GPTRuntimeError(
-            f'Geocoding exited with error {return_code}. '
-            f'See {logfile} for Snap\'s error message.'
+            f"Geocoding exited with error {return_code}. "
+            f"See {logfile} for Snap's error message."
         )
 
     # do check routine
     return_code = h.check_out_dimap(outfile)
     if return_code == 0:
-        return str(outfile.with_suffix('.dim'))
+        return str(outfile.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
@@ -268,27 +257,27 @@ def ls_mask(infile, outfile, logfile, config_dict):
     """
 
     # get relevant config parameters
-    ard = config_dict['processing']['single_ARD']
-    dem_dict = ard['dem']
-    cpus = config_dict['snap_cpu_parallelism']
+    ard = config_dict["processing"]["single_ARD"]
+    dem_dict = ard["dem"]
+    cpus = config_dict["snap_cpu_parallelism"]
 
     # auto projections of snap
-    if 42001 <= dem_dict['out_projection'] <= 97002:
+    if 42001 <= dem_dict["out_projection"] <= 97002:
         projection = f"AUTO:{dem_dict['out_projection']}"
     # epsg codes
-    elif int(dem_dict['out_projection']) == 4326:
-        projection = 'WGS84(DD)'
+    elif int(dem_dict["out_projection"]) == 4326:
+        projection = "WGS84(DD)"
     else:
         projection = f"EPSG:{dem_dict['out_projection']}"
 
-    logger.debug('Creating the Layover/Shadow mask')
+    logger.debug("Creating the Layover/Shadow mask")
 
     # get path to workflow xml
-    graph = OST_ROOT.joinpath('graphs/S1_GRD2ARD/3_LSmap.xml')
+    graph = OST_ROOT / "graphs/S1_GRD2ARD/3_LSmap.xml"
 
     command = (
-        f'{GPT_FILE} {graph} -x -q {2 * cpus} '
-        f'-Pinput=\'{str(infile)}\' '
+        f"{GPT_FILE} {graph} -x -q {2 * cpus} "
+        f"-Pinput='{str(infile)}' "
         f'-Presol={ard["resolution"]} '
         f'-Pdem=\'{dem_dict["dem_name"]}\' '
         f'-Pdem_file=\'{dem_dict["dem_file"]}\' '
@@ -296,8 +285,8 @@ def ls_mask(infile, outfile, logfile, config_dict):
         f'-Pdem_resampling=\'{dem_dict["dem_resampling"]}\' '
         f'-Pimage_resampling=\'{dem_dict["image_resampling"]}\' '
         f'-Pegm_correction=\'{str(dem_dict["egm_correction"]).lower()}\' '
-        f'-Pprojection=\'{projection}\' '
-        f'-Poutput=\'{str(outfile)}\''
+        f"-Pprojection='{projection}' "
+        f"-Poutput='{str(outfile)}'"
     )
 
     # run command and get return code
@@ -305,31 +294,29 @@ def ls_mask(infile, outfile, logfile, config_dict):
 
     # handle errors and logs
     if return_code == 0:
-        logger.debug('Successfully created a Layover/Shadow mask')
+        logger.debug("Successfully created a Layover/Shadow mask")
     else:
         raise GPTRuntimeError(
-            f'Layover/Shadow mask creation exited with error {return_code}. '
-            f'See {logfile} for Snap\'s error message.'
+            f"Layover/Shadow mask creation exited with error {return_code}. "
+            f"See {logfile} for Snap's error message."
         )
 
     # do check routine
     return_code = h.check_out_dimap(outfile, test_stats=False)
     if return_code == 0:
-        return str(outfile.with_suffix('.dim'))
+        return str(outfile.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
 def create_stack(
-        file_list,
-        out_stack,
-        logfile,
-        config_dict,
-        polarisation=None,
-        pattern=None,
+    file_list,
+    out_stack,
+    logfile,
+    config_dict,
+    polarisation=None,
+    pattern=None,
 ):
     """
 
@@ -343,48 +330,46 @@ def create_stack(
     """
 
     # get relevant config parameters
-    cpus = config_dict['snap_cpu_parallelism']
+    cpus = config_dict["snap_cpu_parallelism"]
 
-    logger.debug('Creating multi-temporal stack.')
+    logger.debug("Creating multi-temporal stack.")
 
     if pattern:
-        graph = OST_ROOT.joinpath('graphs/S1_TS/1_BS_Stacking_HAalpha.xml')
+        graph = OST_ROOT / "graphs/S1_TS/1_BS_Stacking_HAalpha.xml"
 
         command = (
-            f'{GPT_FILE} {graph} -x -q {2*cpus} '
-            f'-Pfilelist={file_list} '
-            f'-PbandPattern=\'{pattern}.*\' '
-            f'-Poutput={out_stack}'
+            f"{GPT_FILE} {graph} -x -q {2*cpus} "
+            f"-Pfilelist={file_list} "
+            f"-PbandPattern='{pattern}.*' "
+            f"-Poutput={out_stack}"
         )
 
     else:
-        graph = OST_ROOT.joinpath('graphs/S1_TS/1_BS_Stacking.xml')
+        graph = OST_ROOT / "graphs/S1_TS/1_BS_Stacking.xml"
 
         command = (
-            f'{GPT_FILE} {graph} -x -q {2*cpus} '
-            f'-Pfilelist={file_list} '
-            f'-Ppol={polarisation} '
-            f'-Poutput={out_stack}'
+            f"{GPT_FILE} {graph} -x -q {2*cpus} "
+            f"-Pfilelist={file_list} "
+            f"-Ppol={polarisation} "
+            f"-Poutput={out_stack}"
         )
 
     return_code = h.run_command(command, logfile)
 
     if return_code == 0:
-        logger.debug('Successfully created multi-temporal stack')
+        logger.debug("Successfully created multi-temporal stack")
     else:
         raise GPTRuntimeError(
-            f'Multi-temporal stack creation exited with error {return_code}. '
-            f'See {logfile} for Snap\'s error message.'
+            f"Multi-temporal stack creation exited with error {return_code}. "
+            f"See {logfile} for Snap's error message."
         )
 
     # do check routine
     return_msg = h.check_out_dimap(out_stack)
     if return_msg == 0:
-        logger.debug('Product passed validity check.')
+        logger.debug("Product passed validity check.")
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_msg}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_msg}")
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
@@ -399,46 +384,42 @@ def mt_speckle_filter(in_stack, out_stack, logfile, config_dict):
     """
 
     # get relevant config parameters
-    cpus = config_dict['snap_cpu_parallelism']
-    speckle_dict = (
-        config_dict['processing']['time-series_ARD']['mt_speckle_filter']
-    )
+    cpus = config_dict["snap_cpu_parallelism"]
+    speckle_dict = config_dict["processing"]["time-series_ARD"]["mt_speckle_filter"]
 
     # debug message
-    logger.debug('Applying multi-temporal speckle filtering.')
+    logger.debug("Applying multi-temporal speckle filtering.")
 
     # construct command string
     command = (
         f"{GPT_FILE} Multi-Temporal-Speckle-Filter -x -q {2*cpus} "
-        f"-PestimateENL=\'{speckle_dict['estimate_ENL']}\' "
-        f"-PanSize=\'{speckle_dict['pan_size']}\' "
-        f"-PdampingFactor=\'{speckle_dict['damping']}\' "
-        f"-Penl=\'{speckle_dict['ENL']}\' "
-        f"-Pfilter=\'{speckle_dict['filter']}\' "
-        f"-PfilterSizeX=\'{speckle_dict['filter_x_size']}\' "
-        f"-PfilterSizeY=\'{speckle_dict['filter_y_size']}\' "
-        f"-PnumLooksStr=\'{speckle_dict['num_of_looks']}\' "
-        f"-PsigmaStr=\'{speckle_dict['sigma']}\' "
+        f"-PestimateENL='{speckle_dict['estimate_ENL']}' "
+        f"-PanSize='{speckle_dict['pan_size']}' "
+        f"-PdampingFactor='{speckle_dict['damping']}' "
+        f"-Penl='{speckle_dict['ENL']}' "
+        f"-Pfilter='{speckle_dict['filter']}' "
+        f"-PfilterSizeX='{speckle_dict['filter_x_size']}' "
+        f"-PfilterSizeY='{speckle_dict['filter_y_size']}' "
+        f"-PnumLooksStr='{speckle_dict['num_of_looks']}' "
+        f"-PsigmaStr='{speckle_dict['sigma']}' "
         f"-PtargetWindowSizeStr=\"{speckle_dict['target_window_size']}\" "
         f"-PwindowSize=\"{speckle_dict['window_size']}\" "
-        f"-t \'{out_stack}\' \'{in_stack}\' "
+        f"-t '{out_stack}' '{in_stack}' "
     )
 
     return_code = h.run_command(command, logfile)
 
     if return_code == 0:
-        logger.debug('Successfully applied multi-temporal speckle filtering')
+        logger.debug("Successfully applied multi-temporal speckle filtering")
     else:
         raise GPTRuntimeError(
-            f'Multi-temporal Spackle Filter exited with error {return_code}. '
-            f'See {logfile} for Snap\'s error message.'
+            f"Multi-temporal Spackle Filter exited with error {return_code}. "
+            f"See {logfile} for Snap's error message."
         )
 
     # do check routine
     return_code = h.check_out_dimap(out_stack)
     if return_code == 0:
-        return str(out_stack.with_suffix('.dim'))
+        return str(out_stack.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
diff --git a/ost/generic/mosaic.py b/ost/generic/mosaic.py
index 1a563bb9..7c2647ff 100644
--- a/ost/generic/mosaic.py
+++ b/ost/generic/mosaic.py
@@ -9,14 +9,7 @@
 import rasterio
 import rasterio.mask
 from retrying import retry
-
-try:
-    import gdal
-except ModuleNotFoundError as e:
-    try:
-        from osgeo import gdal
-    except ModuleNotFoundError:
-        raise e
+from osgeo import gdal
 
 from ost.helpers import vector as vec
 from ost.helpers import helpers as h
@@ -28,49 +21,49 @@ def create_timeseries_mosaic_vrt(list_of_args):
     ts_dir, product, outfiles = list_of_args
 
     gdal.BuildVRT(
-        str(ts_dir.joinpath(f'{product}.Timeseries.vrt')),
+        str(ts_dir / f"{product}.Timeseries.vrt"),
         [str(outfile) for outfile in outfiles],
-        options=gdal.BuildVRTOptions(srcNodata=0, separate=True)
+        options=gdal.BuildVRTOptions(srcNodata=0, separate=True),
     )
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
 def mosaic(filelist, outfile, config_file, cut_to_aoi=None, harm=None):
 
-    if outfile.parent.joinpath(f'.{outfile.name[:-4]}.processed').exists():
-        logger.info(f'{outfile} already exists.')
+    if (outfile.parent / f".{outfile.name[:-4]}.processed").exists():
+        logger.info(f"{outfile} already exists.")
         return
 
-    logger.info(f'Mosaicking file {outfile}.')
-    with open(config_file, 'r') as ard_file:
+    logger.info(f"Mosaicking file {outfile}.")
+    with open(config_file, "r") as ard_file:
         config_dict = json.load(ard_file)
-        temp_dir = config_dict['temp_dir']
-        aoi = config_dict['aoi']
-        epsg = config_dict['processing']['single_ARD']['dem']['out_projection']
+        temp_dir = config_dict["temp_dir"]
+        aoi = config_dict["aoi"]
+        epsg = config_dict["processing"]["single_ARD"]["dem"]["out_projection"]
 
         if not harm:
-            harm = config_dict['processing']['mosaic']['harmonization']
+            harm = config_dict["processing"]["mosaic"]["harmonization"]
 
         if not cut_to_aoi:
-            cut_to_aoi = config_dict['processing']['mosaic']['cut_to_aoi']
+            cut_to_aoi = config_dict["processing"]["mosaic"]["cut_to_aoi"]
 
-    logfile = outfile.parent.joinpath(f'{str(outfile)[:-4]}.errLog')
+    logfile = outfile.parent / f"{str(outfile)[:-4]}.errLog"
 
-    with TemporaryDirectory(prefix=f'{temp_dir}/') as temp:
+    with TemporaryDirectory(prefix=f"{temp_dir}/") as temp:
 
         temp = Path(temp)
 
         # get datatype from first image in our mosaic filelist
-        with rasterio.open(filelist.split(' ')[0]) as src:
-            dtype = src.meta['dtype']
-            dtype = 'float' if dtype == 'float32' else dtype
+        with rasterio.open(filelist.split(" ")[0]) as src:
+            dtype = src.meta["dtype"]
+            dtype = "float" if dtype == "float32" else dtype
 
         if cut_to_aoi:
-            tempfile = temp.joinpath(outfile.name)
+            tempfile = (temp / outfile).name
         else:
             tempfile = outfile
 
-        harm = 'band' if harm else 'none'
+        harm = "band" if harm else "none"
 
         cmd = (
             f"otbcli_Mosaic -ram 8192  -progress 1 "
@@ -99,23 +92,24 @@ def mosaic(filelist, outfile, config_file, cut_to_aoi=None, harm=None):
 
             # import raster and mask
             with rasterio.open(tempfile) as src:
-                out_image, out_transform = rasterio.mask.mask(src, features,
-                                                              crop=True)
+                out_image, out_transform = rasterio.mask.mask(src, features, crop=True)
                 out_meta = src.meta.copy()
                 ndv = src.nodata
                 out_image = np.ma.masked_where(out_image == ndv, out_image)
 
-                out_meta.update({
-                    'driver': 'GTiff',
-                    'height': out_image.shape[1],
-                    'width': out_image.shape[2],
-                    'transform': out_transform,
-                    'tiled': True,
-                    'blockxsize': 128,
-                    'blockysize': 128
-                })
-
-                with rasterio.open(outfile, 'w', **out_meta) as dest:
+                out_meta.update(
+                    {
+                        "driver": "GTiff",
+                        "height": out_image.shape[1],
+                        "width": out_image.shape[2],
+                        "transform": out_transform,
+                        "tiled": True,
+                        "blockxsize": 128,
+                        "blockysize": 128,
+                    }
+                )
+
+                with rasterio.open(outfile, "w", **out_meta) as dest:
                     dest.write(out_image.data)
 
             # remove intermediate file
@@ -127,11 +121,9 @@ def mosaic(filelist, outfile, config_file, cut_to_aoi=None, harm=None):
             if outfile.exists():
                 outfile.unlink()
         else:
-            check_file = outfile.parent.joinpath(
-                f'.{outfile.name[:-4]}.processed'
-            )
-            with open(str(check_file), 'w') as file:
-                file.write('passed all tests \n')
+            check_file = outfile.parent / f".{outfile.name[:-4]}.processed"
+            with open(str(check_file), "w") as file:
+                file.write("passed all tests \n")
 
 
 def gd_mosaic(list_of_args):
@@ -145,37 +137,39 @@ def _burst_list(track, date, product, subswath, config_dict):
     from shapely.wkt import loads
     import geopandas as gpd
 
-    aoi = loads(config_dict['aoi'])
-    processing_dir = Path(config_dict['processing_dir'])
+    aoi = loads(config_dict["aoi"])
+    processing_dir = Path(config_dict["processing_dir"])
 
     # adjust search pattern in case of coherence
-    search_last = f'*.{product}.tif' if 'coh' in product else f'{product}.tif'
+    search_last = f"*.{product}.tif" if "coh" in product else f"{product}.tif"
 
     # search for all bursts within subswath(s) in time-series
-    list_of_files = list(processing_dir.glob(
-            f'[A,D]{track}_{subswath}*/Timeseries/'
-            f'*.{date}.{search_last}'
-        ))
+    list_of_files = list(
+        processing_dir.glob(
+            f"[A,D]{track}_{subswath}*/Timeseries/" f"*.{date}.{search_last}"
+        )
+    )
 
     # search for timescans (in case timeseries not found)
     if not list_of_files:
 
-        list_of_files = list(processing_dir.glob(
-            f'[A,D]{track}_{subswath}*/Timescan/'
-            f'*.{product}.{date}.tif'
-        ))
+        list_of_files = list(
+            processing_dir.glob(
+                f"[A,D]{track}_{subswath}*/Timescan/" f"*.{product}.{date}.tif"
+            )
+        )
 
     if not list_of_files:
         return None
 
     # get a list of all extent files to check for real AOI overlap
-    if config_dict['processing']['time-series_ARD']['apply_ls_mask']:
+    if config_dict["processing"]["time-series_ARD"]["apply_ls_mask"]:
         list_of_extents = processing_dir.glob(
-            f'*{track}_{subswath}*/*{track}*.valid.json'
+            f"*{track}_{subswath}*/*{track}*.valid.json"
         )
     else:
         list_of_extents = processing_dir.glob(
-            f'*{track}_{subswath}*/*{track}*.min_bounds.json'
+            f"*{track}_{subswath}*/*{track}*.min_bounds.json"
         )
 
     list_of_actual_extents = []
@@ -184,17 +178,19 @@ def _burst_list(track, date, product, subswath, config_dict):
         burst = gpd.read_file(burst_extent)
         if any(burst.intersects(aoi)):
 
-            burst_name = Path(str(burst_extent).split('.')[-3]).name
+            burst_name = Path(str(burst_extent).split(".")[-3]).name
             list_of_actual_extents.append(burst_name)
 
     # filter the bursts for real AOI overlap
     list_of_files = [
-        file for file in list_of_files for pattern in list_of_actual_extents
+        file
+        for file in list_of_files
+        for pattern in list_of_actual_extents
         if pattern in str(file)
     ]
 
     # and join them into a otb readable list
-    list_of_files = ' '.join([str(file) for file in list_of_files])
+    list_of_files = " ".join([str(file) for file in list_of_files])
     return list_of_files
 
 
@@ -202,34 +198,37 @@ def mosaic_slc_acquisition(track, date, product, outfile, config_file):
 
     # -------------------------------------
     # 1 load project config
-    with open(config_file, 'r') as ard_file:
+    with open(config_file, "r") as ard_file:
         config_dict = json.load(ard_file)
-        temp_dir = Path(config_dict['temp_dir'])
+        temp_dir = Path(config_dict["temp_dir"])
 
     # create a list of bursts that actually overlap theAOI
-    list_of_iw12 = _burst_list(track, date, product, 'IW[1,2]', config_dict)
-    list_of_iw3 = _burst_list(track, date, product, 'IW3', config_dict)
+    list_of_iw12 = _burst_list(track, date, product, "IW[1,2]", config_dict)
+    list_of_iw3 = _burst_list(track, date, product, "IW3", config_dict)
 
     if list_of_iw12:
         logger.info(
-            f'Pre-mosaicking {product} acquisition\'s IW1 and IW2 subswaths '
-            f'from {track} taken at {date}.'
+            f"Pre-mosaicking {product} acquisition's IW1 and IW2 subswaths "
+            f"from {track} taken at {date}."
         )
-        temp_iw12 = temp_dir.joinpath(f'{date}_{track}_{product}_IW1_2.tif')
+        temp_iw12 = temp_dir / f"{date}_{track}_{product}_IW1_2.tif"
         mosaic(list_of_iw12, temp_iw12, config_file, harm=False)
 
     if list_of_iw3:
         logger.info(
-            f'Pre-mosaicking {product} acquisition\'s IW3 subswath '
-            f'from {track} taken at {date}.'
+            f"Pre-mosaicking {product} acquisition's IW3 subswath "
+            f"from {track} taken at {date}."
         )
-        temp_iw3 = temp_dir.joinpath(f'{date}_{track}_{product}_IW3.tif')
+        temp_iw3 = temp_dir / f"{date}_{track}_{product}_IW3.tif"
         mosaic(list_of_iw3, temp_iw3, config_file, harm=False)
 
     if list_of_iw12 and list_of_iw3:
         mosaic(
-            ' '.join([str(temp_iw12), str(temp_iw3)]), outfile, config_file,
-            False, harm=True
+            " ".join([str(temp_iw12), str(temp_iw3)]),
+            outfile,
+            config_file,
+            False,
+            harm=True,
         )
 
         temp_iw12.unlink()
@@ -245,4 +244,4 @@ def mosaic_slc_acquisition(track, date, product, outfile, config_file):
 def gd_mosaic_slc_acquisition(list_of_args):
 
     track, date, product, outfile, config_file = list_of_args
-    mosaic_slc_acquisition(track, date, product, outfile, config_file)
\ No newline at end of file
+    mosaic_slc_acquisition(track, date, product, outfile, config_file)
diff --git a/ost/generic/timescan.py b/ost/generic/timescan.py
index 6e4c64c8..bf0bdfd2 100644
--- a/ost/generic/timescan.py
+++ b/ost/generic/timescan.py
@@ -21,13 +21,11 @@
 
 def remove_outliers(arrayin, stddev=2, z_threshold=None):
 
-    warnings.filterwarnings('ignore', 'invalid value', RuntimeWarning)
+    warnings.filterwarnings("ignore", "invalid value", RuntimeWarning)
 
     if z_threshold:
         z_score = np.abs(stats.zscore(arrayin))
-        array_out = np.ma.MaskedArray(
-            arrayin,
-            mask=z_score > z_threshold)
+        array_out = np.ma.MaskedArray(arrayin, mask=z_score > z_threshold)
     else:
 
         # calculate percentiles
@@ -36,11 +34,7 @@ def remove_outliers(arrayin, stddev=2, z_threshold=None):
 
         # we mask out the percentile outliers for std dev calculation
         masked_array = np.ma.MaskedArray(
-            arrayin,
-            mask=np.logical_or(
-                arrayin > perc95,
-                arrayin < perc5
-            )
+            arrayin, mask=np.logical_or(arrayin > perc95, arrayin < perc5)
         )
 
         # we calculate new std and mean
@@ -53,23 +47,24 @@ def remove_outliers(arrayin, stddev=2, z_threshold=None):
             mask=np.logical_or(
                 arrayin > masked_mean + masked_std * stddev,
                 arrayin < masked_mean - masked_std * stddev,
-            )
+            ),
         )
 
     return array_out
 
 
 def date_as_float(date):
-    size_of_day = 1. / 366.
-    size_of_second = size_of_day / (24. * 60. * 60.)
+    size_of_day = 1.0 / 366.0
+    size_of_second = size_of_day / (24.0 * 60.0 * 60.0)
     days_from_jan1 = date - datetime(date.year, 1, 1)
 
     if not isleap(date.year) and days_from_jan1.days >= 31 + 28:
         days_from_jan1 += timedelta(1)
 
     return (
-            date.year + days_from_jan1.days * size_of_day +
-            days_from_jan1.seconds * size_of_second
+        date.year
+        + days_from_jan1.days * size_of_day
+        + days_from_jan1.seconds * size_of_second
     )
 
 
@@ -86,7 +81,7 @@ def deseasonalize(stack):
 
 
 def _zvalue_from_index(arr, ind):
-    """ work around the limitation of np.choose() by employing np.take()
+    """work around the limitation of np.choose() by employing np.take()
 
     arr has to be a 3D array
     ind has to be a 2D array containing values for z-indicies to take from arr
@@ -142,8 +137,7 @@ def nan_percentile(arr, q):
 
         quant_arr = floor_val + ceil_val
         quant_arr[fc_equal_k_mask] = _zvalue_from_index(
-            arr=arr,
-            ind=k_arr.astype(np.int32)
+            arr=arr, ind=k_arr.astype(np.int32)
         )[fc_equal_k_mask]
 
         result.append(quant_arr)
@@ -153,8 +147,7 @@ def nan_percentile(arr, q):
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
 def mt_metrics(
-        stack, out_prefix, metrics, rescale_to_datatype, to_power,
-        outlier_removal, datelist
+    stack, out_prefix, metrics, rescale_to_datatype, to_power, outlier_removal, datelist
 ):
     """
 
@@ -169,33 +162,27 @@ def mt_metrics(
     """
 
     logger.info(
-        f'Creating timescan layers ({metrics}) of track/burst '
-        f'{out_prefix.parent.parent.name} for {out_prefix.name}'
-
+        f"Creating timescan layers ({metrics}) of track/burst "
+        f"{out_prefix.parent.parent.name} for {out_prefix.name}"
     )
 
-    warnings.filterwarnings('ignore', r'All-NaN (slice|axis) encountered')
-    warnings.filterwarnings('ignore', r'Mean of empty slice')
-    warnings.filterwarnings('ignore', r'Degrees of freedom', RuntimeWarning)
+    warnings.filterwarnings("ignore", r"All-NaN (slice|axis) encountered")
+    warnings.filterwarnings("ignore", r"Mean of empty slice")
+    warnings.filterwarnings("ignore", r"Degrees of freedom", RuntimeWarning)
 
-    harmonics = False
-    if 'harmonics' in metrics:
-        logger.info('Calculating harmonics')
+    if "harmonics" in metrics:
+        logger.info("Calculating harmonics")
         if not datelist:
             raise RuntimeWarning(
-                'Harmonics need the datelist. '
-                'Harmonics will not be calculated'
+                "Harmonics need the datelist. " "Harmonics will not be calculated"
             )
         else:
-            harmonics = True
-            metrics.remove('harmonics')
-            metrics.extend(
-                ['amplitude', 'phase', 'residuals', 'trend', 'model_mean']
-            )
+            metrics.remove("harmonics")
+            metrics.extend(["amplitude", "phase", "residuals", "trend", "model_mean"])
 
-    if 'percentiles' in metrics:
-        metrics.remove('percentiles')
-        metrics.extend(['p95', 'p5'])
+    if "percentiles" in metrics:
+        metrics.remove("percentiles")
+        metrics.extend(["p95", "p5"])
 
     with rasterio.open(stack) as src:
 
@@ -203,41 +190,57 @@ def mt_metrics(
         meta = src.profile
 
         # update driver and reduced band count
-        meta.update({'driver': 'GTiff'})
-        meta.update({'count': 1})
+        meta.update({"driver": "GTiff"})
+        meta.update({"count": 1})
 
         # write all different output files into a dictionary
         metric_dict = {}
         for metric in metrics:
-            filename = f'{out_prefix}.{metric}.tif'
-            metric_dict[metric] = rasterio.open(filename, 'w', **meta)
+            filename = f"{out_prefix}.{metric}.tif"
+            metric_dict[metric] = rasterio.open(filename, "w", **meta)
 
         # scaling factors in case we have to rescale to integer
         minimums = {
-            'avg': int(-30), 'max': int(-30), 'min': int(-30),
-            'median': -30, 'p5': -30, 'p95': -30,
-            'std': 0.00001, 'cov': 0.00001,
-            'amplitude': -5,  'phase': -np.pi, 'residuals': -10,
-            'trend': -5, 'model_mean': -30
+            "avg": int(-30),
+            "max": int(-30),
+            "min": int(-30),
+            "median": -30,
+            "p5": -30,
+            "p95": -30,
+            "std": 0.00001,
+            "cov": 0.00001,
+            "amplitude": -5,
+            "phase": -np.pi,
+            "residuals": -10,
+            "trend": -5,
+            "model_mean": -30,
         }
 
         maximums = {
-            'avg': 5, 'max': 5, 'min': 5,
-            'median': 5, 'p5': 5, 'p95': 5,
-            'std': 0.2, 'cov': 1,
-            'amplitude': 5,  'phase': np.pi, 'residuals': 10,
-            'trend': 5, 'model_mean': 5
+            "avg": 5,
+            "max": 5,
+            "min": 5,
+            "median": 5,
+            "p5": 5,
+            "p95": 5,
+            "std": 0.2,
+            "cov": 1,
+            "amplitude": 5,
+            "phase": np.pi,
+            "residuals": 10,
+            "trend": 5,
+            "model_mean": 5,
         }
 
-        if 'amplitude' in metrics:
+        if "amplitude" in metrics:
             # construct independent variables
             dates, sines, cosines, intercept = [], [], [], []
             two_pi = np.multiply(2, np.pi)
 
             for date in sorted(datelist):
                 delta = difference_in_years(
-                    datetime.strptime('700101', "%y%m%d"),
-                    datetime.strptime(date, "%y%m%d")
+                    datetime.strptime("700101", "%y%m%d"),
+                    datetime.strptime(date, "%y%m%d"),
                 )
                 dates.append(delta)
                 sines.append(np.sin(np.multiply(two_pi, delta)))
@@ -253,8 +256,8 @@ def mt_metrics(
             stack = src.read(range(1, src.count + 1), window=window)
 
             # rescale to float
-            if rescale_to_datatype is True and meta['dtype'] != 'float32':
-                stack = ras.rescale_to_float(stack, meta['dtype'])
+            if rescale_to_datatype is True and meta["dtype"] != "float32":
+                stack = ras.rescale_to_float(stack, meta["dtype"])
 
             # transform to power
             if to_power is True:
@@ -266,31 +269,32 @@ def mt_metrics(
 
             # get stats
             arr = {
-                'p95': (nan_percentile(stack, [95, 5])
-                        if 'p95' in metrics else (False, False))[0],
-                'p5': (nan_percentile(stack, [95, 5])
-                       if 'p95' in metrics else (False, False))[1],
-                'median': (np.nanmedian(stack, axis=0)
-                          if 'median' in metrics else False),
-                'avg': (np.nanmean(stack, axis=0)
-                       if 'avg' in metrics else False),
-                'max': (np.nanmax(stack, axis=0)
-                       if 'max' in metrics else False),
-                'min': (np.nanmin(stack, axis=0)
-                       if 'min' in metrics else False),
-                'std': (np.nanstd(stack, axis=0)
-                       if 'std' in metrics else False),
-                #'cov': (stats.variation(stack, axis=0, nan_policy='omit')
-                'cov': (
-                   np.divide(
-                        np.nanstd(stack, axis=0),
-                        np.nanmean(stack, axis=0)
-                   )
-                   if 'cov' in metrics else False
-                )
+                "p95": (
+                    nan_percentile(stack, [95, 5])
+                    if "p95" in metrics
+                    else (False, False)
+                )[0],
+                "p5": (
+                    nan_percentile(stack, [95, 5])
+                    if "p95" in metrics
+                    else (False, False)
+                )[1],
+                "median": (
+                    np.nanmedian(stack, axis=0) if "median" in metrics else False
+                ),
+                "avg": (np.nanmean(stack, axis=0) if "avg" in metrics else False),
+                "max": (np.nanmax(stack, axis=0) if "max" in metrics else False),
+                "min": (np.nanmin(stack, axis=0) if "min" in metrics else False),
+                "std": (np.nanstd(stack, axis=0) if "std" in metrics else False),
+                # 'cov': (stats.variation(stack, axis=0, nan_policy='omit')
+                "cov": (
+                    np.divide(np.nanstd(stack, axis=0), np.nanmean(stack, axis=0))
+                    if "cov" in metrics
+                    else False
+                ),
             }
 
-            if 'amplitude' in metrics:
+            if "amplitude" in metrics:
 
                 stack_size = (stack.shape[1], stack.shape[2])
                 if to_power is True:
@@ -299,19 +303,16 @@ def mt_metrics(
                     y = stack.reshape(stack.shape[0], -1)
 
                 x, residuals, _, _ = np.linalg.lstsq(x_array.T, y, rcond=-1)
-                arr['amplitude'] = np.hypot(x[1], x[2]).reshape(stack_size)
-                arr['phase'] = np.arctan2(x[2], x[1]).reshape(stack_size)
-                arr['trend'] = x[0].reshape(stack_size)
-                arr['model_mean'] = x[3].reshape(stack_size)
-                arr['residuals'] = np.sqrt(
+                arr["amplitude"] = np.hypot(x[1], x[2]).reshape(stack_size)
+                arr["phase"] = np.arctan2(x[2], x[1]).reshape(stack_size)
+                arr["trend"] = x[0].reshape(stack_size)
+                arr["model_mean"] = x[3].reshape(stack_size)
+                arr["residuals"] = np.sqrt(
                     np.divide(residuals, stack.shape[0])
                 ).reshape(stack_size)
 
-
             # the metrics to be re-turned to dB, in case to_power is True
-            metrics_to_convert = [
-                'avg', 'min', 'max', 'p95', 'p5', 'median'
-            ]
+            metrics_to_convert = ["avg", "min", "max", "p95", "p5", "median"]
 
             # do the back conversions and write to disk loop
             for metric in metrics:
@@ -319,25 +320,24 @@ def mt_metrics(
                 if to_power is True and metric in metrics_to_convert:
                     arr[metric] = ras.convert_to_db(arr[metric])
 
-                if ((rescale_to_datatype is True
-                     and meta['dtype'] != 'float32')
-                        or (metric in ['cov', 'phase']
-                            and meta['dtype'] != 'float32')):
+                if (rescale_to_datatype is True and meta["dtype"] != "float32") or (
+                    metric in ["cov", "phase"] and meta["dtype"] != "float32"
+                ):
                     arr[metric] = ras.scale_to_int(
-                        arr[metric], minimums[metric], maximums[metric],
-                        meta['dtype']
+                        arr[metric], minimums[metric], maximums[metric], meta["dtype"]
                     )
 
                 # write to dest
                 metric_dict[metric].write(
-                    np.nan_to_num(arr[metric]).astype(meta['dtype']),
-                    window=window, indexes=1
+                    np.nan_to_num(arr[metric]).astype(meta["dtype"]),
+                    window=window,
+                    indexes=1,
                 )
                 metric_dict[metric].update_tags(
-                    1, BAND_NAME=f'{Path(out_prefix).name}_{metric}'
+                    1, BAND_NAME=f"{Path(out_prefix).name}_{metric}"
                 )
                 metric_dict[metric].set_band_description(
-                    1, f'{Path(out_prefix).name}_{metric}'
+                    1, f"{Path(out_prefix).name}_{metric}"
                 )
 
     # close the output files
@@ -346,25 +346,25 @@ def mt_metrics(
         metric_dict[metric].close()
 
         # construct filename
-        filename = f'{str(out_prefix)}.{metric}.tif'
+        filename = f"{str(out_prefix)}.{metric}.tif"
         return_code = h.check_out_tiff(filename)
 
         if return_code != 0:
 
             for metric_ in metrics:
                 # remove all files and return
-                filename = f'{str(out_prefix)}.{metric_}.tif'
+                filename = f"{str(out_prefix)}.{metric_}.tif"
                 Path(filename).unlink()
-                if Path(f'{filename}.xml').exists():
-                    Path(f'{filename}.xml').unlink()
+                if Path(f"{filename}.xml").exists():
+                    Path(f"{filename}.xml").unlink()
 
             return None, None, None, return_code
 
     # write out that it's been processed
     dirname = out_prefix.parent
-    check_file = dirname.joinpath(f'.{out_prefix.name}.processed')
-    with open(str(check_file), 'w') as file:
-        file.write('passed all tests \n')
+    check_file = dirname / f".{out_prefix.name}.processed"
+    with open(str(check_file), "w") as file:
+        file.write("passed all tests \n")
 
     target = out_prefix.parent.parent.name
     return target, out_prefix.name, metrics, None
@@ -374,6 +374,11 @@ def gd_mt_metrics(list_of_args):
     stack, out_prefix, metrics, rescale_to_datatype = list_of_args[:4]
     to_power, outlier_removal, datelist = list_of_args[4:]
     return mt_metrics(
-        stack, out_prefix, metrics, rescale_to_datatype, to_power,
-        outlier_removal, datelist
+        stack,
+        out_prefix,
+        metrics,
+        rescale_to_datatype,
+        to_power,
+        outlier_removal,
+        datelist,
     )
diff --git a/ost/generic/ts_extent.py b/ost/generic/ts_extent.py
index 387c1d47..a3ec2298 100644
--- a/ost/generic/ts_extent.py
+++ b/ost/generic/ts_extent.py
@@ -4,14 +4,6 @@
 import json
 import logging
 
-try:
-    import gdal
-except ModuleNotFoundError as e:
-    try:
-        from osgeo import gdal
-    except ModuleNotFoundError:
-        raise e
-
 import geopandas as gpd
 from retrying import retry
 
@@ -27,13 +19,14 @@ def mt_extent(list_of_extents, config_file):
         config_dict = json.load(file)
 
     import warnings
-    warnings.filterwarnings('ignore', 'GeoSeries.isna', UserWarning)
+
+    warnings.filterwarnings("ignore", "GeoSeries.isna", UserWarning)
 
     # get track/burst dir from first scene
     target_dir = list_of_extents[0].parent.parent.parent
-    out_file = target_dir.joinpath(f'{target_dir.name}.min_bounds.json')
+    out_file = target_dir / f"{target_dir.name}.min_bounds.json"
 
-    logger.info(f'Creating common image bounds for track {target_dir.name}.')
+    logger.info(f"Creating common image bounds for track {target_dir.name}.")
 
     for i, file in enumerate(list_of_extents):
 
@@ -46,26 +39,26 @@ def mt_extent(list_of_extents, config_file):
             df2 = df2[~(df2.geometry.is_empty | df2.geometry.isna())]
 
             # do intersect
-            df1 = gpd.overlay(
-                df1, df2, how='intersection'
-            )[['raster_val_1', 'geometry']]
+            df1 = gpd.overlay(df1, df2, how="intersection")[
+                ["raster_val_1", "geometry"]
+            ]
 
             # rename columns
-            df1.columns = ['raster_val', 'geometry']
+            df1.columns = ["raster_val", "geometry"]
             # remove empty or non geometries
             df1 = df1[~(df1.geometry.is_empty | df1.geometry.isna())]
         else:
-            raise RuntimeError('No extents found.')
+            raise RuntimeError("No extents found.")
 
-    if config_dict['processing']['mosaic']['cut_to_aoi']:
+    if config_dict["processing"]["mosaic"]["cut_to_aoi"]:
 
         try:
-            aoi_df = vec.wkt_to_gdf(config_dict['aoi'])
-            df = gpd.overlay(aoi_df, df1, how='intersection')
-            df.to_file(out_file, driver='GPKG')
+            aoi_df = vec.wkt_to_gdf(config_dict["aoi"])
+            df = gpd.overlay(aoi_df, df1, how="intersection")
+            df.to_file(out_file, driver="GPKG")
         except ValueError:
-            df1.to_file(out_file, driver='GeoJSON')
+            df1.to_file(out_file, driver="GeoJSON")
     else:
-        df1.to_file(out_file, driver='GeoJSON')
+        df1.to_file(out_file, driver="GeoJSON")
 
     return target_dir.name, list_of_extents, out_file
diff --git a/ost/generic/ts_ls_mask.py b/ost/generic/ts_ls_mask.py
index 7f7ee001..a360d14f 100644
--- a/ost/generic/ts_ls_mask.py
+++ b/ost/generic/ts_ls_mask.py
@@ -10,14 +10,6 @@
 from shapely.ops import unary_union
 from retrying import retry
 
-try:
-    import gdal
-except ModuleNotFoundError as e:
-    try:
-        from osgeo import gdal
-    except ModuleNotFoundError:
-        raise e
-
 from ost.helpers import vector as vec
 
 logger = logging.getLogger(__name__)
@@ -27,16 +19,15 @@
 def mt_layover(list_of_ls):
 
     import warnings
-    warnings.filterwarnings('ignore', 'GeoSeries.isna', UserWarning)
+
+    warnings.filterwarnings("ignore", "GeoSeries.isna", UserWarning)
 
     target_dir = Path(list_of_ls[0]).parent.parent.parent
-    bounds = target_dir.joinpath(f'{target_dir.name}.min_bounds.json')
-    outfile = target_dir.joinpath(f'{target_dir.name}.ls_mask.json')
-    valid_file = target_dir.joinpath(f'{target_dir.name}.valid.json')
+    bounds = target_dir / f"{target_dir.name}.min_bounds.json"
+    outfile = target_dir / f"{target_dir.name}.ls_mask.json"
+    valid_file = target_dir / f"{target_dir.name}.valid.json"
 
-    logger.info(
-        f'Creating common Layover/Shadow mask for track {target_dir.name}.'
-    )
+    logger.info(f"Creating common Layover/Shadow mask for track {target_dir.name}.")
 
     y = 0
     for i, file in enumerate(list_of_ls):
@@ -62,14 +53,14 @@ def mt_layover(list_of_ls):
 
         # remove slivers
         buffer = 0.00001
-        geom = geom.buffer(
-                -buffer, 1, join_style=2
-            ).buffer(
-                buffer, 1, cap_style=1, join_style=2
-            ).__geo_interface__
+        geom = (
+            geom.buffer(-buffer, 1, join_style=2)
+            .buffer(buffer, 1, cap_style=1, join_style=2)
+            .__geo_interface__
+        )
 
         # write to output
-        with open(outfile, 'w') as file:
+        with open(outfile, "w") as file:
             json.dump(geom, file)
 
         # create difference file for valid data shape
diff --git a/ost/helpers/asf.py b/ost/helpers/asf.py
index dd150547..8017db78 100644
--- a/ost/helpers/asf.py
+++ b/ost/helpers/asf.py
@@ -7,7 +7,6 @@
 import getpass
 import logging
 import requests
-import multiprocessing
 from pathlib import Path
 
 import tqdm
@@ -28,10 +27,12 @@ def ask_credentials():
     """
 
     # SciHub account details (will be asked by execution)
-    print(' If you do not have a ASF/NASA Earthdata user account'
-          ' go to: https://search.asf.alaska.edu/ and register')
-    uname = input(' Your ASF/NASA Earthdata Username:')
-    pword = getpass.getpass(' Your ASF/NASA Earthdata Password:')
+    print(
+        " If you do not have a ASF/NASA Earthdata user account"
+        " go to: https://search.asf.alaska.edu/ and register"
+    )
+    uname = input(" Your ASF/NASA Earthdata Username:")
+    pword = getpass.getpass(" Your ASF/NASA Earthdata Password:")
 
     return uname, pword
 
@@ -46,13 +47,13 @@ def check_connection(uname, pword):
 
     # random url to check
     url = (
-        'https://datapool.asf.alaska.edu/SLC/SA/S1A_IW_SLC__1SSV_'
-        '20160801T234454_20160801T234520_012413_0135F9_B926.zip'
+        "https://datapool.asf.alaska.edu/SLC/SA/S1A_IW_SLC__1SSV_"
+        "20160801T234454_20160801T234520_012413_0135F9_B926.zip"
     )
 
     with requests.Session() as session:
         session.auth = (uname, pword)
-        request = session.request('get', url)
+        request = session.request("get", url)
         response = session.get(request.url, auth=(uname, pword), stream=True)
         return response.status_code
 
@@ -82,15 +83,15 @@ def asf_download(url, filename, uname, pword):
     with requests.Session() as session:
 
         session.auth = (uname, pword)
-        request = session.request('get', url)
+        request = session.request("get", url)
         response = session.get(request.url, auth=(uname, pword), stream=True)
 
         # raise an exception in case of http errors
         response.raise_for_status()
 
-        logger.info(f'Downloading scene to: {filename}')
+        logger.info(f"Downloading scene to: {filename}")
         # get download size
-        total_length = int(response.headers.get('content-length', 0))
+        total_length = int(response.headers.get("content-length", 0))
 
         # define chunk_size
         chunk_size = 1024
@@ -113,9 +114,13 @@ def asf_download(url, filename, uname, pword):
                 if total_length is None:
                     file.write(response.content)
                 else:
-                    pbar = tqdm.tqdm(total=total_length, initial=first_byte,
-                                     unit='B', unit_scale=True,
-                                     desc=' INFO: Downloading ')
+                    pbar = tqdm.tqdm(
+                        total=total_length,
+                        initial=first_byte,
+                        unit="B",
+                        unit_scale=True,
+                        desc=" INFO: Downloading ",
+                    )
 
                     for chunk in response.iter_content(chunk_size):
                         if chunk:
@@ -134,13 +139,13 @@ def asf_download(url, filename, uname, pword):
         if filename.exists():
             filename.unlink()
         raise DownloadError(
-            f'{filename.name} did not pass the zip test. '
-            f'Re-downloading the full scene.'
+            f"{filename.name} did not pass the zip test. "
+            f"Re-downloading the full scene."
         )
     else:
-        logger.info(f'{filename.name} passed the zip test.')
-        with open(filename.with_suffix('.downloaded'), 'w+') as file:
-            file.write('successfully downloaded \n')
+        logger.info(f"{filename.name} passed the zip test.")
+        with open(filename.with_suffix(".downloaded"), "w+") as file:
+            file.write("successfully downloaded \n")
 
 
 def batch_download(inventory_df, download_dir, uname, pword, concurrent=10):
@@ -148,7 +153,7 @@ def batch_download(inventory_df, download_dir, uname, pword, concurrent=10):
     from ost import Sentinel1Scene as S1Scene
 
     # create list with scene ids to download
-    scenes = inventory_df['identifier'].tolist()
+    scenes = inventory_df["identifier"].tolist()
 
     asf_list = []
     for scene_id in scenes:
@@ -158,8 +163,8 @@ def batch_download(inventory_df, download_dir, uname, pword, concurrent=10):
         file_path = scene.download_path(download_dir, True)
 
         # check if already downloaded
-        if file_path.with_suffix('.downloaded').exists():
-            logger.info(f'{scene.scene_id} has been already downloaded.')
+        if file_path.with_suffix(".downloaded").exists():
+            logger.info(f"{scene.scene_id} has been already downloaded.")
             continue
 
         # append to list
@@ -168,30 +173,27 @@ def batch_download(inventory_df, download_dir, uname, pword, concurrent=10):
     # if list is not empty, do parallel download
     check_counter = 0
     if asf_list:
-        executor = Executor(max_workers=concurrent,
-                            executor='concurrent_processes')
+        executor = Executor(max_workers=concurrent, executor="concurrent_processes")
 
         for task in executor.as_completed(
-                func=asf_download_parallel,
-                iterable=asf_list,
-                fargs=[]
+            func=asf_download_parallel, iterable=asf_list, fargs=[]
         ):
             task.result()
             check_counter += 1
 
     # if all have been downloaded then we are through
-    if len(inventory_df['identifier'].tolist()) == check_counter:
-        logger.info('All products are downloaded.')
+    if len(inventory_df["identifier"].tolist()) == check_counter:
+        logger.info("All products are downloaded.")
     # else we
     else:
         for scene in scenes:
             # we check if outputfile exists...
             scene = S1Scene(scene)
             file_path = scene.download_path(download_dir)
-            if file_path.with_suffix('.downloaded').exists():
+            if file_path.with_suffix(".downloaded").exists():
                 # ...and remove from list of scenes to download
                 scenes.remove(scene.scene_id)
             else:
                 raise DownloadError(
-                    'ASF download is incomplete or has failed. Try to re-run.'
+                    "ASF download is incomplete or has failed. Try to re-run."
                 )
diff --git a/ost/helpers/asf_wget.py b/ost/helpers/asf_wget.py
index c9fe189e..0eb002e2 100644
--- a/ost/helpers/asf_wget.py
+++ b/ost/helpers/asf_wget.py
@@ -1,13 +1,12 @@
 # -*- coding: utf-8 -*-
-'''This module provides functions for connecting and downloading
+"""This module provides functions for connecting and downloading
 from Alaska satellite Faciltity's Vertex server
-'''
+"""
 
 import os
 from os.path import join as opj
 import glob
 import requests
-import tqdm
 import multiprocessing
 import logging
 from subprocess import Popen
@@ -18,13 +17,12 @@
 
 logger = logging.getLogger(__name__)
 
+
 # we need this class for earthdata access
 class SessionWithHeaderRedirection(requests.Session):
-    ''' A class that helps connect to NASA's Earthdata
-
-    '''
+    """A class that helps connect to NASA's Earthdata"""
 
-    AUTH_HOST = 'urs.earthdata.nasa.gov'
+    AUTH_HOST = "urs.earthdata.nasa.gov"
 
     def __init__(self, username, password):
         super().__init__()
@@ -38,22 +36,24 @@ def rebuild_auth(self, prepared_request, response):
         headers = prepared_request.headers
         url = prepared_request.url
 
-        if 'Authorization' in headers:
+        if "Authorization" in headers:
 
             original_parsed = requests.utils.urlparse(response.request.url)
             redirect_parsed = requests.utils.urlparse(url)
 
-            if (original_parsed.hostname != redirect_parsed.hostname) and \
-                redirect_parsed.hostname != self.AUTH_HOST and \
-                    original_parsed.hostname != self.AUTH_HOST:
+            if (
+                (original_parsed.hostname != redirect_parsed.hostname)
+                and redirect_parsed.hostname != self.AUTH_HOST
+                and original_parsed.hostname != self.AUTH_HOST
+            ):
 
-                del headers['Authorization']
+                del headers["Authorization"]
 
         return
 
 
 def check_connection(uname, pword):
-    '''A helper function to check if a connection can be established
+    """A helper function to check if a connection can be established
 
     Args:
         uname: username of ASF Vertex server
@@ -61,21 +61,29 @@ def check_connection(uname, pword):
 
     Returns
         int: status code of the get request
-    '''
-    url = ('https://datapool.asf.alaska.edu/OCN/SB/S1B_IW_OCN__2SDV_20191016T051912_20191016T051937_018496_022DA0_2161.zip')
-    #url = ('https://datapool.asf.alaska.edu/SLC/SA/S1A_IW_SLC__1SSV_'
+    """
+    url = "https://datapool.asf.alaska.edu/OCN/SB/S1B_IW_OCN__2SDV_20191016T051912_20191016T051937_018496_022DA0_2161.zip"
+    # url = ('https://datapool.asf.alaska.edu/SLC/SA/S1A_IW_SLC__1SSV_'
     #       '20160801T234454_20160801T234520_012413_0135F9_B926.zip')
-    command='wget  --server-response -c --check-certificate=off -nv --http-user='+uname+' --http-passwd="'+pword+'" "'+url+'" 2>&1|awk "/^  HTTP/{print $2}"'
-    process = Popen(command, stdout=PIPE, stderr=PIPE, shell=True)    
-    astdout, astderr = process.communicate()    
+    command = (
+        "wget  --server-response -c --check-certificate=off -nv --http-user="
+        + uname
+        + ' --http-passwd="'
+        + pword
+        + '" "'
+        + url
+        + '" 2>&1|awk "/^  HTTP/{print $2}"'
+    )
+    process = Popen(command, stdout=PIPE, stderr=PIPE, shell=True)
+    astdout, astderr = process.communicate()
     print(astdout, astderr)
-    outlines=astdout.decode().split('\n')
-    response = [int(i) for i in outlines[len(outlines)-2].split() if i.isdigit()][0]
-    os.remove('S1B_IW_OCN__2SDV_20191016T051912_20191016T051937_018496_022DA0_2161.zip')
-    '''session = SessionWithHeaderRedirection(uname, pword)
+    outlines = astdout.decode().split("\n")
+    response = [int(i) for i in outlines[len(outlines) - 2].split() if i.isdigit()][0]
+    os.remove("S1B_IW_OCN__2SDV_20191016T051912_20191016T051937_018496_022DA0_2161.zip")
+    """session = SessionWithHeaderRedirection(uname, pword)
     response = session.get(url, stream=True)
-    # print(response)'''
-    
+    # print(response)"""
+
     return response
 
 
@@ -96,20 +104,20 @@ def s1_download(argument_list):
     uname = argument_list[2]
     pword = argument_list[3]
 
-    #session = SessionWithHeaderRedirection(uname, pword)
+    # session = SessionWithHeaderRedirection(uname, pword)
 
-    logger.info('Downloading scene to: {}'.format(filename))
+    logger.info("Downloading scene to: {}".format(filename))
     # submit the request using the session
-    #response = session.get(url, stream=True)
+    # response = session.get(url, stream=True)
 
     # raise an exception in case of http errors
-    #response.raise_for_status()
+    # response.raise_for_status()
 
     # get download size
-    #total_length = int(response.headers.get('content-length', 0))
+    # total_length = int(response.headers.get('content-length', 0))
 
     # define chunk_size
-    #chunk_size = 1024
+    # chunk_size = 1024
 
     # check if file is partially downloaded
     if os.path.exists(filename):
@@ -117,34 +125,45 @@ def s1_download(argument_list):
 
     zip_test = 1
     while zip_test is not None and zip_test <= 10:
-        command='wget --check-certificate=off -c -nv --http-user='+uname+' --http-passwd="'+pword+'" -O '+filename+' '+url
-        process = Popen(command, stdout=PIPE, stderr=PIPE, shell=True)    
-        astdout, astderr = process.communicate()    
+        command = (
+            "wget --check-certificate=off -c -nv --http-user="
+            + uname
+            + ' --http-passwd="'
+            + pword
+            + '" -O '
+            + filename
+            + " "
+            + url
+        )
+        process = Popen(command, stdout=PIPE, stderr=PIPE, shell=True)
+        astdout, astderr = process.communicate()
         print(astdout, astderr)
 
-
-        logger.info('Checking the zip archive of {} for inconsistency'
-                  .format(filename))
+        logger.info("Checking the zip archive of {} for inconsistency".format(filename))
         zip_test = h.check_zipfile(filename)
         # if it did not pass the test, remove the file
         # in the while loop it will be downlaoded again
         if zip_test is not None:
-            logger.info('{} did not pass the zip test. \
-                  Re-downloading the full scene.'.format(filename))
+            logger.info(
+                "{} did not pass the zip test. \
+                  Re-downloading the full scene.".format(
+                    filename
+                )
+            )
             if os.path.exists(filename):
                 os.remove(filename)
             # otherwise we change the status to True
         else:
-            logger.info('{} passed the zip test.'.format(filename))
-            with open(str('{}.downloaded'.format(filename)), 'w') as file:
-                file.write('successfully downloaded \n')
+            logger.info("{} passed the zip test.".format(filename))
+            with open(str("{}.downloaded".format(filename)), "w") as file:
+                file.write("successfully downloaded \n")
 
 
 def batch_download(inventory_df, download_dir, uname, pword, concurrent=10):
 
     # create list of scenes
-    scenes = inventory_df['identifier'].tolist()
-    
+    scenes = inventory_df["identifier"].tolist()
+
     check, i = False, 1
     while check is False and i <= 10:
 
@@ -155,24 +174,22 @@ def batch_download(inventory_df, download_dir, uname, pword, concurrent=10):
             scene = S1Scene(scene_id)
             filepath = scene._download_path(download_dir, True)
 
-            if os.path.exists('{}.downloaded'.format(filepath)):
-                logger.info('{} is already downloaded.'
-                      .format(scene.scene_id))
+            if os.path.exists("{}.downloaded".format(filepath)):
+                logger.info("{} is already downloaded.".format(scene.scene_id))
             else:
-                asf_list.append([scene.asf_url(), filepath,
-                                 uname, pword])
+                asf_list.append([scene.asf_url(), filepath, uname, pword])
 
         if asf_list:
             pool = multiprocessing.Pool(processes=concurrent)
             pool.map(s1_download, asf_list)
-                    
+
         downloaded_scenes = glob.glob(
-            opj(download_dir, 'SAR', '*', '20*', '*', '*',
-                '*.zip.downloaded'))
+            opj(download_dir, "SAR", "*", "20*", "*", "*", "*.zip.downloaded")
+        )
 
-        if len(inventory_df['identifier'].tolist()) == len(downloaded_scenes):
+        if len(inventory_df["identifier"].tolist()) == len(downloaded_scenes):
             check = True
-            logger.info('All products are downloaded.')
+            logger.info("All products are downloaded.")
         else:
             check = False
             for scene in scenes:
@@ -180,7 +197,7 @@ def batch_download(inventory_df, download_dir, uname, pword, concurrent=10):
                 scene = S1Scene(scene)
                 filepath = scene._download_path(download_dir)
 
-                if os.path.exists('{}.downloaded'.format(filepath)):
+                if os.path.exists("{}.downloaded".format(filepath)):
                     scenes.remove(scene.scene_id)
 
         i += 1
diff --git a/ost/helpers/db.py b/ost/helpers/db.py
index 70c92201..63e20f5f 100644
--- a/ost/helpers/db.py
+++ b/ost/helpers/db.py
@@ -12,21 +12,15 @@
 import os
 import logging
 import psycopg2 as pg
-
-try:
-    import ogr
-except ModuleNotFoundError as e:
-    try:
-        from osgeo import ogr
-    except ModuleNotFoundError:
-        raise e
+from osgeo import ogr
 
 from ost.helpers.vector import get_proj4, reproject_geometry
 
 logger = logging.getLogger(__name__)
 
+
 # see if the pg-file is there
-def pgHandler(dbConnectFile = '{}/.phiSAR/pgdb'.format(os.getenv("HOME"))):
+def pgHandler(dbConnectFile="{}/.phiSAR/pgdb".format(os.getenv("HOME"))):
     """
     This function connects to an existing PostGreSQL database,
     with the access parameters stored in the dbConnectFile as follows:
@@ -44,7 +38,9 @@ def pgHandler(dbConnectFile = '{}/.phiSAR/pgdb'.format(os.getenv("HOME"))):
     try:
         f = open(dbConnectFile)
     except (FileNotFoundError, IOError):
-        logger.info('ERROR: No PostGreSQL connection established. Make sure to configure a connection to phiSAR.')
+        logger.info(
+            "ERROR: No PostGreSQL connection established. Make sure to configure a connection to phiSAR."
+        )
 
     # read out dbname, username
     lines = f.read().splitlines()
@@ -54,37 +50,39 @@ def pgHandler(dbConnectFile = '{}/.phiSAR/pgdb'.format(os.getenv("HOME"))):
     host = lines[3]
     port = lines[4]
 
-    logger.info('Connecting to PostGreSQL database: {}'.format(dbname))
+    logger.info("Connecting to PostGreSQL database: {}".format(dbname))
     dbConnect = pgConnect(uname, pwDb, dbname, host, port)
 
     return dbConnect
 
 
 class pgConnect:
-
-    def __init__(self, uname=None, pword=None, dbname='sat', host='localhost', port='5432'):
+    def __init__(
+        self, uname=None, pword=None, dbname="sat", host="localhost", port="5432"
+    ):
         """
         Establish a connection to the Scihub-catalogue db
         """
 
         # ask for username and password in case you have not defined as command line options
-        if uname == None:
-            uname = input(' Your PostGreSQL database username:')
-        if pword == None:
-            pword = getpass.getpass(' Your PostGreSQL database password:')
+        if uname is None:
+            uname = input(" Your PostGreSQL database username:")
+        if pword is None:
+            pword = getpass.getpass(" Your PostGreSQL database password:")
 
         # try connecting
         try:
             self.connection = pg.connect(
-                dbname=dbname, user=uname, host=host, password=pword, port=port)
+                dbname=dbname, user=uname, host=host, password=pword, port=port
+            )
             self.connection.autocommit = True
             self.cursor = self.connection.cursor()
-        except:
-            logger.info('Cannot connect to database')
+        except Exception:
+            logger.info("Cannot connect to database")
 
     def pgCreateS1(self, tablename):
 
-        f_list = ('id serial PRIMARY KEY, identifier varchar(100), \
+        f_list = "id serial PRIMARY KEY, identifier varchar(100), \
                    polarisation varchar(100), orbitdirection varchar(12), \
                    acquisitiondate date, relativeorbit smallint, \
                    orbitnumber integer, producttype varchar(4), \
@@ -94,27 +92,29 @@ def pgCreateS1(self, tablename):
                    uuid varchar(40), platformidentifier varchar(10), \
                    missiondatatakeid integer, swathidentifer varchar(21), \
                    ingestiondate timestamp, sensoroperationalmode varchar(3), \
-                   geometry geometry')
+                   geometry geometry"
 
-        sql_cmd = 'CREATE TABLE {} ({})'.format(tablename, f_list)
+        sql_cmd = "CREATE TABLE {} ({})".format(tablename, f_list)
         self.cursor.execute(sql_cmd)
 
     def pgGetUUID(self, sceneID, tablename):
 
-        sql_cmd = 'SELECT uuid FROM {} WHERE identifier = \'{}\''.format(tablename, sceneID)
+        sql_cmd = "SELECT uuid FROM {} WHERE identifier = '{}'".format(
+            tablename, sceneID
+        )
         self.cursor.execute(sql_cmd)
         uuid = self.cursor.fetchall()[0][0]
         return uuid
 
     def pgDrop(self, tablename):
-        sql_cmd = 'DROP TABLE {}'.format(tablename)
+        sql_cmd = "DROP TABLE {}".format(tablename)
         self.cursor.execute(sql_cmd)
 
     def pgInsert(self, tablename, values):
         """
         This function inserts a table into the connected database object.
         """
-        sql_cmd = 'INSERT INTO {} VALUES {}'.format(tablename, values)
+        sql_cmd = "INSERT INTO {} VALUES {}".format(tablename, values)
         self.cursor.execute(sql_cmd)
 
     def pgSQL(self, sql):
@@ -135,14 +135,14 @@ def shpGeom2pg(self, aoi, tablename):
         This function is a wrapper to import a shapefile geometry to a PostGreSQL database
         """
 
-        sqlCmd = 'DROP TABLE IF EXISTS {}'.format(tablename)
+        sqlCmd = "DROP TABLE IF EXISTS {}".format(tablename)
         self.cursor.execute(sqlCmd)
 
-        fList = 'id smallint, geometry geometry'
-        sqlCmd = 'CREATE TABLE {} ({})'.format(tablename, fList)
+        fList = "id smallint, geometry geometry"
+        sqlCmd = "CREATE TABLE {} ({})".format(tablename, fList)
         self.cursor.execute(sqlCmd)
 
-        prjFile = '{}.prj'.format(aoi[:-4])
+        prjFile = "{}.prj".format(aoi[:-4])
         inProj4 = get_proj4(prjFile)
 
         sf = ogr.Open(aoi)
@@ -151,12 +151,12 @@ def shpGeom2pg(self, aoi, tablename):
             feature = layer.GetFeature(i)
             wkt = feature.GetGeometryRef().ExportToWkt()
 
-            if inProj4 != '+proj=longlat +datum=WGS84 +no_defs':
+            if inProj4 != "+proj=longlat +datum=WGS84 +no_defs":
                 wkt = reproject_geometry(wkt, inProj4, 4326)
 
-            wkt = 'St_GeomFromText(\'{}\', 4326)'.format(wkt)
-            values = '(\'{}\', {})'.format(i, wkt)
-            sql_cmd = 'INSERT INTO {} VALUES {}'.format(tablename, values)
+            wkt = "St_GeomFromText('{}', 4326)".format(wkt)
+            values = "('{}', {})".format(i, wkt)
+            sql_cmd = "INSERT INTO {} VALUES {}".format(tablename, values)
             self.cursor.execute(sql_cmd)
 
     def pgDateline(self, tablename, uuid):
@@ -165,7 +165,7 @@ def pgDateline(self, tablename, uuid):
         into a geometry collection if it crosses the dateline
         """
         # edited after https://www.mundialis.de/update-for-our-maps-mundialis-application-solves-dateline-wrap/
-        sql_cmd = 'UPDATE {} SET (geometry) = \
+        sql_cmd = "UPDATE {} SET (geometry) = \
                     (SELECT \
                         ST_SetSRID( \
                             ST_CollectionExtract( \
@@ -186,9 +186,9 @@ def pgDateline(self, tablename, uuid):
                         4326 \
                         ) geometry \
                     FROM {} \
-                    WHERE uuid = \'{}\' \
+                    WHERE uuid = '{}' \
                     ) \
-                    WHERE uuid  = \'{}\' \
+                    WHERE uuid  = '{}' \
                     AND ( \
                         ST_Intersects( \
                             geometry, \
@@ -213,5 +213,7 @@ def pgDateline(self, tablename, uuid):
                         ) \
                     ) \
                     AND \
-                        geometry IS NOT NULL'.format(tablename, tablename, uuid, uuid)
+                        geometry IS NOT NULL".format(
+            tablename, tablename, uuid, uuid
+        )
         self.cursor.execute(sql_cmd)
diff --git a/ost/helpers/errors.py b/ost/helpers/errors.py
index 48961653..b1b6ebb1 100644
--- a/ost/helpers/errors.py
+++ b/ost/helpers/errors.py
@@ -3,12 +3,14 @@
 
 class GPTRuntimeError(RuntimeError):
     """Raised when a GPT process returns wrong return code."""
+
     def __init__(self, message):
         self.message = message
 
 
 class NotValidFileError(RuntimeError):
     """Raised when an output file did not pass the validation test."""
+
     def __init__(self, message):
         self.message = message
 
diff --git a/ost/helpers/helpers.py b/ost/helpers/helpers.py
index 497b6455..066dfb8a 100644
--- a/ost/helpers/helpers.py
+++ b/ost/helpers/helpers.py
@@ -15,14 +15,7 @@
 import logging
 from pathlib import Path
 from datetime import timedelta
-
-try:
-    import gdal
-except ModuleNotFoundError as e:
-    try:
-        from osgeo import gdal
-    except ModuleNotFoundError:
-        raise e
+from osgeo import gdal
 
 logger = logging.getLogger(__name__)
 
@@ -37,7 +30,7 @@ def timer(start):
     """
 
     elapsed = time.time() - start
-    logger.debug(f'Time elapsed: {timedelta(seconds=elapsed)}')
+    logger.debug(f"Time elapsed: {timedelta(seconds=elapsed)}")
 
 
 def remove_folder_content(folder):
@@ -64,7 +57,7 @@ def run_command(command, logfile=None, elapsed=True):
 
     currtime = time.time()
 
-    if os.name == 'nt':
+    if os.name == "nt":
         process = subprocess.run(command, stderr=subprocess.PIPE)
     else:
         process = subprocess.run(shlex.split(command), stderr=subprocess.PIPE)
@@ -72,9 +65,9 @@ def run_command(command, logfile=None, elapsed=True):
     return_code = process.returncode
 
     if return_code != 0 and logfile is not None:
-        with open(str(logfile), 'w') as file:
+        with open(str(logfile), "w") as file:
             for line in process.stderr.decode().splitlines():
-                file.write(f'{line}\n')
+                file.write(f"{line}\n")
 
     if elapsed:
         timer(currtime)
@@ -83,97 +76,82 @@ def run_command(command, logfile=None, elapsed=True):
 
 
 def delete_dimap(dimap_prefix):
-    """Removes both dim and data from a Snap dimap file
+    """Removes both dim and data from a Snap dimap file"""
 
-    """
+    if dimap_prefix.with_suffix(".data").exists():
+        shutil.rmtree(dimap_prefix.with_suffix(".data"))
 
-    if dimap_prefix.with_suffix('.data').exists():
-        shutil.rmtree(dimap_prefix.with_suffix('.data'))
-
-    if dimap_prefix.with_suffix('.dim').exists():
-        dimap_prefix.with_suffix('.dim').unlink()
+    if dimap_prefix.with_suffix(".dim").exists():
+        dimap_prefix.with_suffix(".dim").unlink()
 
 
 def delete_shapefile(shapefile):
-    """Removes the shapefile and all its associated files
+    """Removes the shapefile and all its associated files"""
 
-    """
+    extensions = (".shp", ".prj", ".shx", ".dbf", ".cpg", "shb")
 
-    extensions = ('.shp', '.prj', '.shx', '.dbf', '.cpg', 'shb')
+    for file in glob.glob(f"{os.path.abspath(shapefile[:-4])}*"):
 
-    for file in glob.glob(f'{os.path.abspath(shapefile[:-4])}*'):
-
-        if len(os.path.abspath(shapefile)) == len(file) and \
-                file.endswith(extensions):
+        if len(os.path.abspath(shapefile)) == len(file) and file.endswith(extensions):
 
             os.remove(file)
 
 
 def move_dimap(infile_prefix, outfile_prefix, to_tif):
-    """Function to move dimap's data and dim another locations
-    """
+    """Function to move dimap's data and dim another locations"""
 
     if to_tif:
 
-        gdal.Warp(
-            outfile_prefix.with_suffix('.tif'),
-            infile_prefix.with_suffix('.dim')
-        )
+        gdal.Warp(outfile_prefix.with_suffix(".tif"), infile_prefix.with_suffix(".dim"))
 
     else:
 
         # delete outfile if exists
-        if outfile_prefix.with_suffix('.data').exists():
+        if outfile_prefix.with_suffix(".data").exists():
             delete_dimap(outfile_prefix)
 
         # move them
         try:
-            infile_prefix.with_suffix('.data').rename(
-                outfile_prefix.with_suffix('.data')
+            infile_prefix.with_suffix(".data").rename(
+                outfile_prefix.with_suffix(".data")
             )
         except OSError:
 
             shutil.copytree(
-                infile_prefix.with_suffix('.data'),
-                outfile_prefix.with_suffix('.data')
-            )
-            shutil.rmtree(
-                infile_prefix.with_suffix('.data')
+                infile_prefix.with_suffix(".data"), outfile_prefix.with_suffix(".data")
             )
+            shutil.rmtree(infile_prefix.with_suffix(".data"))
 
         try:
-            infile_prefix.with_suffix('.dim').rename(
-                outfile_prefix.with_suffix('.dim')
-            )
+            infile_prefix.with_suffix(".dim").rename(outfile_prefix.with_suffix(".dim"))
         except OSError:
             shutil.move(
-                infile_prefix.with_suffix('.dim'),
-                outfile_prefix.with_suffix('.dim')
+                infile_prefix.with_suffix(".dim"), outfile_prefix.with_suffix(".dim")
             )
 
 
 def check_out_dimap(dimap_prefix, test_stats=True):
 
     # check if both dim and data exist, else return
-    if not dimap_prefix.with_suffix('.dim').exists():
-        return f'Output file {dimap_prefix}.dim has not been generated.'
+    if not dimap_prefix.with_suffix(".dim").exists():
+        return f"Output file {dimap_prefix}.dim has not been generated."
 
-    if not dimap_prefix.with_suffix('.data').exists():
-        return f'Output file {dimap_prefix}.data has not been generated.'
+    if not dimap_prefix.with_suffix(".data").exists():
+        return f"Output file {dimap_prefix}.data has not been generated."
 
     # check for file size of the dim file
-    dim_size = dimap_prefix.with_suffix('.dim').stat().st_size
+    dim_size = dimap_prefix.with_suffix(".dim").stat().st_size
 
     if dim_size < 8:
-        return f'File {dimap_prefix}.dim seems to small.'
+        return f"File {dimap_prefix}.dim seems to small."
 
-    for file in dimap_prefix.with_suffix('.data').glob('*.img'):
+    for file in dimap_prefix.with_suffix(".data").glob("*.img"):
 
         # check size
         data_size = file.stat().st_size
 
         if data_size < 8:
-            return f'Data file {file} in {dimap_prefix}.data seem to small.'
+            return f"Data file {file} in {dimap_prefix}.data seem to small."
 
         # test on statistics
         if test_stats:
@@ -185,8 +163,8 @@ def check_out_dimap(dimap_prefix, test_stats=True):
             # if difference of min and max is 0 and mean are all 0
             if stats[1] - stats[0] == 0 and stats[2] == 0:
                 return (
-                    f'Data file {file.name} in {dimap_prefix}.data only '
-                    f'contains no data values.'
+                    f"Data file {file.name} in {dimap_prefix}.data only "
+                    f"contains no data values."
                 )
 
     return 0
@@ -200,13 +178,13 @@ def check_out_tiff(file, test_stats=True):
 
     # check if both dim and data exist, else return
     if not file.exists():
-        return f'Output file {file.name} has not been generated.'
+        return f"Output file {file.name} has not been generated."
 
     # check for file size of the dim file
     tiff_size = file.stat().st_size
 
     if tiff_size < 8:
-        return f'File {file.name} seems to small.'
+        return f"File {file.name} seems to small."
 
     if test_stats:
         # open the file
@@ -215,7 +193,7 @@ def check_out_tiff(file, test_stats=True):
 
         # if difference of min and max is 0 and mean are all 0
         if stats[1] - stats[0] == 0 and stats[2] == 0:
-            return f'Data file {file.name} only contains no data values.'
+            return f"Data file {file.name} only contains no data values."
 
     return return_code
 
@@ -225,13 +203,13 @@ def check_zipfile(filename):
     try:
         zip_archive = zipfile.ZipFile(filename)
     except zipfile.BadZipFile as er:
-        print('Error: {}'.format(er))
+        print("Error: {}".format(er))
         return 1
 
     try:
         zip_test = zip_archive.testzip()
-    except:
-        print('Error')
+    except Exception:
+        print("Error")
         return 1
     else:
         return zip_test
diff --git a/ost/helpers/onda.py b/ost/helpers/onda.py
index 47a2301e..eab32c59 100644
--- a/ost/helpers/onda.py
+++ b/ost/helpers/onda.py
@@ -25,10 +25,12 @@ def ask_credentials():
     """
 
     # SciHub account details (will be asked by execution)
-    print(' If you do not have a ONDA DIAS user account'
-          ' go to: https://www.onda-dias.eu/cms/ and register')
-    uname = input(' Your ONDA DIAS Username:')
-    pword = getpass.getpass(' Your ONDA DIAS Password:')
+    print(
+        " If you do not have a ONDA DIAS user account"
+        " go to: https://www.onda-dias.eu/cms/ and register"
+    )
+    uname = input(" Your ONDA DIAS Username:")
+    pword = getpass.getpass(" Your ONDA DIAS Password:")
 
     return uname, pword
 
@@ -45,14 +47,16 @@ def connect(uname=None, pword=None):
     """
 
     if not uname:
-        print(' If you do not have a ONDA DIAS user'
-              ' account go to: https://www.onda-dias.eu/cms/')
-        uname = input(' Your ONDA DIAS Username:')
+        print(
+            " If you do not have a ONDA DIAS user"
+            " account go to: https://www.onda-dias.eu/cms/"
+        )
+        uname = input(" Your ONDA DIAS Username:")
 
     if not pword:
-        pword = getpass.getpass(' Your ONDA DIAS Password:')
+        pword = getpass.getpass(" Your ONDA DIAS Password:")
 
-    base_url = 'https://catalogue.onda-dias.eu/dias-catalogue/'
+    base_url = "https://catalogue.onda-dias.eu/dias-catalogue/"
 
     # open a connection to the scihub
     manager = urllib.request.HTTPPasswordMgrWithDefaultRealm()
@@ -73,8 +77,8 @@ def check_connection(uname, pword):
 
     # we use some random url for checking
     url = (
-        'https://catalogue.onda-dias.eu/dias-catalogue/'
-        'Products(6e0e382e-45f2-4969-96b0-1130b6cd2fa6)/$value'
+        "https://catalogue.onda-dias.eu/dias-catalogue/"
+        "Products(6e0e382e-45f2-4969-96b0-1130b6cd2fa6)/$value"
     )
     response = requests.get(url, auth=(uname, pword), stream=True)
     return response.status_code
@@ -98,32 +102,31 @@ def onda_download(argument_list):
 
     # ask for username and password in case you have not defined as input
     if not uname:
-        print(' If you do not have a ONDA DIAS user'
-              ' account go to: https://www.onda-dias.eu/cms/')
-        uname = input(' Your ONDA DIAS Username:')
+        print(
+            " If you do not have a ONDA DIAS user"
+            " account go to: https://www.onda-dias.eu/cms/"
+        )
+        uname = input(" Your ONDA DIAS Username:")
 
     if not pword:
-        pword = getpass.getpass(' Your ONDA DIAS Password:')
+        pword = getpass.getpass(" Your ONDA DIAS Password:")
 
     # define url (url differs from scihub by the
     # lack of '' around the product uuid)
-    url = (
-        f'https://catalogue.onda-dias.eu/dias-catalogue/'
-        f'Products({uuid})/$value'
-    )
+    url = f"https://catalogue.onda-dias.eu/dias-catalogue/" f"Products({uuid})/$value"
 
     # get first response for file Size
     response = requests.get(url, stream=True, auth=(uname, pword))
 
     # check response
     if response.status_code == 401:
-        raise ValueError(' ERROR: Username/Password are incorrect.')
+        raise ValueError(" ERROR: Username/Password are incorrect.")
     elif response.status_code != 200:
-        print(' ERROR: Something went wrong, will try again in 30 seconds.')
+        print(" ERROR: Something went wrong, will try again in 30 seconds.")
         response.raise_for_status()
 
     # get download size
-    total_length = int(response.headers.get('content-length', 0))
+    total_length = int(response.headers.get("content-length", 0))
 
     # define chunk_size
     chunk_size = 1024
@@ -145,7 +148,7 @@ def onda_download(argument_list):
             # get byte offset for already downloaded file
             header = {"Range": f"bytes={first_byte}-{total_length}"}
 
-            logger.info(f'Downloading scene to: {filename.resolve()}')
+            logger.info(f"Downloading scene to: {filename.resolve()}")
             response = requests.get(
                 url, headers=header, stream=True, auth=(uname, pword)
             )
@@ -157,8 +160,11 @@ def onda_download(argument_list):
                     file.write(response.content)
                 else:
                     pbar = tqdm.tqdm(
-                        total=total_length, initial=first_byte, unit='B',
-                        unit_scale=True, desc=' INFO: Downloading: '
+                        total=total_length,
+                        initial=first_byte,
+                        unit="B",
+                        unit_scale=True,
+                        desc=" INFO: Downloading: ",
                     )
                     for chunk in response.iter_content(chunk_size):
                         if chunk:
@@ -170,35 +176,33 @@ def onda_download(argument_list):
             first_byte = filename.stat().st_size
 
         # zipFile check
-        logger.info(
-            f'Checking the zip archive of {filename.name} for inconsistency'
-        )
+        logger.info(f"Checking the zip archive of {filename.name} for inconsistency")
         zip_test = h.check_zipfile(filename)
-        
+
         # if it did not pass the test, remove the file
         # in the while loop it will be downlaoded again
         if zip_test is not None:
             logger.info(
-                f'{filename.name} did not pass the zip test. '
-                f'Re-downloading the full scene.'
+                f"{filename.name} did not pass the zip test. "
+                f"Re-downloading the full scene."
             )
             filename.unlink()
             first_byte = 0
         # otherwise we change the status to True
         else:
-            logger.info(f'{filename.name} passed the zip test.')
-            with open(filename.with_suffix('.downloaded'), 'w') as file:
-                file.write('successfully downloaded \n')
-            
-            
+            logger.info(f"{filename.name} passed the zip test.")
+            with open(filename.with_suffix(".downloaded"), "w") as file:
+                file.write("successfully downloaded \n")
+
+
 def batch_download(inventory_df, download_dir, uname, pword, concurrent=2):
-    
+
     from ost import Sentinel1Scene as S1Scene
     from ost.helpers import onda
-    
+
     # create list of scenes
-    scenes = inventory_df['identifier'].tolist()
-    
+    scenes = inventory_df["identifier"].tolist()
+
     check, i = False, 1
     while check is False and i <= 10:
 
@@ -208,19 +212,18 @@ def batch_download(inventory_df, download_dir, uname, pword, concurrent=2):
 
             scene = S1Scene(scene_id)
             file_path = scene.download_path(download_dir, True)
-            
+
             try:
-                uuid = (
-                    inventory_df['uuid']
-                    [inventory_df['identifier'] == scene_id].tolist()
-                )
+                uuid = inventory_df["uuid"][
+                    inventory_df["identifier"] == scene_id
+                ].tolist()
             except KeyError:
                 uuid = scene.ondadias_uuid(
                     opener=onda.connect(uname=uname, pword=pword)
                 )
-            
-            if file_path.with_suffix('.downloaded').exists():
-                logger.info(f'{scene.scene_id} is already downloaded.')
+
+            if file_path.with_suffix(".downloaded").exists():
+                logger.info(f"{scene.scene_id} is already downloaded.")
             else:
                 # create list objects for download
                 download_list.append([uuid[0], file_path, uname, pword])
@@ -228,11 +231,11 @@ def batch_download(inventory_df, download_dir, uname, pword, concurrent=2):
         if download_list:
             pool = multiprocessing.Pool(processes=concurrent)
             pool.map(onda_download, download_list)
-                    
-        downloaded_scenes = list(download_dir.glob('**/*.downloaded'))
-    
-        if len(inventory_df['identifier'].tolist()) == len(downloaded_scenes):
-            logger.info('All products are downloaded.')
+
+        downloaded_scenes = list(download_dir.glob("**/*.downloaded"))
+
+        if len(inventory_df["identifier"].tolist()) == len(downloaded_scenes):
+            logger.info("All products are downloaded.")
             check = True
         else:
             check = False
@@ -241,7 +244,7 @@ def batch_download(inventory_df, download_dir, uname, pword, concurrent=2):
                 scene = S1Scene(scene)
                 file_path = scene.download_path(download_dir)
 
-                if file_path.with_suffix('.downloaded'):
+                if file_path.with_suffix(".downloaded"):
                     scenes.remove(scene.scene_id)
 
         i += 1
diff --git a/ost/helpers/peps.py b/ost/helpers/peps.py
index 15270ce1..09e4831a 100644
--- a/ost/helpers/peps.py
+++ b/ost/helpers/peps.py
@@ -26,10 +26,12 @@ def ask_credentials():
     :rtype: tuple
     """
     # SciHub account details (will be asked by execution)
-    print(' If you do not have a CNES Peps user account'
-          ' go to: https://peps.cnes.fr/ and register')
-    uname = input(' Your CNES Peps Username:')
-    pword = getpass.getpass(' Your CNES Peps Password:')
+    print(
+        " If you do not have a CNES Peps user account"
+        " go to: https://peps.cnes.fr/ and register"
+    )
+    uname = input(" Your CNES Peps Username:")
+    pword = getpass.getpass(" Your CNES Peps Password:")
 
     return uname, pword
 
@@ -46,15 +48,17 @@ def connect(uname=None, pword=None):
     """
 
     if not uname:
-        print(' If you do not have a CNES Peps user account'
-              ' go to: https://peps.cnes.fr/ and register')
-        uname = input(' Your CNES Peps Username:')
+        print(
+            " If you do not have a CNES Peps user account"
+            " go to: https://peps.cnes.fr/ and register"
+        )
+        uname = input(" Your CNES Peps Username:")
 
     if not pword:
-        pword = getpass.getpass(' Your CNES Peps Password:')
+        pword = getpass.getpass(" Your CNES Peps Password:")
 
     # open a connection to the CNES Peps
-    base_url = 'https://peps.cnes.fr/'
+    base_url = "https://peps.cnes.fr/"
     manager = urllib.request.HTTPPasswordMgrWithDefaultRealm()
     manager.add_password(None, base_url, uname, pword)
     handler = urllib.request.HTTPBasicAuthHandler(manager)
@@ -72,8 +76,9 @@ def check_connection(uname, pword):
     """
 
     response = requests.get(
-        'https://peps.cnes.fr/rocket/#/search?view=list&maxRecords=50',
-        auth=(uname, pword), stream=True
+        "https://peps.cnes.fr/rocket/#/search?view=list&maxRecords=50",
+        auth=(uname, pword),
+        stream=True,
     )
 
     return response.status_code
@@ -98,7 +103,7 @@ def peps_download(argument_list):
     response = requests.get(url, stream=True, auth=(uname, pword))
 
     # get download size
-    total_length = int(response.headers.get('content-length', 0))
+    total_length = int(response.headers.get("content-length", 0))
 
     # define chunk_size
     chunk_size = 1024
@@ -108,12 +113,12 @@ def peps_download(argument_list):
 
         first_byte = filename.stat().st_size
         if first_byte == total_length:
-            logger.info(f'{filename.name} already downloaded.')
+            logger.info(f"{filename.name} already downloaded.")
         else:
-            logger.info(f'Continue downloading scene to: {filename.name}')
+            logger.info(f"Continue downloading scene to: {filename.name}")
 
     else:
-        logger.info(f'Downloading scene to: {filename.resolve()}')
+        logger.info(f"Downloading scene to: {filename.resolve()}")
         first_byte = 0
 
     if first_byte >= total_length:
@@ -123,22 +128,25 @@ def peps_download(argument_list):
     while zip_test is not None and zip_test <= 10:
 
         while first_byte < total_length:
-            
+
             # get byte offset for already downloaded file
             header = {"Range": f"bytes={first_byte}-{total_length}"}
             response = requests.get(
                 url, headers=header, stream=True, auth=(uname, pword)
             )
-    
+
             # actual download
             with open(filename, "ab") as file:
-    
+
                 if total_length is None:
                     file.write(response.content)
                 else:
                     pbar = tqdm.tqdm(
-                        total=total_length, initial=first_byte, unit='B',
-                        unit_scale=True, desc=' INFO: Downloading: '
+                        total=total_length,
+                        initial=first_byte,
+                        unit="B",
+                        unit_scale=True,
+                        desc=" INFO: Downloading: ",
                     )
                     for chunk in response.iter_content(chunk_size):
                         if chunk:
@@ -147,55 +155,62 @@ def peps_download(argument_list):
             pbar.close()
             # updated fileSize
             first_byte = filename.stat().st_size
-            
+
         # zipFile check
-        logger.info(
-            f'Checking the zip archive of {filename.name} for inconsistency'
-        )
+        logger.info(f"Checking the zip archive of {filename.name} for inconsistency")
 
         zip_test = h.check_zipfile(filename)
         # if it did not pass the test, remove the file
         # in the while loop it will be downlaoded again
         if zip_test is not None:
             logger.info(
-                f'{filename.name} did not pass the zip test. '
-                f'Re-downloading the full scene.')
+                f"{filename.name} did not pass the zip test. "
+                f"Re-downloading the full scene."
+            )
             filename.unlink()
             first_byte = 0
         # otherwise we change the status to True
         else:
-            logger.info(f'{filename} passed the zip test.')
-            with open(filename.with_suffix('.downloaded'), 'w') as file:
-                file.write('successfully downloaded \n')
+            logger.info(f"{filename} passed the zip test.")
+            with open(filename.with_suffix(".downloaded"), "w") as file:
+                file.write("successfully downloaded \n")
 
 
 def batch_download(inventory_df, download_dir, uname, pword, concurrent=10):
 
     from ost import Sentinel1Scene as S1Scene
-    logger.info('Getting the storage status (online/onTape) of each scene.')
-    logger.info('This may take a while.')
+
+    logger.info("Getting the storage status (online/onTape) of each scene.")
+    logger.info("This may take a while.")
 
     # this function does not just check,
     # but it already triggers the production of the S1 scene
-    inventory_df['pepsStatus'], inventory_df['pepsUrl'] = (
-        zip(*[S1Scene(product).peps_online_status(uname, pword)
-              for product in inventory_df.identifier.tolist()]))
+    inventory_df["pepsStatus"], inventory_df["pepsUrl"] = zip(
+        *[
+            S1Scene(product).peps_online_status(uname, pword)
+            for product in inventory_df.identifier.tolist()
+        ]
+    )
 
     # as long as there are any scenes left for downloading, loop
-    while len(inventory_df[inventory_df['pepsStatus'] != 'downloaded']) > 0:
+    while len(inventory_df[inventory_df["pepsStatus"] != "downloaded"]) > 0:
 
         # excluded downlaoded scenes
-        inventory_df = inventory_df[inventory_df['pepsStatus'] != 'downloaded']
+        inventory_df = inventory_df[inventory_df["pepsStatus"] != "downloaded"]
 
         # recheck for status
-        inventory_df['pepsStatus'], inventory_df['pepsUrl'] = (
-            zip(*[S1Scene(product).peps_online_status(uname, pword)
-                  for product in inventory_df.identifier.tolist()]))
+        inventory_df["pepsStatus"], inventory_df["pepsUrl"] = zip(
+            *[
+                S1Scene(product).peps_online_status(uname, pword)
+                for product in inventory_df.identifier.tolist()
+            ]
+        )
 
         # if all scenes to download are on Tape, we wait for a minute
-        if len(inventory_df[inventory_df['pepsStatus'] == 'online']) == 0:
-            logger.info('Imagery still on tape, we will wait for 1 minute '
-                        'and try again.')
+        if len(inventory_df[inventory_df["pepsStatus"] == "online"]) == 0:
+            logger.info(
+                "Imagery still on tape, we will wait for 1 minute " "and try again."
+            )
             time.sleep(60)
 
         # else we start downloading
@@ -203,9 +218,9 @@ def batch_download(inventory_df, download_dir, uname, pword, concurrent=10):
 
             # create the peps_list for parallel download
             peps_list = []
-            for index, row in (
-                    inventory_df[inventory_df['pepsStatus'] == 'online']
-                    .iterrows()):
+            for index, row in inventory_df[
+                inventory_df["pepsStatus"] == "online"
+            ].iterrows():
 
                 # get scene identifier
                 scene_id = row.identifier
@@ -214,18 +229,24 @@ def batch_download(inventory_df, download_dir, uname, pword, concurrent=10):
                 download_path = scene.download_path(download_dir, True)
                 # put all info to the peps_list for parallelised download
                 peps_list.append(
-                    [inventory_df.pepsUrl[
-                        inventory_df.identifier == scene_id].tolist()[0],
-                        download_path, uname, pword])
+                    [
+                        inventory_df.pepsUrl[
+                            inventory_df.identifier == scene_id
+                        ].tolist()[0],
+                        download_path,
+                        uname,
+                        pword,
+                    ]
+                )
 
             # parallelised download
             pool = multiprocessing.Pool(processes=concurrent)
             pool.map(peps_download, peps_list)
 
             # routine to check if the file has been downloaded
-            for index, row in (
-                    inventory_df[inventory_df['pepsStatus'] == 'online']
-                    .iterrows()):
+            for index, row in inventory_df[
+                inventory_df["pepsStatus"] == "online"
+            ].iterrows():
 
                 # get scene identifier
                 scene_id = row.identifier
@@ -233,4 +254,4 @@ def batch_download(inventory_df, download_dir, uname, pword, concurrent=10):
                 scene = S1Scene(scene_id)
                 download_path = scene.download_path(download_dir)
                 if download_path.exists():
-                    inventory_df.at[index, 'pepsStatus'] = 'downloaded'
+                    inventory_df.at[index, "pepsStatus"] = "downloaded"
diff --git a/ost/helpers/raster.py b/ost/helpers/raster.py
index e29671ff..0290546b 100644
--- a/ost/helpers/raster.py
+++ b/ost/helpers/raster.py
@@ -11,14 +11,7 @@
 from datetime import datetime
 
 from godale._concurrent import Executor
-
-try:
-    import gdal
-except ModuleNotFoundError as e:
-    try:
-        from osgeo import gdal
-    except ModuleNotFoundError:
-        raise e
+from osgeo import gdal
 
 import fiona
 import imageio
@@ -26,13 +19,13 @@
 import rasterio as rio
 import rasterio.mask
 from rasterio.features import shapes
-from scipy.interpolate import griddata, LinearNDInterpolator
+from scipy.interpolate import LinearNDInterpolator
 from shapely.geometry import shape, MultiPolygon
 
 from ost.helpers import helpers as h
 
 
-def polygonize_ls(infile, outfile, driver='GeoJSON'):
+def polygonize_ls(infile, outfile, driver="GeoJSON"):
 
     with rio.open(infile) as src:
         image = src.read(1)
@@ -41,24 +34,16 @@ def polygonize_ls(infile, outfile, driver='GeoJSON'):
     mask = image == 1
 
     results = (
-            {
-                'properties': {'raster_val': v},
-                'geometry': s
-            }
-            for i, (s, v)
-            in enumerate(
-                shapes(image, mask=mask, transform=src.transform)
-            )
-        )
+        {"properties": {"raster_val": v}, "geometry": s}
+        for i, (s, v) in enumerate(shapes(image, mask=mask, transform=src.transform))
+    )
 
     with fiona.open(
-            outfile, 'w',
-            driver=driver,
-            crs=pyproj.Proj(src.crs).srs,
-            schema={
-                'properties': [('raster_val', 'int')],
-                'geometry': 'Polygon'
-            }
+        outfile,
+        "w",
+        driver=driver,
+        crs=pyproj.Proj(src.crs).srs,
+        schema={"properties": [("raster_val", "int")], "geometry": "Polygon"},
     ) as dst:
         dst.writerecords(results)
 
@@ -72,23 +57,23 @@ def _closure_procedure(geojson_shape, buffer):
     """
 
     # close islands
-    s = shape(geojson_shape).buffer(
-        -buffer, 1, join_style=2
-    ).buffer(
-        buffer, 1, join_style=2
+    s = (
+        shape(geojson_shape)
+        .buffer(-buffer, 1, join_style=2)
+        .buffer(buffer, 1, join_style=2)
     )
 
     # do negative buffering to reduce border issues due to resampling
     s = s.buffer(buffer, 1, join_style=2)
 
     # upcast to MultiPolygon
-    if s.geom_type == 'Polygon':
+    if s.geom_type == "Polygon":
         s = MultiPolygon([s])
 
     return s.__geo_interface__
 
 
-def polygonize_bounds(infile, outfile, mask_value=1, driver='GeoJSON'):
+def polygonize_bounds(infile, outfile, mask_value=1, driver="GeoJSON"):
     """Polygonize a raster mask based on a mask value
 
     :param infile:
@@ -114,28 +99,25 @@ def polygonize_bounds(infile, outfile, mask_value=1, driver='GeoJSON'):
 
         results = (
             {
-                'properties': {'raster_val': v},
-                'geometry': _closure_procedure(s, neg_buffer)
+                "properties": {"raster_val": v},
+                "geometry": _closure_procedure(s, neg_buffer),
             }
-            for i, (s, v)
-            in enumerate(
+            for i, (s, v) in enumerate(
                 shapes(image, mask=mask, transform=src.transform)
             )
         )
 
         with fiona.open(
-                outfile, 'w',
-                driver=driver,
-                crs=pyproj.Proj(src.crs).srs,
-                schema={
-                    'properties': [('raster_val', 'int')],
-                    'geometry': 'MultiPolygon'
-                }
+            outfile,
+            "w",
+            driver=driver,
+            crs=pyproj.Proj(src.crs).srs,
+            schema={"properties": [("raster_val", "int")], "geometry": "MultiPolygon"},
         ) as dst:
             dst.writerecords(results)
 
 
-def outline(infile, outfile, ndv=0, less_then=False, driver='GeoJSON'):
+def outline(infile, outfile, ndv=0, less_then=False, driver="GeoJSON"):
     """Generates a vector file with the valid areas of a raster file
 
     :param infile: input raster file
@@ -152,14 +134,14 @@ def outline(infile, outfile, ndv=0, less_then=False, driver='GeoJSON'):
         meta = src.meta
 
         # update driver, datatype and reduced band count
-        meta.update(driver='GTiff', dtype='uint8', count=1)
+        meta.update(driver="GTiff", dtype="uint8", count=1)
 
         # we update the meta for more efficient looping due to
         # hardcoded vrt blocksizes
         meta.update(blockxsize=src.shape[1], blockysize=1)
 
         # create outfiles
-        with rio.open(outfile.with_suffix('.tif'), 'w', **meta) as out_min:
+        with rio.open(outfile.with_suffix(".tif"), "w", **meta) as out_min:
 
             # loop through blocks
             for _, window in out_min.block_windows(1):
@@ -182,8 +164,8 @@ def outline(infile, outfile, ndv=0, less_then=False, driver='GeoJSON'):
                 out_min.write(np.uint8(min_array), window=window, indexes=1)
 
     # now let's polygonize
-    polygonize_bounds(outfile.with_suffix('.tif'), outfile, driver=driver)
-    outfile.with_suffix('.tif').unlink()
+    polygonize_bounds(outfile.with_suffix(".tif"), outfile, driver=driver)
+    outfile.with_suffix(".tif").unlink()
 
 
 def image_bounds(data_dir):
@@ -195,20 +177,20 @@ def image_bounds(data_dir):
     :return:
     """
     filelist = []
-    for file in data_dir.glob('*img'):
+    for file in data_dir.glob("*img"):
         filelist.append(str(file))
 
-        temp_extent = data_dir.joinpath(f'{data_dir.name}_bounds.vrt')
+        temp_extent = data_dir / f"{data_dir.name}_bounds.vrt"
         # build vrt stack from all scenes
         gdal.BuildVRT(
             str(temp_extent),
             filelist,
-            options=gdal.BuildVRTOptions(srcNodata=0, separate=True)
+            options=gdal.BuildVRTOptions(srcNodata=0, separate=True),
         )
 
-        file_id = '_'.join(data_dir.name.split('_')[:2])
-        outline(temp_extent, data_dir.joinpath(f'{file_id}_bounds.json'))
-        data_dir.joinpath(f'{data_dir.name}_bounds.vrt').unlink()
+        file_id = "_".join(data_dir.name.split("_")[:2])
+        outline(temp_extent, data_dir / f"{file_id}_bounds.json")
+        (data_dir / f"{data_dir.name}_bounds.vrt").unlink()
 
 
 # convert power to dB
@@ -220,9 +202,8 @@ def convert_to_db(pow_array):
     """
 
     import warnings
-    warnings.filterwarnings(
-        'ignore', 'invalid value encountered', RuntimeWarning
-    )
+
+    warnings.filterwarnings("ignore", "invalid value encountered", RuntimeWarning)
 
     # assure all values are positive (strangely that's not always the case)
     pow_array[pow_array < 0] = 0.0000001
@@ -246,18 +227,17 @@ def scale_to_int(float_array, min_value, max_value, data_type):
     """
 
     import warnings
-    warnings.filterwarnings(
-        'ignore', 'invalid value encountered', RuntimeWarning
-    )
+
+    warnings.filterwarnings("ignore", "invalid value encountered", RuntimeWarning)
 
     # set output min and max
-    display_min = 1.
-    if data_type == 'uint8':
-        display_max = 255.
-    elif data_type == 'uint16':
-        display_max = 65535.
+    display_min = 1.0
+    if data_type == "uint8":
+        display_max = 255.0
+    elif data_type == "uint16":
+        display_max = 65535.0
     else:
-        raise ValueError('Datatype should be either uint8 or uint16.')
+        raise ValueError("Datatype should be either uint8 or uint16.")
 
     # calculate stretch parameters a and x
     a = min_value - ((max_value - min_value) / (display_max - display_min))
@@ -283,18 +263,18 @@ def rescale_to_float(int_array, data_type):
     """
 
     # convert to float and turn 0s to nan
-    int_array = int_array.astype('float32')
+    int_array = int_array.astype("float32")
     int_array[int_array == 0] = np.nan
 
     # calculate conversion parameters
-    if data_type == 'uint8':
-        a = np.divide(35., 254.)
-        b = np.subtract(-30., a)
-    elif data_type == 'uint16':
-        a = np.divide(35., 65535.)
-        b = np.subtract(-30., a)
+    if data_type == "uint8":
+        a = np.divide(35.0, 254.0)
+        b = np.subtract(-30.0, a)
+    elif data_type == "uint16":
+        a = np.divide(35.0, 65535.0)
+        b = np.subtract(-30.0, a)
     else:
-        raise TypeError('Unknown datatype')
+        raise TypeError("Unknown datatype")
 
     # apply stretch
     return np.add(np.multiply(int_array, a), b)
@@ -306,41 +286,39 @@ def fill_internal_nans(array):
     :param array:
     :return:
     """
-    print('a')
-    a = array[0].astype('float32')
+    print("a")
+    a = array[0].astype("float32")
     shape = a.shape
     a[a == 0] = np.nan
     x, y = np.indices(shape)
-    print('b')
+    print("b")
     interp = np.array(a)
-    print('c')
-    #interp[np.isnan(interp)] = griddata(
+    print("c")
+    # interp[np.isnan(interp)] = griddata(
     #    (x[~np.isnan(a)], y[~np.isnan(a)]),  # points we know
     #    a[~np.isnan(a)],  # values we know
     #    (x[np.isnan(a)], y[np.isnan(a)]),
     #    method='cubic'
-    #)
+    # )
 
     interp[np.isnan(interp)] = LinearNDInterpolator(
-        (a[~np.isnan(a)], 2),
-        a[~np.isnan(a)],
-        (x[np.isnan(a)], y[np.isnan(a)])
+        (a[~np.isnan(a)], 2), a[~np.isnan(a)], (x[np.isnan(a)], y[np.isnan(a)])
     )
-    print('d')
+    print("d")
     return interp[np.newaxis, :]
 
 
 def mask_by_shape(
-        infile,
-        outfile,
-        vector,
-        to_db=False,
-        datatype='float32',
-        rescale=True,
-        min_value=0.000001,
-        max_value=1,
-        ndv=None,
-        description=True
+    infile,
+    outfile,
+    vector,
+    to_db=False,
+    datatype="float32",
+    rescale=True,
+    min_value=0.000001,
+    max_value=1,
+    ndv=None,
+    description=True,
 ):
     """Mask a raster layer with a vector file (including data conversions)
 
@@ -358,9 +336,8 @@ def mask_by_shape(
     """
 
     # import vector geometries
-    with fiona.open(vector, 'r') as file:
-        features = [feature['geometry'] for feature in file
-                    if feature['geometry']]
+    with fiona.open(vector, "r") as file:
+        features = [feature["geometry"] for feature in file if feature["geometry"]]
 
     # import raster file
     with rio.open(infile) as src:
@@ -371,7 +348,7 @@ def mask_by_shape(
     # unmask array
     out_image = out_image.data
 
-    if out_image.dtype == 'float32':
+    if out_image.dtype == "float32":
         out_image[out_image == 0] = np.nan
 
     # if to decibel should be applied
@@ -379,28 +356,35 @@ def mask_by_shape(
         out_image = convert_to_db(out_image)
 
     # if rescaling to integer should be applied
-    if rescale and datatype == 'uint8':
-        out_image = scale_to_int(out_image, min_value, max_value, 'uint8')
-    elif rescale and datatype == 'uint16':
-        out_image = scale_to_int(out_image, min_value, max_value, 'uint16')
+    if rescale and datatype == "uint8":
+        out_image = scale_to_int(out_image, min_value, max_value, "uint8")
+    elif rescale and datatype == "uint16":
+        out_image = scale_to_int(out_image, min_value, max_value, "uint16")
 
     # update metadata for outfile
     out_meta.update(
-        {'driver': 'GTiff', 'height': out_image.shape[1],
-         'width': out_image.shape[2], 'transform': out_transform,
-         'nodata': ndv, 'dtype': datatype, 'tiled': True,
-         'blockxsize': 128, 'blockysize': 128}
+        {
+            "driver": "GTiff",
+            "height": out_image.shape[1],
+            "width": out_image.shape[2],
+            "transform": out_transform,
+            "nodata": ndv,
+            "dtype": datatype,
+            "tiled": True,
+            "blockxsize": 128,
+            "blockysize": 128,
+        }
     )
 
     # check that block size is in range of image (for very small subsets)
-    if out_meta['blockysize'] > out_image.shape[1]:
-        del out_meta['blockysize']
+    if out_meta["blockysize"] > out_image.shape[1]:
+        del out_meta["blockysize"]
 
-    if out_meta['blockxsize'] > out_image.shape[2]:
-        del out_meta['blockxsize']
+    if out_meta["blockxsize"] > out_image.shape[2]:
+        del out_meta["blockxsize"]
 
     # write output
-    with rio.open(outfile, 'w', **out_meta) as dest:
+    with rio.open(outfile, "w", **out_meta) as dest:
         dest.write(np.nan_to_num(out_image))
 
         # add some metadata to tif-file
@@ -415,33 +399,43 @@ def create_tscan_vrt(timescan_dir, config_file):
     if isinstance(config_file, dict):
         config_dict = config_file
     else:
-        config_file = open(config_file, 'r')
+        config_file = open(config_file, "r")
         config_dict = json.load(config_file)
         config_file.close()
 
-    ard_tscan = config_dict['processing']['time-scan_ARD']
+    ard_tscan = config_dict["processing"]["time-scan_ARD"]
 
     # loop through all potential products
     # a products list
-    product_list = ['bs.HH', 'bs.VV', 'bs.HV', 'bs.VH',
-                    'coh.VV', 'coh.VH', 'coh.HH', 'coh.HV', 
-                    'pol.Entropy', 'pol.Anisotropy', 'pol.Alpha']
+    product_list = [
+        "bs.HH",
+        "bs.VV",
+        "bs.HV",
+        "bs.VH",
+        "coh.VV",
+        "coh.VH",
+        "coh.HH",
+        "coh.HV",
+        "pol.Entropy",
+        "pol.Anisotropy",
+        "pol.Alpha",
+    ]
 
-    metrics = ard_tscan['metrics']
-    if 'percentiles' in metrics:
-        metrics.remove('percentiles')
-        metrics.extend(['p95', 'p5'])
+    metrics = ard_tscan["metrics"]
+    if "percentiles" in metrics:
+        metrics.remove("percentiles")
+        metrics.extend(["p95", "p5"])
 
-    if 'harmonics' in metrics:
-        metrics.remove('harmonics')
-        metrics.extend(['amplitude', 'phase', 'residuals'])
+    if "harmonics" in metrics:
+        metrics.remove("harmonics")
+        metrics.extend(["amplitude", "phase", "residuals"])
 
     i, outfiles = 0, []
     iteration = itertools.product(product_list, metrics)
     for product, metric in iteration:
 
         # get file and add number for outfile
-        infile = timescan_dir.joinpath(f'{product}.{metric}.tif')
+        infile = timescan_dir / f"{product}.{metric}.tif"
 
         # if there is no file sto the iteration
         if not infile.exists():
@@ -449,7 +443,7 @@ def create_tscan_vrt(timescan_dir, config_file):
 
         i += 1
         # create namespace for output file and add to list for vrt creation
-        outfile = timescan_dir.joinpath(f'{i:02d}.{product}.{metric}.tif')
+        outfile = timescan_dir / f"{i:02d}.{product}.{metric}.tif"
         outfiles.append(str(outfile))
 
         # otherwise rename the file
@@ -457,11 +451,11 @@ def create_tscan_vrt(timescan_dir, config_file):
 
     # build vrt
     gdal.BuildVRT(
-        str(timescan_dir.joinpath('Timescan.vrt')),
+        str(timescan_dir / "Timescan.vrt"),
         outfiles,
-        options=gdal.BuildVRTOptions(srcNodata=0, separate=True)
-     )
-        
+        options=gdal.BuildVRTOptions(srcNodata=0, separate=True),
+    )
+
 
 def norm(array, percentile=False):
     """Normalize array by its min/max or 2- and 98 percentile
@@ -471,13 +465,11 @@ def norm(array, percentile=False):
     :return:
     """
     if percentile:
-        array_min, array_max = (
-            np.percentile(array, 2), np.percentile(array, 98)
-        )
+        array_min, array_max = (np.percentile(array, 2), np.percentile(array, 98))
     else:
         array_min, array_max = np.nanmin(array), np.nanmax(array)
-        
-    return (array - array_min)/(array_max - array_min)
+
+    return (array - array_min) / (array_max - array_min)
 
 
 def visualise_rgb(filepath, shrink_factor=25):
@@ -497,9 +489,9 @@ def visualise_rgb(filepath, shrink_factor=25):
             out_shape=(
                 src.count,
                 int(src.height / shrink_factor),
-                int(src.width / shrink_factor)
+                int(src.width / shrink_factor),
             ),
-            resampling=5    # 5 = average
+            resampling=5,  # 5 = average
         )
 
     # convert 0 to nans
@@ -507,14 +499,14 @@ def visualise_rgb(filepath, shrink_factor=25):
 
     if src.count == 3:
         # normalise RGB bands
-        red = norm(scale_to_int(array[0], -18, 0, 'uint8'))
-        green = norm(scale_to_int(array[1], -25, -5, 'uint8'))
-        blue = norm(scale_to_int(array[2], 1, 15, 'uint8'))
+        red = norm(scale_to_int(array[0], -18, 0, "uint8"))
+        green = norm(scale_to_int(array[1], -25, -5, "uint8"))
+        blue = norm(scale_to_int(array[2], 1, 15, "uint8"))
 
     else:
-        red = norm(scale_to_int(array[0], -18, 0, 'uint8'))
-        green = norm(scale_to_int(array[0], -18, 0, 'uint8'))
-        blue = norm(scale_to_int(array[0], -18, 0, 'uint8'))
+        red = norm(scale_to_int(array[0], -18, 0, "uint8"))
+        green = norm(scale_to_int(array[0], -18, 0, "uint8"))
+        blue = norm(scale_to_int(array[0], -18, 0, "uint8"))
 
     # stack image
     img = np.dstack((red, green, blue))
@@ -522,75 +514,95 @@ def visualise_rgb(filepath, shrink_factor=25):
     plt.imshow(img)
 
 
-def get_min(file, dtype='float32'):
+def get_min(file, dtype="float32"):
 
     mins = {
-        'bs.VV': -20, 'bs.VH': -25, 'bs.HH': -20, 'bs.HV': -25, 'bs.ratio': 1,
-        'coh.VV': 0.1, 'coh.VH': 0.1,
-        'pol.Alpha': 60, 'pol.Entropy': 0.1, 'pol.Anisotropy': 0.1,
-        'coh_IW1_VV': 0.1, 'coh_IW2_VV': 0.1, 'coh_IW3_VV': 0.1,
-        'coh_IW1_VH': 0.1, 'coh_IW2_VH': 0.1, 'coh_IW3_VH': 0.1
+        "bs.VV": -20,
+        "bs.VH": -25,
+        "bs.HH": -20,
+        "bs.HV": -25,
+        "bs.ratio": 1,
+        "coh.VV": 0.1,
+        "coh.VH": 0.1,
+        "pol.Alpha": 60,
+        "pol.Entropy": 0.1,
+        "pol.Anisotropy": 0.1,
+        "coh_IW1_VV": 0.1,
+        "coh_IW2_VV": 0.1,
+        "coh_IW3_VV": 0.1,
+        "coh_IW1_VH": 0.1,
+        "coh_IW2_VH": 0.1,
+        "coh_IW3_VH": 0.1,
     }
 
-    if dtype == 'uint16':
+    if dtype == "uint16":
         for item, value in mins.items():
-            mins[item] = scale_to_int(np.array(value), -30, 5, 'uint16')
-    elif dtype == 'uint8':
+            mins[item] = scale_to_int(np.array(value), -30, 5, "uint16")
+    elif dtype == "uint8":
         for item, value in mins.items():
-            mins[item] = scale_to_int(np.array(value), -30, 5, 'uint8')
+            mins[item] = scale_to_int(np.array(value), -30, 5, "uint8")
 
     for key, items in mins.items():
         if key in file:
             return items
 
 
-def get_max(file, dtype='float32'):
+def get_max(file, dtype="float32"):
 
     maxs = {
-        'bs.VV': 0, 'bs.VH': -12, 'bs.HH': 0, 'bs.HV': -5, 'bs.ratio': 15,
-        'coh.VV': 0.8, 'coh.VH': 0.75,
-        'pol.Alpha': 80, 'pol.Entropy': 0.8, 'pol.Anisotropy': 0.8,
-        'coh_IW1_VV': 0.8, 'coh_IW2_VV': 0.8, 'coh_IW3_VV': 0.8,
-        'coh_IW1_VH': 0.75, 'coh_IW2_VH': 0.75, 'coh_IW3_VH': 0.75
+        "bs.VV": 0,
+        "bs.VH": -12,
+        "bs.HH": 0,
+        "bs.HV": -5,
+        "bs.ratio": 15,
+        "coh.VV": 0.8,
+        "coh.VH": 0.75,
+        "pol.Alpha": 80,
+        "pol.Entropy": 0.8,
+        "pol.Anisotropy": 0.8,
+        "coh_IW1_VV": 0.8,
+        "coh_IW2_VV": 0.8,
+        "coh_IW3_VV": 0.8,
+        "coh_IW1_VH": 0.75,
+        "coh_IW2_VH": 0.75,
+        "coh_IW3_VH": 0.75,
     }
 
-    if dtype == 'uint16':
+    if dtype == "uint16":
         for item, value in maxs.items():
-            maxs[item] = scale_to_int(np.array(value), -30, 5, 'uint16')
-    elif dtype == 'uint8':
+            maxs[item] = scale_to_int(np.array(value), -30, 5, "uint16")
+    elif dtype == "uint8":
         for item, value in maxs.items():
-            maxs[item] = scale_to_int(np.array(value), -30, 5, 'uint8')
+            maxs[item] = scale_to_int(np.array(value), -30, 5, "uint8")
 
     for key, items in maxs.items():
         if key in file:
             return items
 
 
-def calc_min(band, stretch='minmax'):
+def calc_min(band, stretch="minmax"):
 
-    if stretch == 'percentile':
+    if stretch == "percentile":
         band_min = np.percentile(band, 2)
-    elif stretch == 'minmax':
+    elif stretch == "minmax":
         band_min = np.nanmin(band)
     else:
         raise ValueError(
-            'Please select one of percentile or minmax '
-            'for the stretch parameter.'
+            "Please select one of percentile or minmax " "for the stretch parameter."
         )
 
     return band_min
 
 
-def calc_max(band, stretch='minmax'):
+def calc_max(band, stretch="minmax"):
 
-    if stretch == 'percentile':
+    if stretch == "percentile":
         band_max = np.percentile(band, 98)
-    elif stretch == 'minmax':
+    elif stretch == "minmax":
         band_max = np.nanmax(band)
     else:
         raise ValueError(
-            'Please select one of percentile or minmax '
-            'for the stretch parameter.'
+            "Please select one of percentile or minmax " "for the stretch parameter."
         )
     return band_max
 
@@ -598,141 +610,142 @@ def calc_max(band, stretch='minmax'):
 def stretch_to_8bit(file, layer, dtype, aut_stretch=False):
 
     if aut_stretch:
-        min_val = calc_min(layer, 'percentile')
-        max_val = calc_max(layer, 'percentile')
+        min_val = calc_min(layer, "percentile")
+        max_val = calc_max(layer, "percentile")
     else:
         min_val = get_min(file, dtype)
         max_val = get_max(file, dtype)
 
     # if dtype == 'float32':
-    layer = layer.astype('float32')
+    layer = layer.astype("float32")
     layer[layer == 0] = np.nan
 
-    layer = scale_to_int(layer, min_val, max_val, 'uint8')
+    layer = scale_to_int(layer, min_val, max_val, "uint8")
     return np.nan_to_num(layer)
 
 
 def combine_timeseries(processing_dir, config_dict, timescan=True):
 
     # namespaces for folder
-    comb_dir = processing_dir.joinpath('combined')
+    comb_dir = processing_dir / "combined"
     if comb_dir.exists():
         h.remove_folder_content(comb_dir)
 
-    tseries_dir = comb_dir.joinpath('Timeseries')
+    tseries_dir = comb_dir / "Timeseries"
     tseries_dir.mkdir(parents=True, exist_ok=True)
 
     PRODUCT_LIST = [
-        'bs.HH', 'bs.VV', 'bs.HV', 'bs.VH',
-        'coh.VV', 'coh.VH', 'coh.HH', 'coh.HV',
-        'pol.Entropy', 'pol.Anisotropy', 'pol.Alpha'
+        "bs.HH",
+        "bs.VV",
+        "bs.HV",
+        "bs.VH",
+        "coh.VV",
+        "coh.VH",
+        "coh.HH",
+        "coh.HV",
+        "pol.Entropy",
+        "pol.Anisotropy",
+        "pol.Alpha",
     ]
 
     out_files, iter_list = [], []
     for product_type in PRODUCT_LIST:
 
-        filelist = list(
-            processing_dir.glob(f'*/Timeseries/*{product_type}.tif')
-        )
+        filelist = list(processing_dir.glob(f"*/Timeseries/*{product_type}.tif"))
 
         if len(filelist) > 1:
-            datelist = sorted([file.name.split('.')[1] for file in filelist])
+            datelist = sorted([file.name.split(".")[1] for file in filelist])
 
             for i, date in enumerate(datelist):
-                file = list(processing_dir.glob(
-                    f'*/Timeseries/*{date}*{product_type}.tif')
-                )
-                outfile = tseries_dir.joinpath(
-                    f'{i+1:02d}.{date}.{product_type}.tif'
+                file = list(
+                    processing_dir.glob(f"*/Timeseries/*{date}*{product_type}.tif")
                 )
+                outfile = tseries_dir / f"{i+1:02d}.{date}.{product_type}.tif"
 
                 shutil.copy(file[0], str(outfile))
                 out_files.append(str(outfile))
 
             vrt_options = gdal.BuildVRTOptions(srcNodata=0, separate=True)
-            out_vrt = str(
-                tseries_dir.joinpath(f'Timeseries.{product_type}.vrt'))
-            gdal.BuildVRT(
-                str(out_vrt),
-                out_files,
-                options=vrt_options
-            )
+            out_vrt = str(tseries_dir / f"Timeseries.{product_type}.vrt")
+            gdal.BuildVRT(str(out_vrt), out_files, options=vrt_options)
 
             if timescan:
                 from ost.generic import timescan as ts
-                ard = config_dict['processing']['single_ARD']
-                ard_mt = config_dict['processing']['time-series_ARD']
-                ard_tscan = config_dict['processing']['time-scan_ARD']
+
+                ard = config_dict["processing"]["single_ARD"]
+                ard_mt = config_dict["processing"]["time-series_ARD"]
+                ard_tscan = config_dict["processing"]["time-scan_ARD"]
 
                 # get the db scaling right
-                to_db = ard['to_db']
-                if ard['to_db'] or ard_mt['to_db']:
+                to_db = ard["to_db"]
+                if ard["to_db"] or ard_mt["to_db"]:
                     to_db = True
 
-                dtype_conversion = True if ard_mt[
-                                               'dtype_output'] != 'float32' else False
+                dtype_conversion = (
+                    True if ard_mt["dtype_output"] != "float32" else False
+                )
 
-                tscan_dir = comb_dir.joinpath('Timescan')
+                tscan_dir = comb_dir / "Timescan"
                 tscan_dir.mkdir(parents=True, exist_ok=True)
 
                 # get timeseries vrt
-                time_series = tseries_dir.joinpath(
-                    f'Timeseries.{product_type}.vrt'
-                )
+                time_series = tseries_dir / f"Timeseries.{product_type}.vrt"
 
                 if not time_series.exists():
                     continue
 
                 # create a datelist for harmonics
                 scene_list = [
-                    str(file) for file in
-                    list(tseries_dir.glob(f'*{product_type}.tif'))
+                    str(file) for file in list(tseries_dir.glob(f"*{product_type}.tif"))
                 ]
 
                 # create a datelist for harmonics calculation
                 datelist = []
                 for file in sorted(scene_list):
-                    datelist.append(os.path.basename(file).split('.')[1])
+                    datelist.append(os.path.basename(file).split(".")[1])
 
                 # define timescan prefix
-                timescan_prefix = tscan_dir.joinpath(f'{product_type}')
-
-                iter_list.append([
-                    time_series, timescan_prefix, ard_tscan['metrics'],
-                    dtype_conversion, to_db, ard_tscan['remove_outliers'],
-                    datelist
-                ])
+                timescan_prefix = tscan_dir / f"{product_type}"
+
+                iter_list.append(
+                    [
+                        time_series,
+                        timescan_prefix,
+                        ard_tscan["metrics"],
+                        dtype_conversion,
+                        to_db,
+                        ard_tscan["remove_outliers"],
+                        datelist,
+                    ]
+                )
 
     if timescan:
         # now we run with godale, which works also with 1 worker
         executor = Executor(
-            executor=config_dict['executor_type'],
-            max_workers=config_dict['max_workers']
+            executor=config_dict["executor_type"],
+            max_workers=config_dict["max_workers"],
         )
 
         # run timescan creation
-        out_dict = {'track': [], 'prefix': [], 'metrics': [], 'error': []}
-        for task in executor.as_completed(
-                func=ts.gd_mt_metrics,
-                iterable=iter_list
-        ):
+        out_dict = {"track": [], "prefix": [], "metrics": [], "error": []}
+        for task in executor.as_completed(func=ts.gd_mt_metrics, iterable=iter_list):
             burst, prefix, metrics, error = task.result()
-            out_dict['track'].append(burst)
-            out_dict['prefix'].append(prefix)
-            out_dict['metrics'].append(metrics)
-            out_dict['error'].append(error)
+            out_dict["track"].append(burst)
+            out_dict["prefix"].append(prefix)
+            out_dict["metrics"].append(metrics)
+            out_dict["error"].append(error)
 
         create_tscan_vrt(tscan_dir, config_dict)
 
 
 def create_rgb_jpeg(
-        filelist,
-        outfile=None,
-        shrink_factor=1,
-        resampling_factor=5,
-        plot=False,
-        date=None,
-        filetype=None
+    filelist,
+    outfile=None,
+    shrink_factor=1,
+    resampling_factor=5,
+    plot=False,
+    date=None,
+    filetype=None,
 ):
     """
 
@@ -755,7 +768,7 @@ def create_rgb_jpeg(
 
         # get metadata
         out_meta = src.meta.copy()
-        dtype = src.meta['dtype']
+        dtype = src.meta["dtype"]
 
         # !!!assure that dimensions match ####
         new_height = int(src.height / shrink_factor)
@@ -764,29 +777,29 @@ def create_rgb_jpeg(
         count = 1
 
         layer1 = src.read(
-            out_shape=(src.count, new_height, new_width),
-            resampling=resampling_factor
+            out_shape=(src.count, new_height, new_width), resampling=resampling_factor
         )[0]
 
     if len(filelist) > 1:
         with rio.open(filelist[1]) as src:
             layer2 = src.read(
                 out_shape=(src.count, new_height, new_width),
-                resampling=resampling_factor
+                resampling=resampling_factor,
             )[0]
             count = 3
 
     if len(filelist) == 2:  # that should be the BS ratio case
 
-        if dtype == 'float32':
-            layer3 = scale_to_int(np.subtract(layer1, layer2), 1, 15, 'uint8')
+        if dtype == "float32":
+            layer3 = scale_to_int(np.subtract(layer1, layer2), 1, 15, "uint8")
         else:
             layer3 = scale_to_int(
                 np.subtract(
-                    rescale_to_float(layer1, dtype),
-                    rescale_to_float(layer2, dtype)
+                    rescale_to_float(layer1, dtype), rescale_to_float(layer2, dtype)
                 ),
-                1, 15, 'uint8'
+                1,
+                15,
+                "uint8",
             )
 
     elif len(filelist) == 3:
@@ -794,20 +807,16 @@ def create_rgb_jpeg(
         with rio.open(filelist[2]) as src:
             layer3 = src.read(
                 out_shape=(src.count, new_height, new_width),
-                resampling=resampling_factor
+                resampling=resampling_factor,
             )[0]
 
             layer3 = stretch_to_8bit(filelist[2], layer3, dtype)
 
     elif len(filelist) > 3:
-        return RuntimeError(
-            'Not more than 3 bands allowed for creation of RGB file'
-        )
+        return RuntimeError("Not more than 3 bands allowed for creation of RGB file")
 
     # create empty array
-    arr = np.zeros(
-        (int(out_meta['height']), int(out_meta['width']), int(count))
-    )
+    arr = np.zeros((int(out_meta["height"]), int(out_meta["width"]), int(count)))
 
     # fill array with layers
     arr[:, :, 0] = stretch_to_8bit(filelist[0], layer1, dtype)
@@ -819,12 +828,12 @@ def create_rgb_jpeg(
     arr = np.transpose(arr, [2, 0, 1])
 
     # update outfile's metadata
-    filetype = filetype if filetype else 'JPEG'
-    out_meta.update({'driver': filetype, 'dtype': 'uint8', 'count': count})
+    filetype = filetype if filetype else "JPEG"
+    out_meta.update({"driver": filetype, "dtype": "uint8", "count": count})
 
     if outfile:  # write array to disk
-        with rio.open(outfile, 'w', **out_meta) as out:
-            out.write(arr.astype('uint8'))
+        with rio.open(outfile, "w", **out_meta) as out:
+            out.write(arr.astype("uint8"))
 
         if date:
 
@@ -832,102 +841,100 @@ def create_rgb_jpeg(
             # for mosaic or coherence case
             if len(date) > 6:
                 try:
-                    start, end = date.split('-')
-                    string = 'Mosaic'
+                    start, end = date.split("-")
+                    string = "Mosaic"
                     try:
-                        start = start.split('_')[0]
-                        end = end.split('_')[1]
-                        string = 'Coh. Mosaic'
-                    except:
+                        start = start.split("_")[0]
+                        end = end.split("_")[1]
+                        string = "Coh. Mosaic"
+                    except Exception:
                         pass
                 except ValueError:
-                    start, end = date.split('_')
-                    string = 'Intf. Coherence'
-
-                start = datetime.strptime(start, '%y%m%d')
-                start = datetime.strftime(start, '%d.%m.%Y')
-                end = datetime.strptime(end, '%y%m%d')
-                end = datetime.strftime(end, '%d.%m.%Y')
-                date = f'{string}: {start}-{end}'
+                    start, end = date.split("_")
+                    string = "Intf. Coherence"
+
+                start = datetime.strptime(start, "%y%m%d")
+                start = datetime.strftime(start, "%d.%m.%Y")
+                end = datetime.strptime(end, "%y%m%d")
+                end = datetime.strftime(end, "%d.%m.%Y")
+                date = f"{string}: {start}-{end}"
             # for single date
             else:
-                date = datetime.strptime(date, '%y%m%d')
+                date = datetime.strptime(date, "%y%m%d")
                 date = f'Image from: {datetime.strftime(date, "%d.%m.%Y")}'
 
             # calculate label height on the basis of the image width
-            label_height = np.floor(np.divide(int(out_meta['height']), 15))
+            label_height = np.floor(np.divide(int(out_meta["height"]), 15))
 
             # create imagemagick command
             cmd = (
-                f'convert -background \'#0008\' -fill white -gravity center '
+                f"convert -background '#0008' -fill white -gravity center "
                 f'-size {out_meta["width"]}x{label_height} '
-                f'caption:\"{date}\" {outfile} +swap -gravity north '
-                f'-composite {outfile}'
+                f'caption:"{date}" {outfile} +swap -gravity north '
+                f"-composite {outfile}"
             )
             # and execute
-            h.run_command(cmd, f'{outfile}.log', elapsed=False)
+            h.run_command(cmd, f"{outfile}.log", elapsed=False)
 
     if plot:
         plt.imshow(arr)
 
 
 def create_timeseries_animation(
-        timeseries_folder,
-        product_list,
-        out_folder,
-        shrink_factor=1,
-        resampling_factor=5,
-        duration=1,
-        add_dates=False,
-        prefix=False
+    timeseries_folder,
+    product_list,
+    out_folder,
+    shrink_factor=1,
+    resampling_factor=5,
+    duration=1,
+    add_dates=False,
+    prefix=False,
 ):
 
     # get number of products
-    nr_of_products = len(
-        list(timeseries_folder.glob(f'*{product_list[0]}.tif'))
-    )
+    nr_of_products = len(list(timeseries_folder.glob(f"*{product_list[0]}.tif")))
 
     # for coherence it must be one less
-    #if 'coh.VV' in product_list or 'coh.VH' in product_list:
+    # if 'coh.VV' in product_list or 'coh.VH' in product_list:
     #    nr_of_products = nr_of_products - 1
 
     outfiles = []
     for i in range(nr_of_products):
 
         filelist = [
-            list(timeseries_folder.glob(f'{i+1:02d}.*.{product}.tif'))[0]
+            list(timeseries_folder.glob(f"{i+1:02d}.*.{product}.tif"))[0]
             for product in product_list
         ]
 
-        dates = filelist[0].name.split('.')[1]
-        
+        dates = filelist[0].name.split(".")[1]
+
         if add_dates:
             date = dates
         else:
             date = None
-        
+
         create_rgb_jpeg(
             filelist,
-            out_folder.joinpath(f'{i+1:02d}.{dates}.jpeg'),
+            out_folder / f"{i+1:02d}.{dates}.jpeg",
             shrink_factor,
             resampling_factor,
-            date=date
+            date=date,
         )
 
-        outfiles.append(out_folder.joinpath(f'{i+1:02d}.{dates}.jpeg'))
+        outfiles.append(out_folder / f"{i+1:02d}.{dates}.jpeg")
 
     # create gif
     if prefix:
-        gif_name = f'{prefix}_{product_list[0]}_ts_animation.gif'
+        gif_name = f"{prefix}_{product_list[0]}_ts_animation.gif"
     else:
-        gif_name = f'{product_list[0]}_ts_animation.gif'
+        gif_name = f"{product_list[0]}_ts_animation.gif"
     with imageio.get_writer(
-            out_folder.joinpath(gif_name), mode='I', duration=duration
+        out_folder / gif_name, mode="I", duration=duration
     ) as writer:
 
         for file in outfiles:
             image = imageio.imread(file)
             writer.append_data(image)
             file.unlink()
-            if file.with_suffix('.jpeg.aux.xml').exists():
-                file.with_suffix('.jpeg.aux.xml').unlink()
+            if file.with_suffix(".jpeg.aux.xml").exists():
+                file.with_suffix(".jpeg.aux.xml").unlink()
diff --git a/ost/helpers/scihub.py b/ost/helpers/scihub.py
index c4fa1e81..95e10100 100644
--- a/ost/helpers/scihub.py
+++ b/ost/helpers/scihub.py
@@ -27,18 +27,19 @@ def ask_credentials():
     :rtype: tuple
     """
 
-    print(' If you do not have a Copernicus Scihub user account'
-          ' go to: https://scihub.copernicus.eu and register')
-    uname = input(' Your Copernicus Scihub Username:')
-    pword = getpass.getpass(' Your Copernicus Scihub Password:')
+    print(
+        " If you do not have a Copernicus Scihub user account"
+        " go to: https://scihub.copernicus.eu and register"
+    )
+    uname = input(" Your Copernicus Scihub Username:")
+    pword = getpass.getpass(" Your Copernicus Scihub Password:")
 
     return uname, pword
 
 
-def connect(uname=None, pword=None,
-            base_url='https://apihub.copernicus.eu/apihub'):
+def connect(uname=None, pword=None, base_url="https://apihub.copernicus.eu/apihub"):
     """Generates an opener for the Copernicus apihub/dhus
-    
+
 
     :param uname: username of Copernicus' scihub
     :type uname: str
@@ -50,12 +51,14 @@ def connect(uname=None, pword=None,
     """
 
     if not uname:
-        print(' If you do not have a Copernicus Scihub user'
-              ' account go to: https://scihub.copernicus.eu')
-        uname = input(' Your Copernicus Scihub Username:')
+        print(
+            " If you do not have a Copernicus Scihub user"
+            " account go to: https://scihub.copernicus.eu"
+        )
+        uname = input(" Your Copernicus Scihub Username:")
 
     if not pword:
-        pword = getpass.getpass(' Your Copernicus Scihub Password:')
+        pword = getpass.getpass(" Your Copernicus Scihub Password:")
 
     # create opener
     manager = urllib.request.HTTPPasswordMgrWithDefaultRealm()
@@ -75,16 +78,16 @@ def next_page(dom):
     :rtype: str
     """
 
-    links = dom.getElementsByTagName('link')
+    links = dom.getElementsByTagName("link")
     next_page_, this_page, last = None, None, None
 
     for link in links:
-        if link.getAttribute('rel') == 'next':
-            next_page_ = link.getAttribute('href')
-        elif link.getAttribute('rel') == 'self':
-            this_page = link.getAttribute('href')
-        elif link.getAttribute('rel') == 'last':
-            last = link.getAttribute('href')
+        if link.getAttribute("rel") == "next":
+            next_page_ = link.getAttribute("href")
+        elif link.getAttribute("rel") == "self":
+            this_page = link.getAttribute("href")
+        elif link.getAttribute("rel") == "last":
+            last = link.getAttribute("href")
 
     if last == this_page:  # we are not at the end
         next_page_ = None
@@ -101,15 +104,15 @@ def create_satellite_string(mission_id):
     """
 
     if str(1) in mission_id:
-        return f'platformname:Sentinel-1'
+        return "platformname:Sentinel-1"
     elif str(2) in mission_id:
-        return f'platformname:Sentinel-2'
+        return "platformname:Sentinel-2"
     elif str(3) in mission_id:
-        return f'platformname:Sentinel-3'
+        return "platformname:Sentinel-3"
     elif str(5) in mission_id:
-        return f'platformname:Sentinel-5'
+        return "platformname:Sentinel-5"
     else:
-        raise ValueError('No satellite with mission_id')
+        raise ValueError("No satellite with mission_id")
 
 
 def create_aoi_str(aoi):
@@ -125,20 +128,19 @@ def create_aoi_str(aoi):
     geom = loads(aoi)
 
     # dependent on the type construct the query string
-    if geom.geom_type == 'Point':
-        return f'( footprint:\"Intersects({geom.y}, {geom.x})\")'
+    if geom.geom_type == "Point":
+        return f'( footprint:"Intersects({geom.y}, {geom.x})")'
 
     else:
         # simplify geometry
         aoi_convex = geom.convex_hull
 
         # create scihub-confrom aoi string
-        return f'( footprint:\"Intersects({aoi_convex})\")'
+        return f'( footprint:"Intersects({aoi_convex})")'
 
 
 def create_toi_str(
-        start='2014-10-01',
-        end=datetime.datetime.now().strftime("%Y-%m-%d")
+    start="2014-10-01", end=datetime.datetime.now().strftime("%Y-%m-%d")
 ):
     """Convert start and end date to scihub's search url time period attribute
 
@@ -153,14 +155,12 @@ def create_toi_str(
     """
 
     # bring start and end date to query format
-    start = f'{start}T00:00:00.000Z'
-    end = f'{end}T23:59:59.999Z'
-    return (
-        f'beginPosition:[{start} TO {end}] AND endPosition:[{start} TO {end}]'
-    )
+    start = f"{start}T00:00:00.000Z"
+    end = f"{end}T23:59:59.999Z"
+    return f"beginPosition:[{start} TO {end}] AND endPosition:[{start} TO {end}]"
 
 
-def create_s1_product_specs(product_type='*', polarisation='*', beam='*'):
+def create_s1_product_specs(product_type="*", polarisation="*", beam="*"):
     """Convert Sentinel-1's product metadata to scihub's product attributes
 
     Default values for all product specifications is the wildcard
@@ -180,14 +180,13 @@ def create_s1_product_specs(product_type='*', polarisation='*', beam='*'):
 
     # bring product type, polarisation and beam to query format
     return (
-        f'producttype:{product_type} AND '
-        f'polarisationMode:{polarisation} AND '
-        f'sensoroperationalmode:{beam}'
+        f"producttype:{product_type} AND "
+        f"polarisationMode:{polarisation} AND "
+        f"sensoroperationalmode:{beam}"
     )
 
 
-def check_connection(uname, pword,
-                     base_url='https://apihub.copernicus.eu/apihub'):
+def check_connection(uname, pword, base_url="https://apihub.copernicus.eu/apihub"):
     """Check if a connection with scihub can be established
 
     :param uname:
@@ -198,8 +197,8 @@ def check_connection(uname, pword,
 
     # we use some random url for checking (also for czech mirror)
     url = (
-        f'{base_url}/odata/v1/Products('
-        '\'8f30a536-c01c-4ef4-ac74-be3378dc44c4\')/$value'
+        f"{base_url}/odata/v1/Products("
+        "'8f30a536-c01c-4ef4-ac74-be3378dc44c4')/$value"
     )
 
     response = requests.get(url, auth=(uname, pword), stream=True)
@@ -207,24 +206,24 @@ def check_connection(uname, pword,
 
 
 def s1_download_parallel(argument_list):
-    """Helper function for parallel download from scihub
-    """
+    """Helper function for parallel download from scihub"""
 
     uuid, filename, uname, pword, base_url = argument_list
     s1_download(uuid, filename, uname, pword, base_url)
 
 
-def s1_download(uuid, filename, uname, pword,
-                base_url='https://apihub.copernicus.eu/apihub'):
+def s1_download(
+    uuid, filename, uname, pword, base_url="https://apihub.copernicus.eu/apihub"
+):
     """Single scene download function for Copernicus scihub/apihub
-    
+
     :param uuid: product's uuid
     :param filename: local path for the download
     :param uname: username of Copernicus' scihub
     :param pword: password of Copernicus' scihub
     :param base_url:
 
-    :return: 
+    :return:
     """
 
     # get out the arguments
@@ -236,20 +235,20 @@ def s1_download(uuid, filename, uname, pword,
         ask_credentials()
 
     # define url
-    url = f'{base_url}/odata/v1/Products(\'{uuid}\')/$value'
+    url = f"{base_url}/odata/v1/Products('{uuid}')/$value"
 
     # get first response for file Size
     response = requests.get(url, stream=True, auth=(uname, pword))
 
     # check response
     if response.status_code == 401:
-        raise ValueError(' ERROR: Username/Password are incorrect.')
+        raise ValueError(" ERROR: Username/Password are incorrect.")
     elif response.status_code != 200:
-        print(' ERROR: Something went wrong, will try again in 30 seconds.')
+        print(" ERROR: Something went wrong, will try again in 30 seconds.")
         response.raise_for_status()
 
     # get download size
-    total_length = int(response.headers.get('content-length', 0))
+    total_length = int(response.headers.get("content-length", 0))
 
     # define chunk_size
     chunk_size = 1024
@@ -268,7 +267,7 @@ def s1_download(uuid, filename, uname, pword,
             # get byte offset for already downloaded file
             header = {"Range": f"bytes={first_byte}-{total_length}"}
 
-            logger.info(f'Downloading scene to: {filename.name}')
+            logger.info(f"Downloading scene to: {filename.name}")
             response = requests.get(
                 url, headers=header, stream=True, auth=(uname, pword)
             )
@@ -277,8 +276,11 @@ def s1_download(uuid, filename, uname, pword,
             with open(filename, "ab") as file:
 
                 pbar = tqdm.tqdm(
-                    total=total_length, initial=first_byte, unit='B',
-                    unit_scale=True, desc=' INFO: Downloading: '
+                    total=total_length,
+                    initial=first_byte,
+                    unit="B",
+                    unit_scale=True,
+                    desc=" INFO: Downloading: ",
                 )
                 for chunk in response.iter_content(chunk_size):
                     if chunk:
@@ -290,27 +292,33 @@ def s1_download(uuid, filename, uname, pword,
             first_byte = filename.stat().st_size
 
         # zipFile check
-        logger.info(f'Checking zip archive {filename.name} for inconsistency')
+        logger.info(f"Checking zip archive {filename.name} for inconsistency")
         zip_test = h.check_zipfile(filename)
 
         # if it did not pass the test, remove the file
         # in the while loop it will be downloaded again
         if zip_test is not None:
             logger.info(
-                f'{filename.name} did not pass the zip test. Re-downloading '
-                f'the full scene.'
+                f"{filename.name} did not pass the zip test. Re-downloading "
+                f"the full scene."
             )
             filename.unlink()
             first_byte = 0
         # otherwise we change the status to True
         else:
-            logger.info(f'{filename.name} passed the zip test.')
-            with open(filename.with_suffix('.downloaded'), 'w') as file:
-                file.write('successfully downloaded \n')
-
-
-def batch_download(inventory_df, download_dir, uname, pword, concurrent=2,
-                   base_url='https://apihub.copernicus.eu/apihub'):
+            logger.info(f"{filename.name} passed the zip test.")
+            with open(filename.with_suffix(".downloaded"), "w") as file:
+                file.write("successfully downloaded \n")
+
+
+def batch_download(
+    inventory_df,
+    download_dir,
+    uname,
+    pword,
+    concurrent=2,
+    base_url="https://apihub.copernicus.eu/apihub",
+):
     """Batch download Sentinel-1 on the basis of an OST inventory GeoDataFrame
 
     :param inventory_df:
@@ -323,13 +331,12 @@ def batch_download(inventory_df, download_dir, uname, pword, concurrent=2,
     :return:
     """
     from ost import Sentinel1Scene as S1Scene
-    from ost.helpers import scihub
 
     if isinstance(download_dir, str):
         download_dir = Path(download_dir)
 
     # create list of scenes
-    scenes = inventory_df['identifier'].tolist()
+    scenes = inventory_df["identifier"].tolist()
 
     check, i = False, 1
     while check is False and i <= 10:
@@ -340,31 +347,30 @@ def batch_download(inventory_df, download_dir, uname, pword, concurrent=2,
             filepath = scene.download_path(download_dir, True)
 
             try:
-                uuid = (
-                    inventory_df['uuid']
-                    [inventory_df['identifier'] == scene_id].tolist()
-                )
+                uuid = inventory_df["uuid"][
+                    inventory_df["identifier"] == scene_id
+                ].tolist()
             except KeyError:
-                uuid = [scene.scihub_uuid(
-                    connect(uname=uname, pword=pword, base_url=base_url)
-                )]
-
-            if Path(f'{filepath}.downloaded').exists():
-                logger.debug(f'{scene.scene_id} is already downloaded.')
+                uuid = [
+                    scene.scihub_uuid(
+                        connect(uname=uname, pword=pword, base_url=base_url)
+                    )
+                ]
+
+            if Path(f"{filepath}.downloaded").exists():
+                logger.debug(f"{scene.scene_id} is already downloaded.")
             else:
                 # create list objects for download
-                download_list.append(
-                    [uuid[0], filepath, uname, pword, base_url]
-                )
+                download_list.append([uuid[0], filepath, uname, pword, base_url])
 
         if download_list:
             pool = multiprocessing.Pool(processes=concurrent)
             pool.map(s1_download_parallel, download_list)
 
-        downloaded_scenes = list(download_dir.glob('**/*.downloaded'))
+        downloaded_scenes = list(download_dir.glob("**/*.downloaded"))
 
-        if len(inventory_df['identifier'].tolist()) == len(downloaded_scenes):
-            logger.info('All products are downloaded.')
+        if len(inventory_df["identifier"].tolist()) == len(downloaded_scenes):
+            logger.info("All products are downloaded.")
             check = True
         else:
             check = False
@@ -373,7 +379,7 @@ def batch_download(inventory_df, download_dir, uname, pword, concurrent=2,
                 scene = S1Scene(scene)
                 file_path = scene.download_path(download_dir)
 
-                if file_path.with_suffix('.downloaded').exists():
+                if file_path.with_suffix(".downloaded").exists():
                     scenes.remove(scene.scene_id)
 
         i += 1
diff --git a/ost/helpers/settings.py b/ost/helpers/settings.py
index fb9ff1c7..715867ff 100644
--- a/ost/helpers/settings.py
+++ b/ost/helpers/settings.py
@@ -24,9 +24,7 @@ def filter(self, record):
             return record.levelno == self.passlevel
 
 
-formatter = logging.Formatter(
-    ' %(levelname)s (%(asctime)s): %(message)s', '%H:%M:%S'
-)
+formatter = logging.Formatter(" %(levelname)s (%(asctime)s): %(message)s", "%H:%M:%S")
 stream_handler = logging.StreamHandler(sys.stdout)
 stream_handler.setFormatter(formatter)
 stream_handler.setLevel(logging.DEBUG)
@@ -51,7 +49,7 @@ def setup_logfile(logfile):
 
 
 def exception_handler(exception_type, exception, traceback):
-    print(f'{exception_type.__name__}, {exception}', file=sys.stderr)
+    print(f"{exception_type.__name__}, {exception}", file=sys.stderr)
 
 
 # ---------------------------------------------------------------
@@ -66,16 +64,17 @@ def check_value(key, value, expected_type, choices=None):
             "It should be {}.".format(key, value, str(expected_type))
         )
 
-    if key == 'metrics':
+    if key == "metrics":
         all(item in value for item in choices)
 
     elif choices:
         if value not in choices:
             raise ValueError(
                 "Configuration value for ARD parameter {} is wrong {}. "
-                "It should be one of: {}".format(key, value, choices))
+                "It should be one of: {}".format(key, value, choices)
+            )
 
-    return 'passed'
+    return "passed"
 
 
 # fully loop through dict until value
@@ -88,19 +87,21 @@ def check_ard_parameters(ard_parameters):
         if isinstance(value, dict):
             check_ard_parameters(value)
         else:
-            if key == 'dem_file':
+            if key == "dem_file":
                 if value != " ":
-                    check_value(key, value, config_check[key]['type'])
+                    check_value(key, value, config_check[key]["type"])
                     if not Path(value).exists():
-                        raise FileNotFoundError('External DEM File not found.')
+                        raise FileNotFoundError("External DEM File not found.")
             # otherwise we do the check routine
             else:
-                if config_check[key]['type'] is bool:
-                    check_value(key, value, config_check[key]['type'])
+                if config_check[key]["type"] is bool:
+                    check_value(key, value, config_check[key]["type"])
                 else:
                     check_value(
-                        key, value, config_check[key]['type'],
-                        config_check[key]['choices']
+                        key,
+                        value,
+                        config_check[key]["type"],
+                        config_check[key]["choices"],
                     )
 
 
@@ -110,32 +111,34 @@ def get_gpt():
     gpt_file = None
 
     # on Windows
-    if os.name == 'nt':
-        if Path(r'c:/Program Files/snap/bin/gpt.exe').is_file() is True:
-            gpt_file = Path(r'c:/Program Files/snap/bin/gpt.exe')
+    if os.name == "nt":
+        if Path(r"c:/Program Files/snap/bin/gpt.exe").is_file() is True:
+            gpt_file = Path(r"c:/Program Files/snap/bin/gpt.exe")
         else:
-            gpt_file = input(r' Please provide the full path to the'
-                             r' SNAP gpt command line executable'
-                             r' (e.g. C:\path\to\snap\bin\gpt.exe)')
+            gpt_file = input(
+                r" Please provide the full path to the"
+                r" SNAP gpt command line executable"
+                r" (e.g. C:\path\to\snap\bin\gpt.exe)"
+            )
             gpt_file = Path(gpt_file)
 
             if not gpt_file.exists():
-                raise FileNotFoundError('Given path to gpt does not exist.')
+                raise FileNotFoundError("Given path to gpt does not exist.")
 
     # Unix systems (Mac, Linux)
     else:
         # possible UNIX paths
         paths = [
-            Path.home().joinpath('.ost/gpt'),
-            Path.home().joinpath('snap/bin/gpt'),
-            Path.home().joinpath('programs/snap/bin/gpt'),
-            Path('/home/ost/programs/snap/bin/gpt'),
-            Path('/usr/bin/gpt'),
-            Path('/opt/snap/bin/gpt'),
-            Path('/usr/local/snap/bin/gpt'),
-            Path('/usr/local/lib/snap/bin/gpt'),
-            Path('/usr/programs/snap/bin/gpt'),
-            Path('/Applications/snap/bin/gpt')
+            Path.home() / ".ost" / "gpt",
+            Path.home() / "snap" / "bin" / "gpt",
+            Path.home() / "programs" / "snap" / "bin" / "gpt",
+            Path("/home/ost/programs/snap/bin/gpt"),
+            Path("/usr/bin/gpt"),
+            Path("/opt/snap/bin/gpt"),
+            Path("/usr/local/snap/bin/gpt"),
+            Path("/usr/local/lib/snap/bin/gpt"),
+            Path("/usr/programs/snap/bin/gpt"),
+            Path("/Applications/snap/bin/gpt"),
         ]
 
         # loop trough possible paths and see if we find it
@@ -148,32 +151,34 @@ def get_gpt():
 
     # check if we have an environmental variable that contains the path to gpt
     if not gpt_file:
-        gpt_file = os.getenv('GPT_PATH')
+        gpt_file = os.getenv("GPT_PATH")
 
     # we search with bash's which
     if not gpt_file:
         try:
-            gpt_file = Path(shutil.which('gpt'))
+            gpt_file = Path(shutil.which("gpt"))
             gpt_file.exists()
-        except:
+        except Exception:
             # we give up and ask the user
-            gpt_file = input(' Please provide the full path to the SNAP'
-                             ' gpt command line executable'
-                             ' (e.g. /path/to/snap/bin/gpt) or leave empty'
-                             ' if you just want to use the'
-                             ' OST inventory and download routines.')
+            gpt_file = input(
+                " Please provide the full path to the SNAP"
+                " gpt command line executable"
+                " (e.g. /path/to/snap/bin/gpt) or leave empty"
+                " if you just want to use the"
+                " OST inventory and download routines."
+            )
 
         if not gpt_file:
-            gpt_file = ''
+            gpt_file = ""
         elif not Path(gpt_file).exists():
-            raise FileNotFoundError('Given path to gpt does not exist.')
+            raise FileNotFoundError("Given path to gpt does not exist.")
         else:
-            # if file exists we copy to one of th epossible paths, so next time
+            # if file exists we copy to one of the possible paths, so next time
             # we will find it right away
-            Path.home().joinpath('.ost').mkdir(exist_ok=True)
-            if not (Path.home().joinpath('.ost/gpt')).exists():
-                os.symlink(gpt_file, Path.home().joinpath('.ost/gpt'))
-            gpt_file = Path.home().joinpath('.ost/gpt')
+            (Path.home() / ".ost").mkdir(exist_ok=True)
+            if not (Path.home() / ".ost" / "gpt").exists():
+                os.symlink(gpt_file, Path.home() / ".ost" / "gpt")
+            gpt_file = Path.home() / ".ost" / "gpt"
 
     return str(gpt_file)
 
@@ -182,135 +187,171 @@ def get_gpt():
 GPT_FILE = get_gpt()
 
 # set global variable for root directory of OST
-OST_ROOT = Path(importlib.util.find_spec('ost').submodule_search_locations[0])
+OST_ROOT = Path(importlib.util.find_spec("ost").submodule_search_locations[0])
 
 # set global variable for APIHUB product
-APIHUB_BASEURL = 'https://apihub.copernicus.eu/apihub/odata/v1/Products'
+APIHUB_BASEURL = "https://apihub.copernicus.eu/apihub/odata/v1/Products"
 # ---------------------------------------------------------------
 
 # ---------------------------------------------------------------
 # dummy user for tests
-HERBERT_USER = {'uname': 'herbert_thethird',
-                'pword': 'q12w34er56ty7',
-                'asf_pword': 'q12w34er56ty7WER32P'
-                }
-
-config_check = dict({
-    'image_type':  {'type': str, 'choices': ['GRD', 'SLC']},
-    'ard_type': {'type': str, 'choices':
-        ['OST-GTC', 'OST-RTC', 'Earth-Engine', 'CEOS']
-             },
-    'resolution': {'type': int, 'choices': range(10, 5000)},
-    'backscatter': {'type': bool},
-    'remove_border_noise': {'type': bool},
-    'product_type': {'type': str, 'choices':
-        ['GTC-sigma0', 'GTC-gamma0', 'RTC-gamma0']
-                     },
-    'polarisation': {'type': str, 'choices':
-        ['VV, VH, HH, HV', 'VV', 'VH', 'VV, VH', 'HH, HV', 'VV, HH']
-                     },
-    'to_db': {'type': bool},
-    'to_tif': {'type': bool},
-    'geocoding': {'type': str, 'choices': ['terrain', 'ellipsoid']},
-    'remove_speckle': {'type': bool},
-    'filter': {'type': str, 'choices':
-        ['None', 'Boxcar', 'Median', 'Frost', 'Gamma Map', 'Lee',
-         'Refined Lee', 'Lee Sigma', 'IDAN']
-               },
-    'ENL': {'type': int, 'choices': range(1, 500)},
-    'estimate_ENL': {'type': bool},
-    'sigma': {'type': float,
-              'choices': [np.round(i, 1) for i in np.arange(0.5, 1, 0.1)]},
-    'filter_x_size': {'type': int, 'choices': range(1, 100)},
-    'filter_y_size': {'type': int, 'choices': range(1, 100)},
-    'window_size': {'type': str, 'choices':
-        ['5x5', '7x7', '9x9', '11x11', '13x13', '15x15', '17x17']
-                    },
-    'target_window_size': {'type': str, 'choices': ['3x3', '5x5']},
-    'num_of_looks': {'type': int, 'choices': range(1, 4)},
-    'damping': {'type': int, 'choices': range(0, 100)},
-    'pan_size': {'type': int, 'choices': range(1, 200)},
-    'remove_pol_speckle': {'type': bool},
-    'polarimetric_filter': {'type': str, 'choices':
-        ['Box Car Filter', 'IDAN Filter',
-         'Refined Lee Filter', 'Improved Lee Sigma Filter']
-                            },
-    'filter_size': {'type': int, 'choices': range(1, 100)},
-    'search_window_size': {'type': int,
-                           'choices': [i for i in range(3, 27, 2)]},
-    'scale_size': {'type': int, 'choices': range(0, 2)},
-    'create_ls_mask': {'type': bool},
-    'dem_name': {'type': str, 'choices': ['Copernicus 30m Global DEM', 'Copernicus 90m Global DEM', 'SRTM 1Sec HGT', 'SRTM 3Sec', 'Aster 1sec GDEM', 'GETASSE30', 'External DEM']},
-    'dem_file': {'type': str},
-    'dem_nodata': {'type': int, 'choices': range(0, 66000)},
-    'dem_resampling': {'type': str,
-                       'choices':
-                           ['NEAREST_NEIGHBOUR',
-                            'BILINEAR_INTERPOLATION',
-                            'CUBIC_CONVOLUTION',
-                            'BISINC_5_POINT_INTERPOLATION',
-                            'BISINC_11_POINT_INTERPOLATION',
-                            'BISINC_21_POINT_INTERPOLATION',
-                            'BICUBIC_INTERPOLATION',
-                            'DELAUNAY_INTERPOLATION']
-                       },
-    'image_resampling': {'type': str,
-                         'choices':
-                             ['NEAREST_NEIGHBOUR',
-                              'BILINEAR_INTERPOLATION',
-                              'CUBIC_CONVOLUTION',
-                              'BISINC_5_POINT_INTERPOLATION',
-                              'BISINC_11_POINT_INTERPOLATION',
-                              'BISINC_21_POINT_INTERPOLATION',
-                              'BICUBIC_INTERPOLATION']
-                         },
-    'egm_correction': {'type': bool},
-    'out_projection': {'type': int, 'choices': range(2000, 42002)},
-    'coherence': {'type': bool},
-    'coherence_bands': {'type': str, 'choices':
-        ['VV, VH, HH, HV', 'VV', 'VH', 'VV, VH', 'HH, HV', 'VV, HH']
-                        },
-    'coherence_azimuth': {'type': int, 'choices': range(1, 100)},
-    'coherence_range': {'type': int, 'choices': range(1, 500)},
-    'production': {'type': bool},
-    'H-A-Alpha': {'type': bool},
-    'apply_ls_mask': {'type': bool},
-    'remove_mt_speckle': {'type': bool},
-    'deseasonalize': {'type': bool},
-    'dtype_output': {'type': str, 'choices': ['float32', 'uint8', 'uint16']},
-    'metrics': {'type': list, 'choices':
-        ['median', 'percentiles', 'harmonics',
-         'avg', 'max', 'min', 'std', 'cov']
-                },
-    'remove_outliers': {'type': bool},
-    'harmonization': {'type': bool},
-    'cut_to_aoi': {'type': bool}
-})
+HERBERT_USER = {
+    "uname": "herbert_thethird",
+    "pword": "q12w34er56ty7",
+    "asf_pword": "q12w34er56ty7WER32P",
+}
+
+config_check = dict(
+    {
+        "image_type": {"type": str, "choices": ["GRD", "SLC"]},
+        "ard_type": {
+            "type": str,
+            "choices": ["OST-GTC", "OST-RTC", "Earth-Engine", "CEOS"],
+        },
+        "resolution": {"type": int, "choices": range(10, 5000)},
+        "backscatter": {"type": bool},
+        "remove_border_noise": {"type": bool},
+        "product_type": {
+            "type": str,
+            "choices": ["GTC-sigma0", "GTC-gamma0", "RTC-gamma0"],
+        },
+        "polarisation": {
+            "type": str,
+            "choices": ["VV, VH, HH, HV", "VV", "VH", "VV, VH", "HH, HV", "VV, HH"],
+        },
+        "to_db": {"type": bool},
+        "to_tif": {"type": bool},
+        "geocoding": {"type": str, "choices": ["terrain", "ellipsoid"]},
+        "remove_speckle": {"type": bool},
+        "filter": {
+            "type": str,
+            "choices": [
+                "None",
+                "Boxcar",
+                "Median",
+                "Frost",
+                "Gamma Map",
+                "Lee",
+                "Refined Lee",
+                "Lee Sigma",
+                "IDAN",
+            ],
+        },
+        "ENL": {"type": int, "choices": range(1, 500)},
+        "estimate_ENL": {"type": bool},
+        "sigma": {
+            "type": float,
+            "choices": [np.round(i, 1) for i in np.arange(0.5, 1, 0.1)],
+        },
+        "filter_x_size": {"type": int, "choices": range(1, 100)},
+        "filter_y_size": {"type": int, "choices": range(1, 100)},
+        "window_size": {
+            "type": str,
+            "choices": ["5x5", "7x7", "9x9", "11x11", "13x13", "15x15", "17x17"],
+        },
+        "target_window_size": {"type": str, "choices": ["3x3", "5x5"]},
+        "num_of_looks": {"type": int, "choices": range(1, 4)},
+        "damping": {"type": int, "choices": range(0, 100)},
+        "pan_size": {"type": int, "choices": range(1, 200)},
+        "remove_pol_speckle": {"type": bool},
+        "polarimetric_filter": {
+            "type": str,
+            "choices": [
+                "Box Car Filter",
+                "IDAN Filter",
+                "Refined Lee Filter",
+                "Improved Lee Sigma Filter",
+            ],
+        },
+        "filter_size": {"type": int, "choices": range(1, 100)},
+        "search_window_size": {"type": int, "choices": [i for i in range(3, 27, 2)]},
+        "scale_size": {"type": int, "choices": range(0, 2)},
+        "create_ls_mask": {"type": bool},
+        "dem_name": {
+            "type": str,
+            "choices": [
+                "Copernicus 30m Global DEM",
+                "Copernicus 90m Global DEM",
+                "SRTM 1Sec HGT",
+                "SRTM 3Sec",
+                "Aster 1sec GDEM",
+                "GETASSE30",
+                "External DEM",
+            ],
+        },
+        "dem_file": {"type": str},
+        "dem_nodata": {"type": int, "choices": range(0, 66000)},
+        "dem_resampling": {
+            "type": str,
+            "choices": [
+                "NEAREST_NEIGHBOUR",
+                "BILINEAR_INTERPOLATION",
+                "CUBIC_CONVOLUTION",
+                "BISINC_5_POINT_INTERPOLATION",
+                "BISINC_11_POINT_INTERPOLATION",
+                "BISINC_21_POINT_INTERPOLATION",
+                "BICUBIC_INTERPOLATION",
+                "DELAUNAY_INTERPOLATION",
+            ],
+        },
+        "image_resampling": {
+            "type": str,
+            "choices": [
+                "NEAREST_NEIGHBOUR",
+                "BILINEAR_INTERPOLATION",
+                "CUBIC_CONVOLUTION",
+                "BISINC_5_POINT_INTERPOLATION",
+                "BISINC_11_POINT_INTERPOLATION",
+                "BISINC_21_POINT_INTERPOLATION",
+                "BICUBIC_INTERPOLATION",
+            ],
+        },
+        "egm_correction": {"type": bool},
+        "out_projection": {"type": int, "choices": range(2000, 42002)},
+        "coherence": {"type": bool},
+        "coherence_bands": {
+            "type": str,
+            "choices": ["VV, VH, HH, HV", "VV", "VH", "VV, VH", "HH, HV", "VV, HH"],
+        },
+        "coherence_azimuth": {"type": int, "choices": range(1, 100)},
+        "coherence_range": {"type": int, "choices": range(1, 500)},
+        "production": {"type": bool},
+        "H-A-Alpha": {"type": bool},
+        "apply_ls_mask": {"type": bool},
+        "remove_mt_speckle": {"type": bool},
+        "deseasonalize": {"type": bool},
+        "dtype_output": {"type": str, "choices": ["float32", "uint8", "uint16"]},
+        "metrics": {
+            "type": list,
+            "choices": [
+                "median",
+                "percentiles",
+                "harmonics",
+                "avg",
+                "max",
+                "min",
+                "std",
+                "cov",
+            ],
+        },
+        "remove_outliers": {"type": bool},
+        "harmonization": {"type": bool},
+        "cut_to_aoi": {"type": bool},
+    }
+)
 
 
 def generate_access_file():
     access_dict = {
-        'scihub':
-            {'un': None,
-             'pw': None
-             },
-        'asf':
-            {'un': None,
-             'pw': None
-             },
-        'peps':
-            {'un': None,
-             'pw': None
-             },
-        'onda':
-            {'un': None,
-             'pw': None
-             }
+        "scihub": {"un": None, "pw": None},
+        "asf": {"un": None, "pw": None},
+        "peps": {"un": None, "pw": None},
+        "onda": {"un": None, "pw": None},
     }
 
     # Asking for scihub
-    access_dict['scihub']['username'] = input('')
-    getpass.getpass('')
+    access_dict["scihub"]["username"] = input("")
+    getpass.getpass("")
 
     # Asking for ASF
 
diff --git a/ost/helpers/srtm.py b/ost/helpers/srtm.py
index 6794ec77..cafc4ba8 100644
--- a/ost/helpers/srtm.py
+++ b/ost/helpers/srtm.py
@@ -22,21 +22,21 @@
 
 def download_srtm_tile(url):
 
-    snap_aux = Path.home().joinpath('.snap/auxdata/dem/SRTM 1Sec HGT/')
+    snap_aux = Path.home() / ".snap" / "auxdata" / "dem" / "SRTM 1Sec HGT"
 
     if not snap_aux.exists():
         try:
             snap_aux.mkdir(parents=True, exist_ok=True)
-        except:
-            raise RuntimeError(' Snap aux folder not found')
+        except Exception:
+            raise RuntimeError(" Snap aux folder not found")
 
-    filename = snap_aux.joinpath(url.split('/')[-1])
+    filename = snap_aux / url.split("/")[-1]
 
     # get first response for file Size
     response = requests.get(url, stream=True)
 
     # get download size
-    total_length = int(response.headers.get('content-length', 0))
+    total_length = int(response.headers.get("content-length", 0))
 
     # define chunk_size
     chunk_size = 1024
@@ -56,16 +56,17 @@ def download_srtm_tile(url):
             header = {"Range": f"bytes={first_byte}-{total_length}"}
 
             # logger.info(f'Downloading scene to: {filename.name}')
-            response = requests.get(
-                url, headers=header, stream=True
-            )
+            response = requests.get(url, headers=header, stream=True)
 
             # actual download
             with open(filename, "ab") as file:
 
                 pbar = tqdm.tqdm(
-                    total=total_length, initial=first_byte, unit='B',
-                    unit_scale=True, desc=' INFO: Downloading: '
+                    total=total_length,
+                    initial=first_byte,
+                    unit="B",
+                    unit_scale=True,
+                    desc=" INFO: Downloading: ",
                 )
                 for chunk in response.iter_content(chunk_size):
                     if chunk:
@@ -77,44 +78,36 @@ def download_srtm_tile(url):
             first_byte = filename.stat().st_size
 
         # zipFile check
-        logger.info(f'Checking zip archive {filename.name} for inconsistency')
+        logger.info(f"Checking zip archive {filename.name} for inconsistency")
         zip_test = h.check_zipfile(filename)
         if zip_test is not None:
             logger.info(
-                f'{filename.name} did not pass the zip test. Re-downloading '
-                f'the full scene.'
+                f"{filename.name} did not pass the zip test. Re-downloading "
+                f"the full scene."
             )
             filename.unlink()
             first_byte = 0
         # otherwise we change the status to True
         else:
-            logger.info(f'{filename.name} passed the zip test.')
+            logger.info(f"{filename.name} passed the zip test.")
 
 
 def download_srtm(aoi):
 
-    warnings.filterwarnings(
-        'ignore', 'Geometry is in a geographic CRS', UserWarning
-    )
+    warnings.filterwarnings("ignore", "Geometry is in a geographic CRS", UserWarning)
 
-    srtm = gpd.read_file(OST_ROOT.joinpath('auxdata/srtm1sectiles.gpkg'))
+    srtm = gpd.read_file(OST_ROOT / "auxdata" / "srtm1sectiles.gpkg")
 
     aoi_gdf = vec.wkt_to_gdf(aoi)
-    aoi_gdf['geometry'] = aoi_gdf.geometry.buffer(1)
-    overlap_df = gpd.overlay(srtm, aoi_gdf, how='intersection')
+    aoi_gdf["geometry"] = aoi_gdf.geometry.buffer(1)
+    overlap_df = gpd.overlay(srtm, aoi_gdf, how="intersection")
 
     iter_list = []
     for file in overlap_df.url.values:
         iter_list.append(file)
 
     # now we run with godale, which works also with 1 worker
-    executor = Executor(
-        executor='concurrent_processes',
-        max_workers=10
-    )
-
-    for task in executor.as_completed(
-            func=download_srtm_tile,
-            iterable=iter_list
-    ):
+    executor = Executor(executor="concurrent_processes", max_workers=10)
+
+    for task in executor.as_completed(func=download_srtm_tile, iterable=iter_list):
         task.result()
diff --git a/ost/helpers/vector.py b/ost/helpers/vector.py
index 9675e4df..a27efbd9 100644
--- a/ost/helpers/vector.py
+++ b/ost/helpers/vector.py
@@ -7,23 +7,8 @@
 from pyproj.crs.coordinate_operation import AzimuthalEquidistantConversion
 import geopandas as gpd
 import logging
-
-# osgeo safe imports
-try:
-    import osr
-except ModuleNotFoundError as e:
-    try:
-        from osgeo import osr
-    except ModuleNotFoundError:
-        raise e
-
-try:
-    import ogr
-except ModuleNotFoundError as e:
-    try:
-        from osgeo import ogr
-    except ModuleNotFoundError:
-        raise e
+from osgeo import osr
+from osgeo import ogr
 
 from shapely.ops import transform
 from shapely.wkt import loads
@@ -31,8 +16,6 @@
 from shapely.errors import WKTReadingError
 from fiona import collection
 from fiona.crs import from_epsg
-# from pyproj.exceptions import CRSError as projCRSError
-# from fiona.errors import DriverError, CRSError
 
 logger = logging.getLogger(__name__)
 
@@ -62,28 +45,28 @@ def aoi_to_wkt(aoi):
         # see if aoi is an ISO3 country code
         try:
             # let's check if it is a shapely readable WKT
-            world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
-            aoi_wkt = world['geometry'][world['iso_a3'] == aoi].values[0].wkt
-            
+            world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
+            aoi_wkt = world["geometry"][world["iso_a3"] == aoi].values[0].wkt
+
         except IndexError:
             # see if it is a Geovector file
             if Path(aoi).exists():
                 try:
                     gdf = gpd.GeoDataFrame.from_file(aoi)
-                    if gdf.crs != 'epsg:4326':
+                    if gdf.crs != "epsg:4326":
                         try:
-                            gdf = gdf.geometry.to_crs('epsg:4326')
-                        except:
-                            raise ValueError('No valid OST AOI definition.')
+                            gdf = gdf.geometry.to_crs("epsg:4326")
+                        except Exception:
+                            raise ValueError("No valid OST AOI definition.")
                     # return AOI as single vector object
                     aoi_wkt = str(gdf.geometry.unary_union)
-                except:
+                except Exception:
                     # give up
-                    raise ValueError('No valid OST AOI definition.')
+                    raise ValueError("No valid OST AOI definition.")
 
             else:
                 # give up
-                raise ValueError('No valid OST AOI definition.')
+                raise ValueError("No valid OST AOI definition.")
 
     return aoi_wkt
 
@@ -95,7 +78,7 @@ def get_epsg(prjfile):
     :return:
     """
 
-    prj_file = open(prjfile, 'r')
+    prj_file = open(prjfile, "r")
     prj_txt = prj_file.read()
     srs = osr.SpatialReference()
     srs.ImportFromESRI([prj_txt])
@@ -111,18 +94,24 @@ def get_proj4(prjfile):
     :return:
     """
 
-    prj_file = open(prjfile, 'r')
+    prj_file = open(prjfile, "r")
     prj_string = prj_file.read()
 
     # Lambert error
-    if '\"Lambert_Conformal_Conic\"' in prj_string:
-
-        print(' ERROR: It seems you used an ESRI generated shapefile'
-              ' with Lambert Conformal Conic projection. ')
-        print(' This one is not compatible with Open Standard OGR/GDAL'
-              ' tools used here. ')
-        print(' Reproject your shapefile to a standard Lat/Long projection'
-              ' and try again')
+    if '"Lambert_Conformal_Conic"' in prj_string:
+
+        print(
+            " ERROR: It seems you used an ESRI generated shapefile"
+            " with Lambert Conformal Conic projection. "
+        )
+        print(
+            " This one is not compatible with Open Standard OGR/GDAL"
+            " tools used here. "
+        )
+        print(
+            " Reproject your shapefile to a standard Lat/Long projection"
+            " and try again"
+        )
         exit(1)
 
     srs = osr.SpatialReference()
@@ -138,8 +127,8 @@ def epsg_to_wkt_projection(epsg_code):
     """
 
     spatial_ref = osr.SpatialReference()
-    spatial_ref.ImportFromEPSG(epsg_code)  
-            
+    spatial_ref.ImportFromEPSG(epsg_code)
+
     return spatial_ref.ExpotToWkt()
 
 
@@ -162,12 +151,11 @@ def reproject_geometry(geom, inproj4, out_epsg):
     spatial_ref_out.ImportFromEPSG(int(out_epsg))
 
     # create the CoordinateTransformation
-    coord_transform = osr.CoordinateTransformation(spatial_ref_in,
-                                                   spatial_ref_out)
+    coord_transform = osr.CoordinateTransformation(spatial_ref_in, spatial_ref_out)
     try:
         geom.Transform(coord_transform)
-    except:
-        raise RuntimeError(' ERROR: Not able to transform the geometry')
+    except Exception:
+        raise RuntimeError(" ERROR: Not able to transform the geometry")
 
     return geom
 
@@ -186,13 +174,9 @@ def geodesic_point_buffer(lon, lat, meters, envelope=False):
         conversion=AzimuthalEquidistantConversion(float(lat), float(lon))
     )
 
-    proj_wgs84 = pyproj.Proj('EPSG:4326')
+    proj_wgs84 = pyproj.Proj("EPSG:4326")
 
-    Trans = pyproj.Transformer.from_proj(
-        proj_crs,
-        proj_wgs84,
-        always_xy=True
-    ).transform
+    Trans = pyproj.Transformer.from_proj(proj_crs, proj_wgs84, always_xy=True).transform
 
     buf = Point(0, 0).buffer(meters)  # distance in metres
 
@@ -204,9 +188,7 @@ def geodesic_point_buffer(lon, lat, meters, envelope=False):
     return geom.wkt
 
 
-def latlon_to_wkt(
-        lat, lon, buffer_degree=None, buffer_meter=None, envelope=False
-):
+def latlon_to_wkt(lat, lon, buffer_degree=None, buffer_meter=None, envelope=False):
     """A helper function to create a WKT representation of Lat/Lon pair
 
     This function takes lat and lon values and returns
@@ -232,10 +214,10 @@ def latlon_to_wkt(
     """
 
     if buffer_degree is None and buffer_meter is None:
-        aoi_wkt = f'POINT ({lon} {lat})'
+        aoi_wkt = f"POINT ({lon} {lat})"
 
     elif buffer_degree:
-        aoi_geom = loads(f'POINT ({lon} {lat})').buffer(buffer_degree)
+        aoi_geom = loads(f"POINT ({lon} {lat})").buffer(buffer_degree)
         if envelope:
             aoi_geom = aoi_geom.envelope
 
@@ -268,14 +250,14 @@ def wkt_manipulations(wkt, buffer=None, convex=False, envelope=False):
     if envelope:
         geom = geom.GetEnvelope()
         geom = ogr.CreateGeometryFromWkt(
-                    f'POLYGON (('
-                    f'{geom[1]} {geom[3]}, '
-                    f'{geom[0]} {geom[3]}, '
-                    f'{geom[0]} {geom[2]}, '
-                    f'{geom[1]} {geom[2]}, '
-                    f'{geom[1]} {geom[3]}, '
-                    f'{geom[1]} {geom[3]}'
-                    f'))'
+            f"POLYGON (("
+            f"{geom[1]} {geom[3]}, "
+            f"{geom[0]} {geom[3]}, "
+            f"{geom[0]} {geom[2]}, "
+            f"{geom[1]} {geom[2]}, "
+            f"{geom[1]} {geom[3]}, "
+            f"{geom[1]} {geom[3]}"
+            f"))"
         )
 
     return geom.ExportToWkt()
@@ -293,7 +275,7 @@ def shp_to_wkt(shapefile, buffer=None, convex=False, envelope=False):
 
     # get filepaths and proj4 string
     shpfile = os.path.abspath(shapefile)
-    prjfile = shpfile[:-4] + '.prj'
+    prjfile = shpfile[:-4] + ".prj"
     proj4 = get_proj4(prjfile)
 
     lyr_name = os.path.basename(shapefile)[:-4]
@@ -306,13 +288,12 @@ def shp_to_wkt(shapefile, buffer=None, convex=False, envelope=False):
 
     wkt = geom.ExportToWkt()
 
-    if proj4 != '+proj=longlat +datum=WGS84 +no_defs':
-        logger.info('Reprojecting AOI file to Lat/Long (WGS84)')
+    if proj4 != "+proj=longlat +datum=WGS84 +no_defs":
+        logger.info("Reprojecting AOI file to Lat/Long (WGS84)")
         wkt = reproject_geometry(wkt, proj4, 4326).ExportToWkt()
 
     # do manipulations if needed
-    wkt = wkt_manipulations(wkt, buffer=buffer, convex=convex,
-                            envelope=envelope)
+    wkt = wkt_manipulations(wkt, buffer=buffer, convex=convex, envelope=envelope)
 
     return wkt
 
@@ -328,18 +309,15 @@ def latlon_to_shp(lon, lat, shapefile):
 
     shapefile = str(shapefile)
 
-    schema = {'geometry': 'Point',
-              'properties': {'id': 'str'}}
+    schema = {"geometry": "Point", "properties": {"id": "str"}}
 
-    wkt = loads('POINT ({} {})'.format(lon, lat))
+    wkt = loads("POINT ({} {})".format(lon, lat))
 
-    with collection(shapefile, "w",
-                    crs=from_epsg(4326),
-                    driver="ESRI Shapefile",
-                    schema=schema) as output:
+    with collection(
+        shapefile, "w", crs=from_epsg(4326), driver="ESRI Shapefile", schema=schema
+    ) as output:
 
-        output.write({'geometry': mapping(wkt),
-                      'properties': {'id': '1'}})
+        output.write({"geometry": mapping(wkt), "properties": {"id": "1"}})
 
 
 def wkt_to_gdf(wkt):
@@ -353,41 +331,39 @@ def wkt_to_gdf(wkt):
     geometry = loads(wkt)
 
     # point wkt
-    if geometry.geom_type == 'Point':
-        data = {'id': ['1'], 'geometry': loads(wkt).buffer(0.05).envelope}
+    if geometry.geom_type == "Point":
+        data = {"id": ["1"], "geometry": loads(wkt).buffer(0.05).envelope}
         gdf = gpd.GeoDataFrame(data)
-    
+
     # polygon wkt
-    elif geometry.geom_type == 'Polygon':
-        data = {'id': ['1'], 'geometry': loads(wkt)}
-        gdf = gpd.GeoDataFrame(data, crs='epsg:4326')
+    elif geometry.geom_type == "Polygon":
+        data = {"id": ["1"], "geometry": loads(wkt)}
+        gdf = gpd.GeoDataFrame(data, crs="epsg:4326")
 
     # geometry collection of single multiploygon
     elif (
-            geometry.geom_type == 'GeometryCollection' and
-            len(geometry) == 1 and 'MULTIPOLYGON' in str(geometry)
+        geometry.geom_type == "GeometryCollection"
+        and len(geometry) == 1
+        and "MULTIPOLYGON" in str(geometry)
     ):
 
-        data = {'id': ['1'], 'geometry': geometry}
-        gdf = gpd.GeoDataFrame(data, crs='epsg:4326')
-        
+        data = {"id": ["1"], "geometry": geometry}
+        gdf = gpd.GeoDataFrame(data, crs="epsg:4326")
+
         ids, feats = [], []
         for i, feat in enumerate(gdf.geometry.values[0]):
             ids.append(i)
             feats.append(feat)
 
         gdf = gpd.GeoDataFrame(
-            {'id': ids, 'geometry': feats},
-            geometry='geometry',
-            crs=gdf.crs
+            {"id": ids, "geometry": feats}, geometry="geometry", crs=gdf.crs
         )
-    
+
     # geometry collection of single polygon
-    elif geometry.geom_type == 'GeometryCollection' and len(geometry) == 1:
-        
-        data = {'id': ['1'],
-                'geometry': geometry}
-        gdf = gpd.GeoDataFrame(data, crs='epsg:4326')
+    elif geometry.geom_type == "GeometryCollection" and len(geometry) == 1:
+
+        data = {"id": ["1"], "geometry": geometry}
+        gdf = gpd.GeoDataFrame(data, crs="epsg:4326")
 
     # everything else
     else:
@@ -398,18 +374,19 @@ def wkt_to_gdf(wkt):
             geoms.append(geom)
             i += 1
 
-        gdf = gpd.GeoDataFrame({'id': ids, 'geometry': geoms}, crs='epsg:4326')
-    
+        gdf = gpd.GeoDataFrame({"id": ids, "geometry": geoms}, crs="epsg:4326")
+
     return gdf
 
 
 def gdf_to_json_geometry(gdf):
-    """Function to parse features from GeoDataFrame in such a manner 
-       that rasterio wants them"""
+    """Function to parse features from GeoDataFrame in such a manner
+    that rasterio wants them"""
 
     geojson = json.loads(gdf.to_json())
-    return [feature['geometry'] for feature in geojson['features'] 
-            if feature['geometry']]
+    return [
+        feature["geometry"] for feature in geojson["features"] if feature["geometry"]
+    ]
 
 
 def exterior(infile, outfile, buffer=None):
@@ -420,7 +397,7 @@ def exterior(infile, outfile, buffer=None):
     :param buffer:
     :return:
     """
-    gdf = gpd.read_file(infile, crs='epsg:4326')
+    gdf = gpd.read_file(infile, crs="epsg:4326")
     gdf.geometry = gdf.geometry.apply(lambda row: Polygon(row.exterior))
     gdf_clean = gdf[gdf.geometry.area >= 1.0e-6]
 
@@ -430,30 +407,25 @@ def exterior(infile, outfile, buffer=None):
     # a negative buffer might polygons make disappear, so let's clean them
     gdf_clean = gdf_clean[~gdf_clean.geometry.is_empty]
 
-    gdf_clean.to_file(outfile, driver='GPKG')
+    gdf_clean.to_file(outfile, driver="GPKG")
 
 
 def difference(infile1, infile2, outfile):
 
     import warnings
-    warnings.filterwarnings(
-        'ignore', 'Geometry is in a geographic CRS', UserWarning
-    )
+
+    warnings.filterwarnings("ignore", "Geometry is in a geographic CRS", UserWarning)
 
     gdf1 = gpd.read_file(infile1)
     gdf2 = gpd.read_file(infile2)
 
-    gdf3 = gpd.overlay(gdf1, gdf2, how='difference')
+    gdf3 = gpd.overlay(gdf1, gdf2, how="difference")
 
     # remove slivers and artifacts
     gdf3 = gdf3.buffer(0)
     buffer = 0.00001
-    gdf3 = gdf3.buffer(
-        -buffer, 1, join_style=2
-    ).buffer(
-        buffer, 1, join_style=2
-    )
-    gdf3.to_file(outfile, driver='GeoJSON')
+    gdf3 = gdf3.buffer(-buffer, 1, join_style=2).buffer(buffer, 1, join_style=2)
+    gdf3.to_file(outfile, driver="GeoJSON")
 
 
 def set_subset(aoi, inventory_df):
@@ -462,10 +434,9 @@ def set_subset(aoi, inventory_df):
     aoi = loads(aoi)
 
     # burst_inventory case
-    if 'bid' in inventory_df.columns:
+    if "bid" in inventory_df.columns:
         for burst in inventory_df.bid.unique():
-            burst_geom = inventory_df.geometry[
-                inventory_df.bid == burst].unary_union
+            burst_geom = inventory_df.geometry[inventory_df.bid == burst].unary_union
             subset = True if aoi.within(burst_geom) else False
             if not subset:
                 return subset
@@ -474,7 +445,8 @@ def set_subset(aoi, inventory_df):
     else:
         for track in inventory_df.relativeorbit.unique():
             track_geom = inventory_df.geometry[
-                inventory_df.relativeorbit == track].unary_union
+                inventory_df.relativeorbit == track
+            ].unary_union
             subset = True if aoi.within(track_geom) else False
             if not subset:
                 return subset
@@ -486,19 +458,17 @@ def set_subset(aoi, inventory_df):
 def buffer_shape(infile, outfile, buffer=None):
 
     with collection(infile, "r") as in_shape:
-        schema = {'geometry': 'Polygon', 'properties': {'id': 'int'}}
+        schema = {"geometry": "Polygon", "properties": {"id": "int"}}
         crs = in_shape.crs
-        with collection(
-                outfile, "w", "ESRI Shapefile", schema, crs=crs) as output:
+        with collection(outfile, "w", "ESRI Shapefile", schema, crs=crs) as output:
 
             for i, point in enumerate(in_shape):
-                output.write({
-                    'properties': {
-                        'id': i
-                    },
-                    'geometry': mapping(
-                        shape(point['geometry']).buffer(buffer))
-                })
+                output.write(
+                    {
+                        "properties": {"id": i},
+                        "geometry": mapping(shape(point["geometry"]).buffer(buffer)),
+                    }
+                )
 
 
 def plot_inventory(aoi, inventory_df, transparency=0.05, annotate=False):
@@ -506,7 +476,7 @@ def plot_inventory(aoi, inventory_df, transparency=0.05, annotate=False):
     import matplotlib.pyplot as plt
 
     # load world borders for background
-    world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
+    world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
 
     # do the import of aoi as gdf
     aoi_gdf = wkt_to_gdf(aoi)
@@ -515,31 +485,32 @@ def plot_inventory(aoi, inventory_df, transparency=0.05, annotate=False):
     bounds = inventory_df.geometry.bounds
 
     # get world map as base
-    base = world.plot(color='lightgrey', edgecolor='white')
+    base = world.plot(color="lightgrey", edgecolor="white")
 
     # plot aoi
-    aoi_gdf.plot(ax=base, color='None', edgecolor='black')
+    aoi_gdf.plot(ax=base, color="None", edgecolor="black")
 
     # plot footprints
     inventory_df.plot(ax=base, alpha=transparency)
 
     # set bounds
-    plt.xlim([bounds.minx.min()-2, bounds.maxx.max()+2])
-    plt.ylim([bounds.miny.min()-2, bounds.maxy.max()+2])
-    plt.grid(color='grey', linestyle='-', linewidth=0.2)
+    plt.xlim([bounds.minx.min() - 2, bounds.maxx.max() + 2])
+    plt.ylim([bounds.miny.min() - 2, bounds.maxy.max() + 2])
+    plt.grid(color="grey", linestyle="-", linewidth=0.2)
     if annotate:
         import math
+
         for idx, row in inventory_df.iterrows():
             # print([row['geometry'].bounds[0],row['geometry'].bounds[3]])
-            coord = [row['geometry'].centroid.x, row['geometry'].centroid.y]
-            x1, y2, x2, y1 = row['geometry'].bounds
+            coord = [row["geometry"].centroid.x, row["geometry"].centroid.y]
+            x1, y2, x2, y1 = row["geometry"].bounds
             angle = math.degrees(math.atan2((y2 - y1), (x2 - x1)))
 
             plt.annotate(
-                s=row['bid'],
+                s=row["bid"],
                 xy=coord,
                 rotation=angle + 5,
                 size=10,
-                color='red',
-                horizontalalignment='center'
+                color="red",
+                horizontalalignment="center",
             )
diff --git a/ost/s1/burst_batch.py b/ost/s1/burst_batch.py
index 9d51b984..b4378221 100644
--- a/ost/s1/burst_batch.py
+++ b/ost/s1/burst_batch.py
@@ -29,16 +29,21 @@
 # ---------------------------------------------------
 # Global variable
 PRODUCT_LIST = [
-    'bs.HH', 'bs.VV', 'bs.HV', 'bs.VH',
-    'coh.VV', 'coh.VH', 'coh.HH', 'coh.HV',
-    'pol.Entropy', 'pol.Anisotropy', 'pol.Alpha'
+    "bs.HH",
+    "bs.VV",
+    "bs.HV",
+    "bs.VH",
+    "coh.VV",
+    "coh.VH",
+    "coh.HH",
+    "coh.HV",
+    "pol.Entropy",
+    "pol.Anisotropy",
+    "pol.Alpha",
 ]
 
 
-def bursts_to_ards(
-        burst_gdf,
-        config_file
-):
+def bursts_to_ards(burst_gdf, config_file):
     """Batch processing from single bursts to ARD format
 
     This function handles the burst processing based on a OST burst inventory
@@ -55,45 +60,51 @@ def bursts_to_ards(
     :return:
     """
 
-    print('--------------------------------------------------------------')
-    logger.info('Processing all single bursts to ARD')
-    print('--------------------------------------------------------------')
+    print("--------------------------------------------------------------")
+    logger.info("Processing all single bursts to ARD")
+    print("--------------------------------------------------------------")
 
-    logger.info('Preparing the processing pipeline. This may take a moment.')
+    logger.info("Preparing the processing pipeline. This may take a moment.")
     proc_inventory = prepare_burst_inventory(burst_gdf, config_file)
 
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        executor_type = config_dict['executor_type']
-        max_workers = config_dict['max_workers']
+        executor_type = config_dict["executor_type"]
+        max_workers = config_dict["max_workers"]
 
     # we update max_workers in case we have less snap_cpu_parallelism
     # then cpus available
-    if (
-            max_workers == 1 and
-            config_dict['snap_cpu_parallelism'] < os.cpu_count()
-    ):
-        max_workers = int(os.cpu_count() / config_dict['snap_cpu_parallelism'])
+    if max_workers == 1 and config_dict["snap_cpu_parallelism"] < os.cpu_count():
+        max_workers = int(os.cpu_count() / config_dict["snap_cpu_parallelism"])
 
     # now we run with godale, which works also with 1 worker
     out_dict = {
-        'burst': [], 'acq_date': [], 'out_bs': [], 'out_ls': [],
-        'out_pol': [], 'out_coh': [], 'error': []
+        "burst": [],
+        "acq_date": [],
+        "out_bs": [],
+        "out_ls": [],
+        "out_pol": [],
+        "out_coh": [],
+        "error": [],
     }
     executor = Executor(executor=executor_type, max_workers=max_workers)
     for task in executor.as_completed(
-            func=burst_to_ard,
-            iterable=proc_inventory.iterrows(),
-            fargs=([str(config_file), ])
+        func=burst_to_ard,
+        iterable=proc_inventory.iterrows(),
+        fargs=(
+            [
+                str(config_file),
+            ]
+        ),
     ):
         burst, date, out_bs, out_ls, out_pol, out_coh, error = task.result()
-        out_dict['burst'].append(burst)
-        out_dict['acq_date'].append(date)
-        out_dict['out_bs'].append(out_bs)
-        out_dict['out_ls'].append(out_ls)
-        out_dict['out_pol'].append(out_pol)
-        out_dict['out_coh'].append(out_coh)
-        out_dict['error'].append(error)
+        out_dict["burst"].append(burst)
+        out_dict["acq_date"].append(date)
+        out_dict["out_bs"].append(out_bs)
+        out_dict["out_ls"].append(out_ls)
+        out_dict["out_pol"].append(out_pol)
+        out_dict["out_coh"].append(out_coh)
+        out_dict["error"].append(error)
 
     return pd.DataFrame.from_dict(out_dict)
 
@@ -101,46 +112,49 @@ def bursts_to_ards(
 def _create_extents(burst_gdf, config_file):
     """Batch processing for multi-temporal Layover7Shadow mask
 
-     This function handles the organization of the
+    This function handles the organization of the
 
-     :param burst_gdf:
-     :param config_file:
-     :return:
-     """
+    :param burst_gdf:
+    :param config_file:
+    :return:
+    """
 
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        processing_dir = Path(config_dict['processing_dir'])
+        processing_dir = Path(config_dict["processing_dir"])
 
     # create extent iterable
     iter_list = []
     for burst in burst_gdf.bid.unique():
 
         # get the burst directory
-        burst_dir = processing_dir.joinpath(burst)
+        burst_dir = processing_dir / burst
 
-        list_of_extents = list(burst_dir.glob('*/*/*bounds.json'))
+        list_of_extents = list(burst_dir.glob("*/*/*bounds.json"))
 
         # if extent does not already exist, add to iterable
-        if not burst_dir.joinpath(f'{burst}.min_bounds.json').exists():
+        if not (burst_dir / f"{burst}.min_bounds.json").exists():
             iter_list.append(list_of_extents)
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=os.cpu_count()
+        executor=config_dict["executor_type"], max_workers=os.cpu_count()
     )
 
-    out_dict = {'burst': [], 'list_of_scenes': [], 'extent': []}
+    out_dict = {"burst": [], "list_of_scenes": [], "extent": []}
     for task in executor.as_completed(
-            func=ts_extent.mt_extent,
-            iterable=iter_list,
-            fargs=([str(config_file), ])
+        func=ts_extent.mt_extent,
+        iterable=iter_list,
+        fargs=(
+            [
+                str(config_file),
+            ]
+        ),
     ):
         track, list_of_scenes, extent = task.result()
-        out_dict['burst'].append(track)
-        out_dict['list_of_scenes'].append(list_of_scenes)
-        out_dict['extent'].append(extent)
+        out_dict["burst"].append(track)
+        out_dict["list_of_scenes"].append(list_of_scenes)
+        out_dict["extent"].append(extent)
 
 
 def _create_extents_old(burst_gdf, config_file):
@@ -153,38 +167,39 @@ def _create_extents_old(burst_gdf, config_file):
     :return:
     """
 
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        processing_dir = Path(config_dict['processing_dir'])
+        processing_dir = Path(config_dict["processing_dir"])
 
     # create extent iterable
     iter_list = []
     for burst in burst_gdf.bid.unique():
 
         # get the burst directory
-        burst_dir = processing_dir.joinpath(burst)
+        burst_dir = processing_dir / burst
 
         # get common burst extent
-        list_of_bursts = list(burst_dir.glob('**/*img'))
-        list_of_bursts = [
-            str(x) for x in list_of_bursts if 'layover' not in str(x)
-        ]
+        list_of_bursts = list(burst_dir.glob("**/*img"))
+        list_of_bursts = [str(x) for x in list_of_bursts if "layover" not in str(x)]
 
         # if the file does not already exist, add to iterable
-        extent = burst_dir.joinpath(f'{burst}.extent.gpkg')
+        extent = burst_dir / f"{burst}.extent.gpkg"
         if not extent.exists():
             iter_list.append(list_of_bursts)
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=config_dict['max_workers']
+        executor=config_dict["executor_type"], max_workers=config_dict["max_workers"]
     )
 
     for task in executor.as_completed(
-            func=ts_extent.mt_extent,
-            iterable=iter_list,
-            fargs=([str(config_file), ])
+        func=ts_extent.mt_extent,
+        iterable=iter_list,
+        fargs=(
+            [
+                str(config_file),
+            ]
+        ),
     ):
         task.result()
 
@@ -199,32 +214,28 @@ def _create_mt_ls_mask(burst_gdf, config_file):
     :return:
     """
     # read config file
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        processing_dir = config_dict['processing_dir']
+        processing_dir = config_dict["processing_dir"]
 
     # create layover
     iter_list = []
     for burst in burst_gdf.bid.unique():  # ***
 
         # get the burst directory
-        burst_dir = Path(processing_dir).joinpath(burst)
+        burst_dir = Path(processing_dir) / burst
 
         # get common burst extent
-        list_of_masks = list(burst_dir.glob('*/*/*_ls_mask.json'))
-        if not burst_dir.joinpath(f'{burst}.ls_mask.json').exists():
+        list_of_masks = list(burst_dir.glob("*/*/*_ls_mask.json"))
+        if not (burst_dir / f"{burst}.ls_mask.json").exists():
             iter_list.append(list_of_masks)
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=os.cpu_count()
+        executor=config_dict["executor_type"], max_workers=os.cpu_count()
     )
 
-    for task in executor.as_completed(
-            func=ts_ls_mask.mt_layover,
-            iterable=iter_list
-    ):
+    for task in executor.as_completed(func=ts_ls_mask.mt_layover, iterable=iter_list):
         task.result()
 
 
@@ -239,32 +250,28 @@ def _create_mt_ls_mask_old(burst_gdf, config_file):
     """
 
     # read config file
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        processing_dir = config_dict['processing_dir']
+        processing_dir = config_dict["processing_dir"]
 
     # create layover
     iter_list = []
     for burst in burst_gdf.bid.unique():  # ***
 
         # get the burst directory
-        burst_dir = Path(processing_dir).joinpath(burst)
+        burst_dir = Path(processing_dir) / burst
 
         # get layover scenes
-        list_of_scenes = list(burst_dir.glob('20*/*data*/*img'))
-        list_of_layover = [
-            str(x) for x in list_of_scenes if 'layover' in str(x)
-        ]
+        list_of_scenes = list(burst_dir.glob("20*/*data*/*img"))
+        list_of_layover = [str(x) for x in list_of_scenes if "layover" in str(x)]
 
         # we need to redefine the namespace of the already created extents
-        extent = burst_dir.joinpath(f'{burst}.extent.gpkg')
+        extent = burst_dir / f"{burst}.extent.gpkg"
         if not extent.exists():
-            raise FileNotFoundError(
-                f'Extent file for burst {burst} not found.'
-            )
+            raise FileNotFoundError(f"Extent file for burst {burst} not found.")
 
         # layover/shadow mask
-        out_ls = burst_dir.joinpath(f'{burst}.ls_mask.tif')
+        out_ls = burst_dir / f"{burst}.ls_mask.tif"
 
         # if the file does not already exists, then put into list to process
         if not out_ls.exists():
@@ -272,14 +279,17 @@ def _create_mt_ls_mask_old(burst_gdf, config_file):
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=config_dict['max_workers']
+        executor=config_dict["executor_type"], max_workers=config_dict["max_workers"]
     )
 
     for task in executor.as_completed(
-            func=ts_ls_mask.mt_layover,
-            iterable=iter_list,
-            fargs=([str(config_file), ])
+        func=ts_ls_mask.mt_layover,
+        iterable=iter_list,
+        fargs=(
+            [
+                str(config_file),
+            ]
+        ),
     ):
         task.result()
 
@@ -287,20 +297,24 @@ def _create_mt_ls_mask_old(burst_gdf, config_file):
 def _create_timeseries(burst_gdf, config_file):
     # we need a
     # dict_of_product_types = {'bs': 'Gamma0', 'coh': 'coh', 'pol': 'pol'}
-    list_of_product_types = {('bs', 'Gamma0'), ('bs', 'Sigma0'),
-                             ('coh', 'coh'), ('pol', 'pol')}
-    pols = ['VV', 'VH', 'HH', 'HV', 'Alpha', 'Entropy', 'Anisotropy']
+    list_of_product_types = {
+        ("bs", "Gamma0"),
+        ("bs", "Sigma0"),
+        ("coh", "coh"),
+        ("pol", "pol"),
+    }
+    pols = ["VV", "VH", "HH", "HV", "Alpha", "Entropy", "Anisotropy"]
 
     # read config file
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        processing_dir = config_dict['processing_dir']
+        processing_dir = config_dict["processing_dir"]
 
     # create iterable
     iter_list = []
     for burst in burst_gdf.bid.unique():
 
-        burst_dir = Path(processing_dir).joinpath(burst)
+        burst_dir = Path(processing_dir) / burst
 
         # for pr, pol in itertools.product(dict_of_product_types.items(), pols):
         for pr, pol in itertools.product(list_of_product_types, pols):
@@ -309,16 +323,13 @@ def _create_timeseries(burst_gdf, config_file):
             product, product_name = list(pr)
 
             # take care of H-A-Alpha naming for file search
-            if pol in ['Alpha', 'Entropy', 'Anisotropy'] and product == 'pol':
-                list_of_files = sorted(
-                    list(burst_dir.glob(f'20*/*data*/*{pol}*img')))
+            if pol in ["Alpha", "Entropy", "Anisotropy"] and product == "pol":
+                list_of_files = sorted(list(burst_dir.glob(f"20*/*data*/*{pol}*img")))
             else:
                 # see if there is actually any imagery for this
                 # combination of product and polarisation
                 list_of_files = sorted(
-                    list(burst_dir.glob(
-                        f'20*/*data*/*{product_name}*{pol}*img')
-                    )
+                    list(burst_dir.glob(f"20*/*data*/*{product_name}*{pol}*img"))
                 )
 
             if len(list_of_files) <= 1:
@@ -326,53 +337,60 @@ def _create_timeseries(burst_gdf, config_file):
 
             # create list of dims if polarisation is present
             list_of_dims = sorted(
-                str(dim) for dim in list(burst_dir.glob(f'20*/*{product}*dim'))
+                str(dim) for dim in list(burst_dir.glob(f"20*/*{product}*dim"))
             )
 
             iter_list.append([list_of_dims, burst, product, pol])
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=config_dict['max_workers']
+        executor=config_dict["executor_type"], max_workers=config_dict["max_workers"]
     )
 
     out_dict = {
-        'burst': [], 'list_of_dims': [], 'out_files': [],
-        'out_vrt': [], 'product': [], 'error': []
+        "burst": [],
+        "list_of_dims": [],
+        "out_files": [],
+        "out_vrt": [],
+        "product": [],
+        "error": [],
     }
     for task in executor.as_completed(
-            func=ard_to_ts.gd_ard_to_ts,
-            iterable=iter_list,
-            fargs=([str(config_file), ])
+        func=ard_to_ts.gd_ard_to_ts,
+        iterable=iter_list,
+        fargs=(
+            [
+                str(config_file),
+            ]
+        ),
     ):
         burst, list_of_dims, out_files, out_vrt, product, error = task.result()
-        out_dict['burst'].append(burst)
-        out_dict['list_of_dims'].append(list_of_dims)
-        out_dict['out_files'].append(out_files)
-        out_dict['out_vrt'].append(out_vrt)
-        out_dict['product'].append(product)
-        out_dict['error'].append(error)
+        out_dict["burst"].append(burst)
+        out_dict["list_of_dims"].append(list_of_dims)
+        out_dict["out_files"].append(out_files)
+        out_dict["out_vrt"].append(out_vrt)
+        out_dict["product"].append(product)
+        out_dict["error"].append(error)
 
     return pd.DataFrame.from_dict(out_dict)
 
 
 def ards_to_timeseries(burst_gdf, config_file):
-    print('--------------------------------------------------------------')
-    logger.info('Processing all burst ARDs time-series')
-    print('--------------------------------------------------------------')
+    print("--------------------------------------------------------------")
+    logger.info("Processing all burst ARDs time-series")
+    print("--------------------------------------------------------------")
 
     # load ard parameters
-    with open(config_file, 'r') as ard_file:
-        ard_params = json.load(ard_file)['processing']
-        ard = ard_params['single_ARD']
-        ard_mt = ard_params['time-series_ARD']
+    with open(config_file, "r") as ard_file:
+        ard_params = json.load(ard_file)["processing"]
+        ard = ard_params["single_ARD"]
+        ard_mt = ard_params["time-series_ARD"]
 
     # create all extents
     _create_extents(burst_gdf, config_file)
 
     # update extents in case of ls_mask
-    if ard['create_ls_mask'] or ard_mt['apply_ls_mask']:
+    if ard["create_ls_mask"] or ard_mt["apply_ls_mask"]:
         _create_mt_ls_mask(burst_gdf, config_file)
 
     # finally create time-series
@@ -384,28 +402,26 @@ def ards_to_timeseries(burst_gdf, config_file):
 # timescan part
 # --------------------
 def timeseries_to_timescan(burst_gdf, config_file):
-    """Function to create a timescan out of a OST timeseries.
+    """Function to create a timescan out of a OST timeseries."""
 
-    """
-
-    print('--------------------------------------------------------------')
-    logger.info('Processing all burst ARDs time-series to ARD timescans')
-    print('--------------------------------------------------------------')
+    print("--------------------------------------------------------------")
+    logger.info("Processing all burst ARDs time-series to ARD timescans")
+    print("--------------------------------------------------------------")
 
     # -------------------------------------
     # 1 load project config
-    with open(config_file, 'r') as ard_file:
+    with open(config_file, "r") as ard_file:
         config_dict = json.load(ard_file)
-        processing_dir = config_dict['processing_dir']
-        ard = config_dict['processing']['single_ARD']
-        ard_mt = config_dict['processing']['time-series_ARD']
-        ard_tscan = config_dict['processing']['time-scan_ARD']
+        processing_dir = config_dict["processing_dir"]
+        ard = config_dict["processing"]["single_ARD"]
+        ard_mt = config_dict["processing"]["time-series_ARD"]
+        ard_tscan = config_dict["processing"]["time-scan_ARD"]
 
     # get the db scaling right
-    to_db = True if ard['to_db'] or ard_mt['to_db'] else False
+    to_db = True if ard["to_db"] or ard_mt["to_db"] else False
 
     # get datatype right
-    dtype_conversion = True if ard_mt['dtype_output'] != 'float32' else False
+    dtype_conversion = True if ard_mt["dtype_output"] != "float32" else False
 
     # -------------------------------------
     # 2 create iterable for parallel processing
@@ -413,73 +429,76 @@ def timeseries_to_timescan(burst_gdf, config_file):
     for burst in burst_gdf.bid.unique():
 
         # get relevant directories
-        burst_dir = Path(processing_dir).joinpath(burst)
-        timescan_dir = burst_dir.joinpath('Timescan')
+        burst_dir = Path(processing_dir) / burst
+        timescan_dir = burst_dir / "Timescan"
         timescan_dir.mkdir(parents=True, exist_ok=True)
 
         for product in PRODUCT_LIST:
 
             # check if already processed
-            if timescan_dir.joinpath(f'.{product}.processed').exists():
-                logger.debug(f'Timescans for burst {burst} already processed.')
+            if (timescan_dir / f".{product}.processed").exists():
+                logger.debug(f"Timescans for burst {burst} already processed.")
                 continue
 
             # get respective timeseries
-            timeseries = burst_dir.joinpath(
-                f'Timeseries/Timeseries.{product}.vrt'
-            )
+            timeseries = burst_dir / "Timeseries" / f"Timeseries.{product}.vrt"
 
             # che if this timsereis exists ( since we go through all products
             if not timeseries.exists():
                 continue
 
             # datelist for harmonics
-            scenelist = list(burst_dir.glob(f'Timeseries/*{product}*tif'))
-            datelist = [
-                file.name.split('.')[1][:6] for file in sorted(scenelist)
-            ]
+            scenelist = list(burst_dir.glob(f"Timeseries/*{product}*tif"))
+            datelist = [file.name.split(".")[1][:6] for file in sorted(scenelist)]
 
             # define timescan prefix
-            timescan_prefix = timescan_dir.joinpath(product)
+            timescan_prefix = timescan_dir / product
 
             # get rescaling and db right (backscatter vs. coh/pol)
-            if 'bs.' in str(timescan_prefix):
+            if "bs." in str(timescan_prefix):
                 to_power, rescale = to_db, dtype_conversion
             else:
                 to_power, rescale = False, False
 
-            iter_list.append([
-                timeseries, timescan_prefix, ard_tscan['metrics'], rescale,
-                to_power, ard_tscan['remove_outliers'], datelist
-            ])
+            iter_list.append(
+                [
+                    timeseries,
+                    timescan_prefix,
+                    ard_tscan["metrics"],
+                    rescale,
+                    to_power,
+                    ard_tscan["remove_outliers"],
+                    datelist,
+                ]
+            )
 
         vrt_iter_list.append(timescan_dir)
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=config_dict['max_workers']
+        executor=config_dict["executor_type"], max_workers=config_dict["max_workers"]
     )
 
     # run timescan creation
-    out_dict = {'burst': [], 'prefix': [], 'metrics': [], 'error': []}
-    for task in executor.as_completed(
-            func=timescan.gd_mt_metrics,
-            iterable=iter_list
-    ):
+    out_dict = {"burst": [], "prefix": [], "metrics": [], "error": []}
+    for task in executor.as_completed(func=timescan.gd_mt_metrics, iterable=iter_list):
         burst, prefix, metrics, error = task.result()
-        out_dict['burst'].append(burst)
-        out_dict['prefix'].append(prefix)
-        out_dict['metrics'].append(metrics)
-        out_dict['error'].append(error)
+        out_dict["burst"].append(burst)
+        out_dict["prefix"].append(prefix)
+        out_dict["metrics"].append(metrics)
+        out_dict["error"].append(error)
 
     df = pd.DataFrame.from_dict(out_dict)
 
     # run vrt creation
     for task in executor.as_completed(
-            func=ras.create_tscan_vrt,
-            iterable=vrt_iter_list,
-            fargs=([str(config_file), ])
+        func=ras.create_tscan_vrt,
+        iterable=vrt_iter_list,
+        fargs=(
+            [
+                str(config_file),
+            ]
+        ),
     ):
         task.result()
 
@@ -487,21 +506,21 @@ def timeseries_to_timescan(burst_gdf, config_file):
 
 
 def mosaic_timeseries(burst_inventory, config_file):
-    print(' -----------------------------------------------------------------')
-    logger.info('Mosaicking time-series layers.')
-    print(' -----------------------------------------------------------------')
+    print(" -----------------------------------------------------------------")
+    logger.info("Mosaicking time-series layers.")
+    print(" -----------------------------------------------------------------")
 
     # -------------------------------------
     # 1 load project config
-    with open(config_file, 'r') as ard_file:
+    with open(config_file, "r") as ard_file:
         config_dict = json.load(ard_file)
-        processing_dir = Path(config_dict['processing_dir'])
+        processing_dir = Path(config_dict["processing_dir"])
 
     # create output folder
-    ts_dir = processing_dir.joinpath('Mosaic/Timeseries')
+    ts_dir = processing_dir / "Mosaic" / "Timeseries"
     ts_dir.mkdir(parents=True, exist_ok=True)
 
-    temp_mosaic = processing_dir.joinpath('Mosaic/temp')
+    temp_mosaic = processing_dir / "Mosaic" / "temp"
     temp_mosaic.mkdir(parents=True, exist_ok=True)
     # -------------------------------------
     # 2 create iterable
@@ -512,43 +531,38 @@ def mosaic_timeseries(burst_inventory, config_file):
         for track in burst_inventory.Track.unique():
 
             dates = [
-                date[2:] for date in sorted(
-                    burst_inventory.Date[
-                        burst_inventory.Track == track].unique()
+                date[2:]
+                for date in sorted(
+                    burst_inventory.Date[burst_inventory.Track == track].unique()
                 )
             ]
 
             for i, date in enumerate(dates):
 
-                if 'coh' in product:
+                if "coh" in product:
                     # we do the try, since for the last date
                     # there is no dates[i+1] for coherence
                     try:
-                        temp_acq = temp_mosaic.joinpath(
-                            f'{i}.{date}.{dates[i + 1]}.{track}.{product}.tif'
+                        temp_acq = (
+                            temp_mosaic
+                            / f"{i}.{date}.{dates[i + 1]}.{track}.{product}.tif"
                         )
                     except IndexError:
                         temp_acq = None
                 else:
-                    temp_acq = temp_mosaic.joinpath(
-                        f'{i}.{date}.{track}.{product}.tif'
-                    )
+                    temp_acq = temp_mosaic / f"{i}.{date}.{track}.{product}.tif"
 
                 if temp_acq:
-                    iter_list.append(
-                        [track, date, product, temp_acq, config_file]
-                    )
+                    iter_list.append([track, date, product, temp_acq, config_file])
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=config_dict['max_workers']
+        executor=config_dict["executor_type"], max_workers=config_dict["max_workers"]
     )
 
     # run vrt creation
     for task in executor.as_completed(
-            func=mosaic.gd_mosaic_slc_acquisition,
-            iterable=iter_list
+        func=mosaic.gd_mosaic_slc_acquisition, iterable=iter_list
     ):
         task.result()
 
@@ -559,29 +573,27 @@ def mosaic_timeseries(burst_inventory, config_file):
         outfiles = []
         for i in range(len(dates)):
 
-            list_of_files = list(temp_mosaic.glob(f'{i}.*{product}.tif'))
+            list_of_files = list(temp_mosaic.glob(f"{i}.*{product}.tif"))
 
             if not list_of_files:
                 continue
 
             datelist = []
             for file in list_of_files:
-                if 'coh' in product:
+                if "coh" in product:
                     datelist.append(
                         f"{file.name.split('.')[2]}_{file.name.split('.')[1]}"
                     )
                 else:
-                    datelist.append(file.name.split('.')[1])
+                    datelist.append(file.name.split(".")[1])
 
             # get start and endate of mosaic
             start, end = sorted(datelist)[0], sorted(datelist)[-1]
-            list_of_files = ' '.join([str(file) for file in list_of_files])
+            list_of_files = " ".join([str(file) for file in list_of_files])
 
             # create namespace for output file
             if start == end:
-                outfile = ts_dir.joinpath(
-                    f'{i + 1:02d}.{start}.{product}.tif'
-                )
+                outfile = ts_dir / f"{i + 1:02d}.{start}.{product}.tif"
 
                 # with the above operation, the list automatically
                 # turns into string, so we can call directly list_of_files
@@ -590,17 +602,13 @@ def mosaic_timeseries(burst_inventory, config_file):
                 continue
 
             else:
-                outfile = ts_dir.joinpath(
-                    f'{i + 1:02d}.{start}-{end}.{product}.tif'
-                )
+                outfile = ts_dir / f"{i + 1:02d}.{start}-{end}.{product}.tif"
 
             # create namespace for check_file
-            check_file = outfile.parent.joinpath(
-                f'.{outfile.name[:-4]}.processed'
-            )
+            check_file = outfile.parent / f".{outfile.name[:-4]}.processed"
 
             if check_file.exists():
-                logger.info(f'Mosaic layer {outfile} already processed.')
+                logger.info(f"Mosaic layer {outfile} already processed.")
                 continue
 
             # append to list of outfile for vrt creation
@@ -611,21 +619,16 @@ def mosaic_timeseries(burst_inventory, config_file):
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=config_dict['max_workers']
+        executor=config_dict["executor_type"], max_workers=config_dict["max_workers"]
     )
 
     # run mosaicking
-    for task in executor.as_completed(
-            func=mosaic.gd_mosaic,
-            iterable=iter_list
-    ):
+    for task in executor.as_completed(func=mosaic.gd_mosaic, iterable=iter_list):
         task.result()
 
     # run mosaicking vrts
     for task in executor.as_completed(
-            func=mosaic.create_timeseries_mosaic_vrt,
-            iterable=vrt_iter_list
+        func=mosaic.create_timeseries_mosaic_vrt, iterable=vrt_iter_list
     ):
         task.result()
 
@@ -641,104 +644,94 @@ def mosaic_timescan(burst_inventory, config_file):
     :return:
     """
 
-    print(' -----------------------------------------------------------------')
-    logger.info('Mosaicking time-scan layers.')
-    print(' -----------------------------------------------------------------')
+    print(" -----------------------------------------------------------------")
+    logger.info("Mosaicking time-scan layers.")
+    print(" -----------------------------------------------------------------")
 
     # -------------------------------------
     # 1 load project config
-    with open(config_file, 'r') as ard_file:
+    with open(config_file, "r") as ard_file:
         config_dict = json.load(ard_file)
-        processing_dir = Path(config_dict['processing_dir'])
-        metrics = config_dict['processing']['time-scan_ARD']['metrics']
+        processing_dir = Path(config_dict["processing_dir"])
+        metrics = config_dict["processing"]["time-scan_ARD"]["metrics"]
 
-    if 'harmonics' in metrics:
-        metrics.remove('harmonics')
-        metrics.extend(['amplitude', 'phase', 'residuals', 'model_mean'])
+    if "harmonics" in metrics:
+        metrics.remove("harmonics")
+        metrics.extend(["amplitude", "phase", "residuals", "model_mean"])
 
-    if 'percentiles' in metrics:
-        metrics.remove('percentiles')
-        metrics.extend(['p95', 'p5'])
+    if "percentiles" in metrics:
+        metrics.remove("percentiles")
+        metrics.extend(["p95", "p5"])
 
     # create output folder
-    ts_dir = processing_dir.joinpath('Mosaic/Timescan')
+    ts_dir = processing_dir / "Mosaic" / "Timescan"
     ts_dir.mkdir(parents=True, exist_ok=True)
 
-    temp_mosaic = processing_dir.joinpath('Mosaic/temp')
+    temp_mosaic = processing_dir / "Mosaic" / "temp"
     temp_mosaic.mkdir(parents=True, exist_ok=True)
     # -------------------------------------
     # 2 create iterable
     # loop through each product
-    iter_list, vrt_iter_list = [], []
+    iter_list = []
     for product, metric in itertools.product(PRODUCT_LIST, metrics):
 
         for track in burst_inventory.Track.unique():
 
-            filelist = list(processing_dir.glob(
-                f'[A,D]{track}_IW*/Timescan/*{product}.{metric}.tif'
-            ))
+            filelist = list(
+                processing_dir.glob(
+                    f"[A,D]{track}_IW*/Timescan/*{product}.{metric}.tif"
+                )
+            )
 
             if not len(filelist) >= 1:
                 continue
 
-            temp_acq = temp_mosaic.joinpath(
-                f'{track}.{product}.{metric}.tif'
-            )
+            temp_acq = temp_mosaic / f"{track}.{product}.{metric}.tif"
 
             if temp_acq:
-                iter_list.append(
-                    [track, metric, product, temp_acq, config_file]
-                )
+                iter_list.append([track, metric, product, temp_acq, config_file])
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=config_dict['max_workers']
+        executor=config_dict["executor_type"], max_workers=config_dict["max_workers"]
     )
 
     # run vrt creation
     for task in executor.as_completed(
-            func=mosaic.gd_mosaic_slc_acquisition,
-            iterable=iter_list
+        func=mosaic.gd_mosaic_slc_acquisition, iterable=iter_list
     ):
         task.result()
 
-    iter_list, vrt_iter_list = [], []
+    iter_list = []
     for product, metric in itertools.product(PRODUCT_LIST, metrics):
 
-        list_of_files = list(temp_mosaic.glob(f'*{product}.{metric}.tif'))
+        list_of_files = list(temp_mosaic.glob(f"*{product}.{metric}.tif"))
 
         if not list_of_files:
             continue
 
         # turn to OTB readable format
-        list_of_files = ' '.join([str(file) for file in list_of_files])
+        list_of_files = " ".join([str(file) for file in list_of_files])
 
         # create namespace for outfile
-        outfile = ts_dir.joinpath(f'{product}.{metric}.tif')
-        check_file = outfile.parent.joinpath(
-            f'.{outfile.name[:-4]}.processed'
-        )
+        outfile = ts_dir / f"{product}.{metric}.tif"
+        check_file = outfile.parent / f".{outfile.name[:-4]}.processed"
 
         if check_file.exists():
-            logger.info(f'Mosaic layer {outfile.name} already processed.')
+            logger.info(f"Mosaic layer {outfile.name} already processed.")
             continue
 
-        logger.info(f'Mosaicking layer {outfile.name}.')
+        logger.info(f"Mosaicking layer {outfile.name}.")
 
         iter_list.append([list_of_files, outfile, config_file])
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=config_dict['max_workers']
+        executor=config_dict["executor_type"], max_workers=config_dict["max_workers"]
     )
 
     # run mosaicking
-    for task in executor.as_completed(
-            func=mosaic.gd_mosaic,
-            iterable=iter_list
-    ):
+    for task in executor.as_completed(func=mosaic.gd_mosaic, iterable=iter_list):
         task.result()
 
     ras.create_tscan_vrt(ts_dir, config_file)
@@ -748,62 +741,54 @@ def mosaic_timescan(burst_inventory, config_file):
 
 
 def mosaic_timescan_old(config_file):
-    print(' -----------------------------------------------------------------')
-    logger.info('Mosaicking time-scan layers.')
-    print(' -----------------------------------------------------------------')
+    print(" -----------------------------------------------------------------")
+    logger.info("Mosaicking time-scan layers.")
+    print(" -----------------------------------------------------------------")
 
-    with open(config_file, 'r') as ard_file:
+    with open(config_file, "r") as ard_file:
         config_dict = json.load(ard_file)
-        processing_dir = Path(config_dict['processing_dir'])
-        metrics = config_dict['processing']['time-scan_ARD']['metrics']
+        processing_dir = Path(config_dict["processing_dir"])
+        metrics = config_dict["processing"]["time-scan_ARD"]["metrics"]
 
-    if 'harmonics' in metrics:
-        metrics.remove('harmonics')
-        metrics.extend(['amplitude', 'phase', 'residuals'])
+    if "harmonics" in metrics:
+        metrics.remove("harmonics")
+        metrics.extend(["amplitude", "phase", "residuals"])
 
-    if 'percentiles' in metrics:
-        metrics.remove('percentiles')
-        metrics.extend(['p95', 'p5'])
+    if "percentiles" in metrics:
+        metrics.remove("percentiles")
+        metrics.extend(["p95", "p5"])
 
-    tscan_dir = processing_dir.joinpath('Mosaic/Timescan')
+    tscan_dir = processing_dir / "Mosaic" / "Timescan"
     tscan_dir.mkdir(parents=True, exist_ok=True)
 
     iter_list = []
     for product, metric in itertools.product(PRODUCT_LIST, metrics):
 
-        filelist = list(processing_dir.glob(
-            f'*/Timescan/*{product}.{metric}.tif'
-        ))
+        filelist = list(processing_dir.glob(f"*/Timescan/*{product}.{metric}.tif"))
 
         if not len(filelist) >= 1:
             continue
 
-        filelist = ' '.join([str(file) for file in filelist])
+        filelist = " ".join([str(file) for file in filelist])
 
-        outfile = tscan_dir.joinpath(f'{product}.{metric}.tif')
-        check_file = outfile.parent.joinpath(
-            f'.{outfile.name[:-4]}.processed'
-        )
+        outfile = tscan_dir / f"{product}.{metric}.tif"
+        check_file = outfile.parent / f".{outfile.name[:-4]}.processed"
 
         if check_file.exists():
-            logger.info(f'Mosaic layer {outfile.name} already processed.')
+            logger.info(f"Mosaic layer {outfile.name} already processed.")
             continue
 
-        logger.info(f'Mosaicking layer {outfile.name}.')
+        logger.info(f"Mosaicking layer {outfile.name}.")
 
         iter_list.append([filelist, outfile, config_file])
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=config_dict['max_workers']
+        executor=config_dict["executor_type"], max_workers=config_dict["max_workers"]
     )
 
     # run mosaicking
-    for task in executor.as_completed(
-            func=mosaic.gd_mosaic,
-            iterable=iter_list
-    ):
+    for task in executor.as_completed(func=mosaic.gd_mosaic, iterable=iter_list):
         task.result()
 
     ras.create_tscan_vrt(tscan_dir, config_file)
diff --git a/ost/s1/burst_inventory.py b/ost/s1/burst_inventory.py
index 4281c36b..21cb40cd 100644
--- a/ost/s1/burst_inventory.py
+++ b/ost/s1/burst_inventory.py
@@ -30,38 +30,38 @@ def burst_extract(scene_id, track, acq_date, et_root):
 
     # define columns for burst gdf and create empty gdf
     column_names = [
-        'SceneID', 'Track', 'Date', 'SwathID', 'AnxTime', 'BurstNr', 'geometry'
+        "SceneID",
+        "Track",
+        "Date",
+        "SwathID",
+        "AnxTime",
+        "BurstNr",
+        "geometry",
     ]
     gdf = gpd.GeoDataFrame(columns=column_names)
 
     # pol = root.find('adsHeader').find('polarisation').text
-    swath = et_root.find('adsHeader').find('swath').text
-    burst_lines = np.int(
-        et_root.find('swathTiming').find('linesPerBurst').text
-    )
+    swath = et_root.find("adsHeader").find("swath").text
+    burst_lines = np.int(et_root.find("swathTiming").find("linesPerBurst").text)
 
-    burst_samples = np.int(
-        et_root.find('swathTiming').find('samplesPerBurst').text
-    )
+    burst_samples = np.int(et_root.find("swathTiming").find("samplesPerBurst").text)
 
-    list_of_bursts = et_root.find('swathTiming').find('burstList')
-    geolocation_grid = et_root.find('geolocationGrid')[0]
+    list_of_bursts = et_root.find("swathTiming").find("burstList")
+    geolocation_grid = et_root.find("geolocationGrid")[0]
 
     first, last = {}, {}
 
     # Get burst corner geolocation info
     for geo_point in geolocation_grid:
 
-        if geo_point.find('pixel').text == '0':
-            first[geo_point.find('line').text] = np.float32(
-                [geo_point.find('latitude').text,
-                 geo_point.find('longitude').text]
+        if geo_point.find("pixel").text == "0":
+            first[geo_point.find("line").text] = np.float32(
+                [geo_point.find("latitude").text, geo_point.find("longitude").text]
             )
 
-        elif geo_point.find('pixel').text == str(burst_samples - 1):
-            last[geo_point.find('line').text] = np.float32(
-                [geo_point.find('latitude').text,
-                 geo_point.find('longitude').text]
+        elif geo_point.find("pixel").text == str(burst_samples - 1):
+            last[geo_point.find("line").text] = np.float32(
+                [geo_point.find("latitude").text, geo_point.find("longitude").text]
             )
 
     for i, b in enumerate(list_of_bursts):
@@ -69,7 +69,7 @@ def burst_extract(scene_id, track, acq_date, et_root):
         firstline = str(i * burst_lines)
         lastline = str((i + 1) * burst_lines)
 
-        azi_anx_time = np.float32(b.find('azimuthAnxTime').text)
+        azi_anx_time = np.float32(b.find("azimuthAnxTime").text)
         orbit_time = 12 * 24 * 60 * 60 / 175
 
         if azi_anx_time > orbit_time:
@@ -85,7 +85,7 @@ def burst_extract(scene_id, track, acq_date, et_root):
             try:
                 firstthis = first[firstline]
             except KeyError:
-                print('First line not found in annotation file')
+                print("First line not found in annotation file")
                 firstthis = []
         try:
             lastthis = last[lastline]
@@ -94,7 +94,7 @@ def burst_extract(scene_id, track, acq_date, et_root):
             try:
                 lastthis = last[lastline]
             except KeyError:
-                print('Last line not found in annotation file')
+                print("Last line not found in annotation file")
                 lastthis = []
 
         corners = np.zeros([4, 2], dtype=np.float32)
@@ -106,7 +106,7 @@ def burst_extract(scene_id, track, acq_date, et_root):
             corners[3] = first[lastline]
             corners[2] = last[lastline]
 
-        wkt = 'POLYGON (({} {},{} {},{} {},{} {},{} {}))'.format(
+        wkt = "POLYGON (({} {},{} {},{} {},{} {},{} {}))".format(
             np.around(float(corners[0, 1]), 3),
             np.around(float(corners[0, 0]), 3),
             np.around(float(corners[3, 1]), 3),
@@ -116,16 +116,17 @@ def burst_extract(scene_id, track, acq_date, et_root):
             np.around(float(corners[1, 1]), 3),
             np.around(float(corners[1, 0]), 3),
             np.around(float(corners[0, 1]), 3),
-            np.around(float(corners[0, 0]), 3))
+            np.around(float(corners[0, 0]), 3),
+        )
 
         geo_dict = {
-            'SceneID': scene_id,
-            'Track': track,
-            'Date': acq_date,
-            'SwathID': swath,
-            'AnxTime': azi_anx_time,
-            'BurstNr': i + 1,
-            'geometry': loads(wkt)
+            "SceneID": scene_id,
+            "Track": track,
+            "Date": acq_date,
+            "SwathID": swath,
+            "AnxTime": azi_anx_time,
+            "BurstNr": i + 1,
+            "geometry": loads(wkt),
         }
 
         gdf = gdf.append(geo_dict, ignore_index=True)
@@ -133,18 +134,32 @@ def burst_extract(scene_id, track, acq_date, et_root):
     return gdf
 
 
-def burst_inventory(inventory_df, outfile, download_dir=os.getenv('HOME'),
-                    data_mount=None, uname=None, pword=None):
+def burst_inventory(
+    inventory_df,
+    outfile,
+    download_dir=os.getenv("HOME"),
+    data_mount=None,
+    uname=None,
+    pword=None,
+):
     """Creates a Burst GeoDataFrame from an OST inventory file
     Args:
     Returns:
     """
     # create column names for empty data frame
-    column_names = ['SceneID', 'Track', 'Direction', 'Date', 'SwathID',
-                    'AnxTime', 'BurstNr', 'geometry']
+    column_names = [
+        "SceneID",
+        "Track",
+        "Direction",
+        "Date",
+        "SwathID",
+        "AnxTime",
+        "BurstNr",
+        "geometry",
+    ]
 
     # crs for empty dataframe
-    crs = 'epsg:4326'
+    crs = "epsg:4326"
     # create empty dataframe
     gdf_full = gpd.GeoDataFrame(columns=column_names, crs=crs)
 
@@ -153,70 +168,78 @@ def burst_inventory(inventory_df, outfile, download_dir=os.getenv('HOME'),
         # read into S1scene class
         scene = S1Scene(scene_id)
 
-        logger.info('Getting burst info from {}.'.format(scene.scene_id))
+        logger.info("Getting burst info from {}.".format(scene.scene_id))
 
         # get orbit direction
         orbit_direction = inventory_df[
-            inventory_df.identifier == scene_id].orbitdirection.values[0]
+            inventory_df.identifier == scene_id
+        ].orbitdirection.values[0]
 
         file_path = scene.get_path(download_dir, data_mount)
         if not file_path:
 
-            logger.info('Retrieving burst info from scihub'
-                        ' (need to download xml files)')
+            logger.info(
+                "Retrieving burst info from scihub" " (need to download xml files)"
+            )
             if not uname and not pword:
                 uname, pword = scihub.ask_credentials()
 
             opener = scihub.connect(uname=uname, pword=pword)
             if scene.scihub_online_status(opener) is False:
                 raise RuntimeError(
-                    'Product needs to be downloaded or online '
-                    'to create a burst database.'
-                    'Download the product first and '
-                    'do the burst inventory with the local data.'
+                    "Product needs to be downloaded or online "
+                    "to create a burst database."
+                    "Download the product first and "
+                    "do the burst inventory with the local data."
                 )
             else:
                 single_gdf = scene.scihub_annotation_get(uname, pword)
 
-        elif file_path.suffix == '.zip':
+        elif file_path.suffix == ".zip":
             single_gdf = scene.zip_annotation_get(download_dir, data_mount)
 
-        elif file_path.suffix == '.SAFE':
+        elif file_path.suffix == ".SAFE":
             single_gdf = scene.safe_annotation_get(download_dir, data_mount)
 
         else:
             raise RuntimeError(
-                'Burst inventory failed because of unavailability of data. '
-                'Make sure to download all scenes first.'
+                "Burst inventory failed because of unavailability of data. "
+                "Make sure to download all scenes first."
             )
 
         # add orbit direction
-        single_gdf['Direction'] = orbit_direction
+        single_gdf["Direction"] = orbit_direction
 
         # append
         gdf_full = gdf_full.append(single_gdf, sort=True)
 
     gdf_full = gdf_full.reset_index(drop=True)
 
-    for i in gdf_full['AnxTime'].unique():
+    for i in gdf_full["AnxTime"].unique():
 
         # get similar burst times
-        idx = gdf_full.index[
-            (gdf_full.AnxTime >= i - 6) &
-            (gdf_full.AnxTime <= i + 6) &
-            (gdf_full.AnxTime != i)
-            ].unique().values
+        idx = (
+            gdf_full.index[
+                (gdf_full.AnxTime >= i - 6)
+                & (gdf_full.AnxTime <= i + 6)
+                & (gdf_full.AnxTime != i)
+            ]
+            .unique()
+            .values
+        )
 
         # reset all to first value
         for j in idx:
-            gdf_full.loc[j, 'AnxTime'] = i
+            gdf_full.loc[j, "AnxTime"] = i
 
     # create the actual burst id
-    gdf_full['bid'] = (
-            gdf_full.Direction.str[0] +
-            gdf_full.Track.astype(str) + '_' +
-            gdf_full.SwathID.astype(str) + '_' +
-            gdf_full.AnxTime.astype(str)
+    gdf_full["bid"] = (
+        gdf_full.Direction.str[0]
+        + gdf_full.Track.astype(str)
+        + "_"
+        + gdf_full.SwathID.astype(str)
+        + "_"
+        + gdf_full.AnxTime.astype(str)
     )
 
     # save file to out
@@ -231,41 +254,34 @@ def refine_burst_inventory(aoi, burst_gdf, outfile, coverages=None):
     Returns:
     """
 
-    warnings.filterwarnings(
-        'ignore', 'Geometry is in a geographic CRS', UserWarning
-    )
+    warnings.filterwarnings("ignore", "Geometry is in a geographic CRS", UserWarning)
 
     # turn aoi into a geodataframe
     aoi_gdf = gpd.GeoDataFrame(vec.wkt_to_gdf(aoi).buffer(0.05))
-    aoi_gdf.columns = ['geometry']
-    aoi_gdf.crs = 'epsg:4326'
+    aoi_gdf.columns = ["geometry"]
+    aoi_gdf.crs = "epsg:4326"
 
     # get columns of input dataframe for later return function
     cols = burst_gdf.columns
 
     # 1) get only intersecting footprints (double, since we do this before)
-    burst_gdf = gpd.sjoin(burst_gdf, aoi_gdf, how='inner', op='intersects')
+    burst_gdf = gpd.sjoin(burst_gdf, aoi_gdf, how="inner", op="intersects")
 
     # if aoi  gdf has an id field we need to rename the changed id_left field
-    if 'id_left' in burst_gdf.columns:
+    if "id_left" in burst_gdf.columns:
         # rename id_left to id
-        burst_gdf.columns = (['id' if x == 'id_left' else x
-                              for x in burst_gdf.columns])
+        burst_gdf.columns = ["id" if x == "id_left" else x for x in burst_gdf.columns]
 
     # remove duplicates
-    burst_gdf.drop_duplicates(['SceneID', 'Date', 'bid'], inplace=True)
+    burst_gdf.drop_duplicates(["SceneID", "Date", "bid"], inplace=True)
 
     # check if number of bursts align with number of coverages
     if coverages:
         for burst in burst_gdf.bid.unique():
             if len(burst_gdf[burst_gdf.bid == burst]) != coverages:
-                logging.info(
-                    f'Removing burst {burst} because of unsuffcient coverage.'
-                )
+                logging.info(f"Removing burst {burst} because of unsuffcient coverage.")
 
-                burst_gdf.drop(
-                    burst_gdf[burst_gdf.bid == burst].index, inplace=True
-                )
+                burst_gdf.drop(burst_gdf[burst_gdf.bid == burst].index, inplace=True)
 
     # save file to out
     burst_gdf.to_file(outfile, driver="GPKG")
@@ -275,79 +291,86 @@ def refine_burst_inventory(aoi, burst_gdf, outfile, coverages=None):
 def prepare_burst_inventory(burst_gdf, config_file):
 
     cols = [
-        'AnxTime', 'BurstNr', 'Date', 'Direction', 'SceneID', 'SwathID',
-        'Track', 'geometry', 'bid', 'master_prefix', 'out_directory',
-        'slave_date', 'slave_scene_id', 'slave_file', 'slave_burst_nr',
-        'slave_prefix'
+        "AnxTime",
+        "BurstNr",
+        "Date",
+        "Direction",
+        "SceneID",
+        "SwathID",
+        "Track",
+        "geometry",
+        "bid",
+        "master_prefix",
+        "out_directory",
+        "slave_date",
+        "slave_scene_id",
+        "slave_file",
+        "slave_burst_nr",
+        "slave_prefix",
     ]
 
     # create empty geodataframe
-    proc_burst_gdf = gpd.GeoDataFrame(columns=cols, geometry='geometry', crs='epsg:4326')
+    proc_burst_gdf = gpd.GeoDataFrame(
+        columns=cols, geometry="geometry", crs="epsg:4326"
+    )
 
     # load relevant config parameters
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        processing_dir = Path(config_dict['processing_dir'])
-        download_dir = Path(config_dict['download_dir'])
-        data_mount = Path(config_dict['data_mount'])
+        processing_dir = Path(config_dict["processing_dir"])
+        download_dir = Path(config_dict["download_dir"])
+        data_mount = Path(config_dict["data_mount"])
 
     # loop through burst_gdf and add slave infos
     for burst in burst_gdf.bid.unique():
 
         # create a list of dates over which we loop
-        dates = burst_gdf.Date[
-            burst_gdf.bid == burst].sort_values().tolist()
+        dates = burst_gdf.Date[burst_gdf.bid == burst].sort_values().tolist()
 
         # loop through dates
         for idx, date in enumerate(dates):
 
             # get master date
             burst_row = burst_gdf[
-                (burst_gdf.Date == date) &
-                (burst_gdf.bid == burst)].copy()
+                (burst_gdf.Date == date) & (burst_gdf.bid == burst)
+            ].copy()
 
             # get parameters for master
             master_scene = S1Scene(burst_row.SceneID.values[0])
-            burst_row['file_location'] = master_scene.get_path(
-                download_dir, data_mount
-            )
-            burst_row['master_prefix'] = f'{date}_{burst_row.bid.values[0]}'
-            burst_row['out_directory'] = processing_dir.joinpath(burst, date)
+            burst_row["file_location"] = master_scene.get_path(download_dir, data_mount)
+            burst_row["master_prefix"] = f"{date}_{burst_row.bid.values[0]}"
+            burst_row["out_directory"] = processing_dir / burst / date
 
             # try to get slave date
             try:
                 # get slave date and add column to burst row
                 slave_date = dates[idx + 1]
-                burst_row['slave_date'] = slave_date
+                burst_row["slave_date"] = slave_date
 
                 # read slave burst line
                 slave_burst = burst_gdf[
-                    (burst_gdf.Date == slave_date) &
-                    (burst_gdf.bid == burst)]
+                    (burst_gdf.Date == slave_date) & (burst_gdf.bid == burst)
+                ]
 
                 # get scene id and add into master row
                 slave_scene_id = S1Scene(slave_burst.SceneID.values[0])
-                burst_row['slave_scene_id'] = slave_scene_id.scene_id
+                burst_row["slave_scene_id"] = slave_scene_id.scene_id
 
                 # get path to slave file
-                burst_row['slave_file'] = slave_scene_id.get_path(
+                burst_row["slave_file"] = slave_scene_id.get_path(
                     download_dir, data_mount
                 )
 
                 # burst number in slave file (subswath is same)
-                burst_row['slave_burst_nr'] = slave_burst.BurstNr.values[0]
+                burst_row["slave_burst_nr"] = slave_burst.BurstNr.values[0]
 
                 # outfile name
-                burst_row['slave_prefix'] = (
-                    f'{slave_date}_{slave_burst.bid.values[0]}'
-                )
+                burst_row["slave_prefix"] = f"{slave_date}_{slave_burst.bid.values[0]}"
 
             except IndexError:
-                burst_row['slave_date'], burst_row[
-                    'slave_scene_id'] = None, None
-                burst_row['slave_file'], burst_row[
-                    'slave_burst_nr'] = None, None
-                burst_row['slave_prefix'] = None
+                burst_row["slave_date"], burst_row["slave_scene_id"] = None, None
+                burst_row["slave_file"], burst_row["slave_burst_nr"] = None, None
+                burst_row["slave_prefix"] = None
 
             proc_burst_gdf = proc_burst_gdf.append(burst_row, sort=False)
 
diff --git a/ost/s1/burst_to_ard.py b/ost/s1/burst_to_ard.py
index 93af5dc4..ade26d39 100644
--- a/ost/s1/burst_to_ard.py
+++ b/ost/s1/burst_to_ard.py
@@ -18,9 +18,7 @@
 logger = logging.getLogger(__name__)
 
 
-def create_polarimetric_layers(
-        import_file, out_dir, burst_prefix, config_dict
-):
+def create_polarimetric_layers(import_file, out_dir, burst_prefix, config_dict):
     """Pipeline for Dual-polarimetric decomposition
 
     :param import_file:
@@ -37,10 +35,10 @@ def create_polarimetric_layers(
         # 1 Polarimetric Decomposition
 
         # create namespace for temporary decomposed product
-        out_haa = temp.joinpath(f'{burst_prefix}_h')
+        out_haa = temp / f"{burst_prefix}_h"
 
         # create namespace for decompose log
-        haa_log = out_dir.joinpath(f'{burst_prefix}_haa.err_log')
+        haa_log = out_dir / f"{burst_prefix}_haa.err_log"
 
         # run polarimetric decomposition
         try:
@@ -52,54 +50,49 @@ def create_polarimetric_layers(
         # 2 Geocoding
 
         # create namespace for temporary geocoded product
-        out_htc = temp.joinpath(f'{burst_prefix}_pol')
+        out_htc = temp / f"{burst_prefix}_pol"
 
         # create namespace for geocoding log
-        haa_tc_log = out_dir.joinpath(f'{burst_prefix}_haa_tc.err_log')
+        haa_tc_log = out_dir / f"{burst_prefix}_haa_tc.err_log"
 
         # run geocoding
         try:
             common.terrain_correction(
-                out_haa.with_suffix('.dim'), out_htc, haa_tc_log, config_dict
+                out_haa.with_suffix(".dim"), out_htc, haa_tc_log, config_dict
             )
         except (GPTRuntimeError, NotValidFileError) as error:
             logger.info(error)
             return None, error
 
         # set nans to 0 (issue from SNAP for polarimetric layers)
-        for infile in list(out_htc.with_suffix('.data').glob('*.img')):
+        for infile in list(out_htc.with_suffix(".data").glob("*.img")):
 
-            with rasterio.open(str(infile), 'r') as src:
+            with rasterio.open(str(infile), "r") as src:
                 meta = src.meta.copy()
                 array = src.read()
                 array[np.isnan(array)] = 0
 
-            with rasterio.open(str(infile), 'w', **meta) as dest:
+            with rasterio.open(str(infile), "w", **meta) as dest:
                 dest.write(array)
 
         # ---------------------------------------------------------------------
         # 5 Create an outline
-        ras.image_bounds(out_htc.with_suffix('.data'))
+        ras.image_bounds(out_htc.with_suffix(".data"))
 
         # move to final destination
-        ard = config_dict['processing']['single_ARD']
-        h.move_dimap(
-            out_htc, out_dir.joinpath(f'{burst_prefix}_pol'), ard['to_tif']
-        )
+        ard = config_dict["processing"]["single_ARD"]
+        h.move_dimap(out_htc, out_dir / f"{burst_prefix}_pol", ard["to_tif"])
 
         # write out check file for tracking that it is processed
-        with open(out_dir.joinpath('.pol.processed'), 'w+') as file:
-            file.write('passed all tests \n')
+        with (out_dir / ".pol.processed").open("w+") as file:
+            file.write("passed all tests \n")
 
-        return (
-            str(out_dir.joinpath(f'{burst_prefix}_pol').with_suffix('.dim')),
-            None
-        )
+        dim_file = out_dir / f"{burst_prefix}_pol.dim"
 
+        return (str(dim_file), None)
 
-def create_backscatter_layers(
-        import_file, out_dir, burst_prefix, config_dict
-):
+
+def create_backscatter_layers(import_file, out_dir, burst_prefix, config_dict):
     """Pipeline for backscatter processing
 
     :param import_file:
@@ -110,7 +103,7 @@ def create_backscatter_layers(
     """
 
     # get relevant config parameters
-    ard = config_dict['processing']['single_ARD']
+    ard = config_dict["processing"]["single_ARD"]
 
     # temp dir for intermediate files
     with TemporaryDirectory(prefix=f"{config_dict['temp_dir']}/") as temp:
@@ -120,35 +113,35 @@ def create_backscatter_layers(
         # 1 Calibration
 
         # create namespace for temporary calibrated product
-        out_cal = temp.joinpath(f'{burst_prefix}_cal')
+        out_cal = temp / f"{burst_prefix}_cal"
 
         # create namespace for calibrate log
-        cal_log = out_dir.joinpath(f'{burst_prefix}_cal.err_log')
+        cal_log = out_dir / f"{burst_prefix}_cal.err_log"
 
         # run calibration on imported scene
         try:
-            slc.calibration(
-                import_file, out_cal, cal_log, config_dict
-            )
+            slc.calibration(import_file, out_cal, cal_log, config_dict)
         except (GPTRuntimeError, NotValidFileError) as error:
             logger.info(error)
             return None, None, error
 
         # ---------------------------------------------------------------------
         # 2 Speckle filtering
-        if ard['remove_speckle']:
+        if ard["remove_speckle"]:
 
             # create namespace for temporary speckle filtered product
-            speckle_import = temp.joinpath(f'{burst_prefix}_speckle_import')
+            speckle_import = temp / f"{burst_prefix}_speckle_import"
 
             # create namespace for speckle filter log
-            speckle_log = out_dir.joinpath(f'{burst_prefix}_speckle.err_log')
+            speckle_log = out_dir / f"{burst_prefix}_speckle.err_log"
 
             # run speckle filter on calibrated input
             try:
                 common.speckle_filter(
-                    out_cal.with_suffix('.dim'), speckle_import, speckle_log,
-                    config_dict
+                    out_cal.with_suffix(".dim"),
+                    speckle_import,
+                    speckle_log,
+                    config_dict,
                 )
             except (GPTRuntimeError, NotValidFileError) as error:
                 logger.info(error)
@@ -162,18 +155,18 @@ def create_backscatter_layers(
 
         # ---------------------------------------------------------------------
         # 3 dB scaling
-        if ard['to_db']:
+        if ard["to_db"]:
 
             # create namespace for temporary db scaled product
-            out_db = temp.joinpath(f'{burst_prefix}_cal_db')
+            out_db = temp / f"{burst_prefix}_cal_db"
 
             # create namespace for db scaling log
-            db_log = out_dir.joinpath(f'{burst_prefix}_cal_db.err_log')
+            db_log = out_dir / f"{burst_prefix}_cal_db.err_log"
 
             # run db scaling on calibrated/speckle filtered input
             try:
                 common.linear_to_db(
-                    out_cal.with_suffix('.dim'), out_db, db_log, config_dict
+                    out_cal.with_suffix(".dim"), out_db, db_log, config_dict
                 )
             except (GPTRuntimeError, NotValidFileError) as error:
                 logger.info(error)
@@ -189,15 +182,15 @@ def create_backscatter_layers(
         # 4 Geocoding
 
         # create namespace for temporary geocoded product
-        out_tc = temp.joinpath(f'{burst_prefix}_bs')
+        out_tc = temp / f"{burst_prefix}_bs"
 
         # create namespace for geocoding log
-        tc_log = out_dir.joinpath(f'{burst_prefix}_bs_tc.err_log')
+        tc_log = out_dir / f"{burst_prefix}_bs_tc.err_log"
 
         # run terrain correction on calibrated/speckle filtered/db  input
         try:
             common.terrain_correction(
-                out_cal.with_suffix('.dim'), out_tc, tc_log, config_dict
+                out_cal.with_suffix(".dim"), out_tc, tc_log, config_dict
             )
         except (GPTRuntimeError, NotValidFileError) as error:
             logger.info(error)
@@ -205,57 +198,55 @@ def create_backscatter_layers(
 
         # ---------------------------------------------------------------------
         # 5 Create an outline
-        ras.image_bounds(out_tc.with_suffix('.data'))
+        ras.image_bounds(out_tc.with_suffix(".data"))
 
         # ---------------------------------------------------------------------
         # 6 Layover/Shadow mask
         out_ls = None  # set to none for final return statement
-        if ard['create_ls_mask'] is True:
+        if ard["create_ls_mask"] is True:
 
             # create namespace for temporary ls mask product
-            ls_mask = temp.joinpath(f'{burst_prefix}_ls_mask')
+            ls_mask = temp / f"{burst_prefix}_ls_mask"
 
             # create namespace for ls mask log
-            logfile = out_dir.joinpath(f'{burst_prefix}.ls_mask.errLog')
+            logfile = out_dir / f"{burst_prefix}.ls_mask.errLog"
 
             # run ls mask routine
             try:
                 common.ls_mask(
-                    out_cal.with_suffix('.dim'), ls_mask, logfile, config_dict
+                    out_cal.with_suffix(".dim"), ls_mask, logfile, config_dict
                 )
             except (GPTRuntimeError, NotValidFileError) as error:
                 logger.info(error)
                 return None, None, error
 
             # polygonize
-            ls_raster = list(ls_mask.with_suffix('.data').glob('*img'))[0]
-            ras.polygonize_ls(ls_raster, ls_mask.with_suffix('.json'))
+            ls_raster = list(ls_mask.with_suffix(".data").glob("*img"))[0]
+            ras.polygonize_ls(ls_raster, ls_mask.with_suffix(".json"))
 
-            out_ls = out_tc.with_suffix('.data')\
-                .joinpath(ls_mask.name).with_suffix('.json')
+            out_ls = (
+                out_tc.with_suffix(".data").joinpath(ls_mask.name).with_suffix(".json")
+            )
 
             # move to product folder
-            ls_mask.with_suffix('.json').rename(out_ls)
+            ls_mask.with_suffix(".json").rename(out_ls)
 
         # move final backscatter product to actual output directory
-        h.move_dimap(
-            out_tc, out_dir.joinpath(f'{burst_prefix}_bs'), ard['to_tif']
-        )
+        h.move_dimap(out_tc, out_dir / f"{burst_prefix}_bs", ard["to_tif"])
 
         # write out check file for tracking that it is processed
-        with open(out_dir.joinpath('.bs.processed'), 'w+') as file:
-            file.write('passed all tests \n')
+        with (out_dir / ".bs.processed").open("w+") as file:
+            file.write("passed all tests \n")
 
         return (
-            str(out_dir.joinpath(f'{burst_prefix}_bs').with_suffix('.dim')),
+            str((out_dir / f"{burst_prefix}_bs").with_suffix(".dim")),
             str(out_ls),
-            None
+            None,
         )
 
 
 def create_coherence_layers(
-        master_import, slave_import, out_dir,
-        master_prefix, config_dict
+    master_import, slave_import, out_dir, master_prefix, config_dict
 ):
     """Pipeline for Dual-polarimetric decomposition
 
@@ -268,7 +259,7 @@ def create_coherence_layers(
     """
 
     # get relevant config parameters
-    ard = config_dict['processing']['single_ARD']
+    ard = config_dict["processing"]["single_ARD"]
 
     with TemporaryDirectory(prefix=f"{config_dict['temp_dir']}/") as temp:
 
@@ -276,16 +267,14 @@ def create_coherence_layers(
         # ---------------------------------------------------------------
         # 1 Co-registration
         # create namespace for temporary co-registered stack
-        out_coreg = temp.joinpath(f'{master_prefix}_coreg')
+        out_coreg = temp / f"{master_prefix}_coreg"
 
         # create namespace for co-registration log
-        coreg_log = out_dir.joinpath(f'{master_prefix}_coreg.err_log')
+        coreg_log = out_dir / f"{master_prefix}_coreg.err_log"
 
         # run co-registration
         try:
-            slc.coreg(
-                master_import, slave_import, out_coreg, coreg_log, config_dict
-            )
+            slc.coreg(master_import, slave_import, out_coreg, coreg_log, config_dict)
         except (GPTRuntimeError, NotValidFileError) as error:
             logger.info(error)
             h.delete_dimap(out_coreg)
@@ -302,16 +291,14 @@ def create_coherence_layers(
         # 2 Coherence calculation
 
         # create namespace for temporary coherence product
-        out_coh = temp.joinpath(f'{master_prefix}_coherence')
+        out_coh = temp / f"{master_prefix}_coherence"
 
         # create namespace for coherence log
-        coh_log = out_dir.joinpath(f'{master_prefix}_coh.err_log')
+        coh_log = out_dir / f"{master_prefix}_coh.err_log"
 
         # run coherence estimation
         try:
-            slc.coherence(
-                out_coreg.with_suffix('.dim'), out_coh, coh_log, config_dict
-            )
+            slc.coherence(out_coreg.with_suffix(".dim"), out_coh, coh_log, config_dict)
         except (GPTRuntimeError, NotValidFileError) as error:
             logger.info(error)
             return None, error
@@ -323,15 +310,15 @@ def create_coherence_layers(
         # 3 Geocoding
 
         # create namespace for temporary geocoded roduct
-        out_tc = temp.joinpath(f'{master_prefix}_coh')
+        out_tc = temp / f"{master_prefix}_coh"
 
         # create namespace for geocoded log
-        tc_log = out_dir.joinpath(f'{master_prefix}_coh_tc.err_log')
+        tc_log = out_dir / f"{master_prefix}_coh_tc.err_log"
 
         # run geocoding
         try:
             common.terrain_correction(
-                out_coh.with_suffix('.dim'), out_tc, tc_log, config_dict
+                out_coh.with_suffix(".dim"), out_tc, tc_log, config_dict
             )
         except (GPTRuntimeError, NotValidFileError) as error:
             logger.info(error)
@@ -345,21 +332,17 @@ def create_coherence_layers(
 
         # ---------------------------------------------------------------------
         # 5 Create an outline
-        ras.image_bounds(out_tc.with_suffix('.data'))
+        ras.image_bounds(out_tc.with_suffix(".data"))
 
         # move to final destination
-        h.move_dimap(
-            out_tc, out_dir.joinpath(f'{master_prefix}_coh'), ard['to_tif']
-        )
+        h.move_dimap(out_tc, out_dir / f"{master_prefix}_coh", ard["to_tif"])
 
         # write out check file for tracking that it is processed
-        with open(out_dir.joinpath('.coh.processed'), 'w+') as file:
-            file.write('passed all tests \n')
+        with (out_dir / ".coh.processed").open("w+") as file:
+            file.write("passed all tests \n")
 
-        return (
-            str(out_dir.joinpath(f'{master_prefix}_coh').with_suffix('.dim')),
-            None
-        )
+        dim_file = out_dir / f"{master_prefix}_coh.dim"
+        return (str(dim_file), None)
 
 
 def burst_to_ard(burst, config_file):
@@ -369,25 +352,25 @@ def burst_to_ard(burst, config_file):
         i, burst = burst
 
     # load relevant config parameters
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        ard = config_dict['processing']['single_ARD']
-        temp_dir = Path(config_dict['temp_dir'])
+        ard = config_dict["processing"]["single_ARD"]
+        temp_dir = Path(config_dict["temp_dir"])
 
     # creation of out_directory
     out_dir = Path(burst.out_directory)
     out_dir.mkdir(parents=True, exist_ok=True)
 
     # existence of processed files
-    pol_file = out_dir.joinpath('.pol.processed').exists()
-    bs_file = out_dir.joinpath('.bs.processed').exists()
-    coh_file = out_dir.joinpath('.coh.processed').exists()
+    pol_file = (out_dir / ".pol.processed").exists()
+    bs_file = (out_dir / ".bs.processed").exists()
+    coh_file = (out_dir / ".coh.processed").exists()
 
     # set all return values initially to None
     out_bs, out_ls, out_pol, out_coh = None, None, None, None
 
     # check if we need to produce coherence
-    if ard['coherence']:
+    if ard["coherence"]:
         # we check if there is actually a slave file or
         # if it is the end of the time-series
         coherence = True if burst.slave_file else False
@@ -395,28 +378,28 @@ def burst_to_ard(burst, config_file):
         coherence = False
 
     # get info on master from GeoSeries
-    master_prefix = burst['master_prefix']
-    master_file = burst['file_location']
-    master_burst_nr = burst['BurstNr']
-    swath = burst['SwathID']
+    master_prefix = burst["master_prefix"]
+    master_file = burst["file_location"]
+    master_burst_nr = burst["BurstNr"]
+    swath = burst["SwathID"]
 
-    logger.info(f'Processing burst {burst.bid} acquired at {burst.Date}')
+    logger.info(f"Processing burst {burst.bid} acquired at {burst.Date}")
     # check if somethings already processed
     if (
-            (ard['H-A-Alpha'] and not pol_file) or
-            (ard['backscatter'] and not bs_file) or
-            (coherence and not coh_file)
+        (ard["H-A-Alpha"] and not pol_file)
+        or (ard["backscatter"] and not bs_file)
+        or (coherence and not coh_file)
     ):
 
         # ---------------------------------------------------------------------
         # 1 Master import
         # create namespace for master import
-        master_import = temp_dir.joinpath(f'{master_prefix}_import')
+        master_import = temp_dir / f"{master_prefix}_import"
 
-        if not master_import.with_suffix('.dim').exists():
+        if not master_import.with_suffix(".dim").exists():
 
             # create namespace for log file
-            import_log = out_dir.joinpath(f'{master_prefix}_import.err_log')
+            import_log = out_dir / f"{master_prefix}_import.err_log"
 
             # run import
             try:
@@ -426,10 +409,10 @@ def burst_to_ard(burst, config_file):
                     import_log,
                     swath,
                     master_burst_nr,
-                    config_dict
+                    config_dict,
                 )
             except (GPTRuntimeError, NotValidFileError) as error:
-                if master_import.with_suffix('.dim').exists():
+                if master_import.with_suffix(".dim").exists():
                     h.delete_dimap(master_import)
 
                 logger.info(error)
@@ -437,42 +420,30 @@ def burst_to_ard(burst, config_file):
 
         # ---------------------------------------------------------------------
         # 2 Product Generation
-        if ard['H-A-Alpha'] and not pol_file:
+        if ard["H-A-Alpha"] and not pol_file:
             out_pol, error = create_polarimetric_layers(
-                master_import.with_suffix('.dim'),
-                out_dir,
-                master_prefix,
-                config_dict
+                master_import.with_suffix(".dim"), out_dir, master_prefix, config_dict
             )
-        elif ard['H-A-Alpha'] and pol_file:
+        elif ard["H-A-Alpha"] and pol_file:
             # construct namespace for existing pol layer
-            out_pol = str(
-                out_dir.joinpath(f'{master_prefix}_pol').with_suffix('.dim')
-            )
+            out_pol = str(out_dir / f"{master_prefix}_pol.dim")
 
-        if ard['backscatter'] and not bs_file:
+        if ard["backscatter"] and not bs_file:
             out_bs, out_ls, error = create_backscatter_layers(
-                master_import.with_suffix('.dim'),
-                out_dir,
-                master_prefix,
-                config_dict
-            )
-        elif ard['backscatter'] and bs_file:
-            out_bs = str(
-                out_dir.joinpath(f'{master_prefix}_bs').with_suffix('.dim')
+                master_import.with_suffix(".dim"), out_dir, master_prefix, config_dict
             )
+        elif ard["backscatter"] and bs_file:
+            out_bs = str(out_dir / f"{master_prefix}_bs.dim")
 
-            if ard['create_ls_mask'] and bs_file:
-                out_ls = str(
-                    out_dir.joinpath(f'{master_prefix}_LS').with_suffix('.dim')
-                )
+            if ard["create_ls_mask"] and bs_file:
+                out_ls = str(out_dir / f"{master_prefix}_LS.dim")
 
         if coherence and not coh_file:
 
             # get info on slave from GeoSeries
-            slave_prefix = burst['slave_prefix']
-            slave_file = burst['slave_file']
-            slave_burst_nr = burst['slave_burst_nr']
+            slave_prefix = burst["slave_prefix"]
+            slave_file = burst["slave_file"]
+            slave_burst_nr = burst["slave_burst_nr"]
 
             with TemporaryDirectory(prefix=f"{str(temp_dir)}/") as temp:
 
@@ -480,8 +451,8 @@ def burst_to_ard(burst, config_file):
                 temp = Path(temp)
 
                 # import slave burst
-                slave_import = temp.joinpath(f'{slave_prefix}_import')
-                import_log = out_dir.joinpath(f'{slave_prefix}_import.err_log')
+                slave_import = temp / f"{slave_prefix}_import"
+                import_log = out_dir / f"{slave_prefix}_import.err_log"
 
                 try:
                     slc.burst_import(
@@ -490,30 +461,28 @@ def burst_to_ard(burst, config_file):
                         import_log,
                         swath,
                         slave_burst_nr,
-                        config_dict
+                        config_dict,
                     )
                 except (GPTRuntimeError, NotValidFileError) as error:
-                    if slave_import.with_suffix('.dim').exists():
+                    if slave_import.with_suffix(".dim").exists():
                         h.delete_dimap(slave_import)
 
                     logger.info(error)
                     return burst.bid, burst.Date, None, None, None, None, error
 
                 out_coh, error = create_coherence_layers(
-                    master_import.with_suffix('.dim'),
-                    slave_import.with_suffix('.dim'),
+                    master_import.with_suffix(".dim"),
+                    slave_import.with_suffix(".dim"),
                     out_dir,
                     master_prefix,
-                    config_dict
+                    config_dict,
                 )
 
                 # remove master import
                 h.delete_dimap(master_import)
 
         elif coherence and coh_file:
-            out_coh = str(
-                out_dir.joinpath(f'{master_prefix}_coh').with_suffix('.dim')
-            )
+            out_coh = str(out_dir / f"{master_prefix}_coh.dim")
 
             # remove master import
             h.delete_dimap(master_import)
@@ -524,25 +493,17 @@ def burst_to_ard(burst, config_file):
     # in case everything has been already processed,
     # we re-construct the out names for proper return value
     else:
-        if ard['H-A-Alpha'] and pol_file:
-            out_pol = str(
-                out_dir.joinpath(f'{master_prefix}_pol').with_suffix('.dim')
-            )
+        if ard["H-A-Alpha"] and pol_file:
+            out_pol = str(out_dir / f"{master_prefix}_pol.dim")
 
-        if ard['backscatter'] and bs_file:
-            out_bs = str(
-                out_dir.joinpath(f'{master_prefix}_bs').with_suffix('.dim')
-            )
+        if ard["backscatter"] and bs_file:
+            out_bs = str(out_dir / f"{master_prefix}_bs.dim")
 
-        if ard['create_ls_mask'] and bs_file:
-            out_ls = str(
-                out_dir.joinpath(f'{master_prefix}_LS').with_suffix('.dim')
-            )
+        if ard["create_ls_mask"] and bs_file:
+            out_ls = str(out_dir / f"{master_prefix}_LS.dim")
 
         if coherence and coh_file:
-            out_coh = str(
-                out_dir.joinpath(f'{master_prefix}_coh').with_suffix('.dim')
-            )
+            out_coh = str(out_dir / f"{master_prefix}_coh.dim")
 
     return burst.bid, burst.Date, out_bs, out_ls, out_pol, out_coh, None
 
@@ -567,13 +528,18 @@ def burst_to_ard(burst, config_file):
     parser = argparse.ArgumentParser(description=descript, epilog=epilog)
 
     # search parameters
-    parser.add_argument('-b', '--burst',
-                        help=' (str) path to OST burst inventory file for'
-                             ' one burst',
-                        required=True)
-    parser.add_argument('-c', '--config_file',
-                        help=' (str) path to OST project configuration file',
-                        required=True)
+    parser.add_argument(
+        "-b",
+        "--burst",
+        help=" (str) path to OST burst inventory file for" " one burst",
+        required=True,
+    )
+    parser.add_argument(
+        "-c",
+        "--config_file",
+        help=" (str) path to OST project configuration file",
+        required=True,
+    )
 
     args = parser.parse_args()
 
diff --git a/ost/s1/download.py b/ost/s1/download.py
index a2c385ae..881fdbd5 100644
--- a/ost/s1/download.py
+++ b/ost/s1/download.py
@@ -15,7 +15,7 @@
 
 from ost.s1.s1scene import Sentinel1Scene as S1Scene
 from ost.helpers import helpers as h
-from ost.helpers import scihub, peps, asf, onda   # , asf_wget
+from ost.helpers import scihub, peps, asf, onda  # , asf_wget
 
 logger = logging.getLogger(__name__)
 
@@ -39,7 +39,7 @@ def restore_download_dir(input_directory, download_dir):
     if isinstance(download_dir, str):
         download_dir = Path(download_dir)
 
-    for scene in list(input_directory.glob('*zip')):
+    for scene in list(input_directory.glob("*zip")):
 
         # get scene
         s1scene = S1Scene(scene.name[:-4])
@@ -48,30 +48,23 @@ def restore_download_dir(input_directory, download_dir):
         file_path = s1scene.download_path(download_dir, True)
 
         # check zipfile
-        logger.info(f'Checking zip file {str(scene)} for inconsistency.')
+        logger.info(f"Checking zip file {str(scene)} for inconsistency.")
         zip_test = h.check_zipfile(str(scene))
-        
+
         if not zip_test:
 
-            logger.info('Passed zip file test.')
+            logger.info("Passed zip file test.")
             scene.rename(file_path)
-        
+
             # add downloaded (should be zip checked in future)
-            with open(file_path.with_suffix('.downloaded'), 'w+') as file:
-                file.write('successfully downloaded \n')
+            with open(file_path.with_suffix(".downloaded"), "w+") as file:
+                file.write("successfully downloaded \n")
         else:
-            logger.info(
-                f'File {str(scene)} is corrupted and will not be moved.'
-            )
+            logger.info(f"File {str(scene)} is corrupted and will not be moved.")
 
 
 def download_sentinel1(
-        inventory_df,
-        download_dir,
-        mirror=None,
-        concurrent=2,
-        uname=None,
-        pword=None
+    inventory_df, download_dir, mirror=None, concurrent=2, uname=None, pword=None
 ):
     """Function to download Sentinel-1 with choice of data repository
 
@@ -94,25 +87,25 @@ def download_sentinel1(
     """
 
     if not mirror:
-        print(' Select the server from where you want to download:')
-        print(' (1) Copernicus Apihub (ESA, rolling archive)')
-        print(' (2) Alaska Satellite Facility (NASA, full archive)')
-        print(' (3) PEPS (CNES, 1 year rolling archive)')
+        print(" Select the server from where you want to download:")
+        print(" (1) Copernicus Apihub (ESA, rolling archive)")
+        print(" (2) Alaska Satellite Facility (NASA, full archive)")
+        print(" (3) PEPS (CNES, 1 year rolling archive)")
         print(
-            ' (4) ONDA DIAS (ONDA DIAS full archive for SLC -'
-            ' or GRD from 30 June 2019)'
+            " (4) ONDA DIAS (ONDA DIAS full archive for SLC -"
+            " or GRD from 30 June 2019)"
         )
         # print(' (5) Alaska Satellite Facility (using WGET - '
         # 'unstable - use only if 2 does not work)')
-        mirror = input(' Type 1, 2, 3, or 4: ')
+        mirror = input(" Type 1, 2, 3, or 4: ")
 
     if not uname:
-        print(' Please provide username for the selected server')
-        uname = input(' Username:')
+        print(" Please provide username for the selected server")
+        uname = input(" Username:")
 
     if not pword:
-        print(' Please provide password for the selected server')
-        pword = getpass.getpass(' Password:')
+        print(" Please provide password for the selected server")
+        pword = getpass.getpass(" Password:")
 
     # check if uname and pwoed are correct
     if int(mirror) == 1:
@@ -122,11 +115,11 @@ def download_sentinel1(
 
         if concurrent > 10:
             logger.info(
-                'Maximum allowed parallel downloads from Earthdata are 10. '
-                'Setting concurrent accordingly.'
+                "Maximum allowed parallel downloads from Earthdata are 10. "
+                "Setting concurrent accordingly."
             )
             concurrent = 10
-    
+
     elif int(mirror) == 3:
         error_code = peps.check_connection(uname, pword)
     elif int(mirror) == 4:
@@ -136,33 +129,34 @@ def download_sentinel1(
     # hidden option for downloading from czech mirror
     elif int(mirror) == 321:
         error_code = scihub.check_connection(
-           uname, pword, base_url='https://dhr1.cesnet.cz/'
+            uname, pword, base_url="https://dhr1.cesnet.cz/"
         )
     else:
-        raise ValueError('No valid mirror selected')
+        raise ValueError("No valid mirror selected")
 
     if error_code == 401:
-        raise ValueError('Username/Password are incorrect')
+        raise ValueError("Username/Password are incorrect")
     elif error_code != 200:
-        raise ValueError(f'Some connection error. Error code {error_code}.')
-    
+        raise ValueError(f"Some connection error. Error code {error_code}.")
+
     # download in parallel
-    if int(mirror) == 1:    # scihub
-        scihub.batch_download(inventory_df, download_dir,
-                              uname, pword, concurrent)
-    elif int(mirror) == 2:    # ASF
-        asf.batch_download(inventory_df, download_dir,
-                           uname, pword, concurrent)
-    elif int(mirror) == 3:   # PEPS
-        peps.batch_download(inventory_df, download_dir,
-                            uname, pword, concurrent)
-    elif int(mirror) == 4:   # ONDA DIAS
-        onda.batch_download(inventory_df, download_dir,
-                            uname, pword, concurrent)
-    if int(mirror) == 321:    # scihub czech mirror
-        scihub.batch_download(inventory_df, download_dir,
-                              uname, pword, concurrent,
-                              base_url='https://dhr1.cesnet.cz/')
+    if int(mirror) == 1:  # scihub
+        scihub.batch_download(inventory_df, download_dir, uname, pword, concurrent)
+    elif int(mirror) == 2:  # ASF
+        asf.batch_download(inventory_df, download_dir, uname, pword, concurrent)
+    elif int(mirror) == 3:  # PEPS
+        peps.batch_download(inventory_df, download_dir, uname, pword, concurrent)
+    elif int(mirror) == 4:  # ONDA DIAS
+        onda.batch_download(inventory_df, download_dir, uname, pword, concurrent)
+    if int(mirror) == 321:  # scihub czech mirror
+        scihub.batch_download(
+            inventory_df,
+            download_dir,
+            uname,
+            pword,
+            concurrent,
+            base_url="https://dhr1.cesnet.cz/",
+        )
     # elif int(mirror) == 5:    # ASF WGET
     #    asf_wget.batch_download(inventory_df, download_dir,
     #                            uname, pword, concurrent)
diff --git a/ost/s1/grd_batch.py b/ost/s1/grd_batch.py
index 2c931030..79e42249 100644
--- a/ost/s1/grd_batch.py
+++ b/ost/s1/grd_batch.py
@@ -41,45 +41,46 @@ def _create_processing_dict(inventory_df):
     dict_scenes = {}
 
     # get relative orbits and loop through each
-    track_list = inventory_df['relativeorbit'].unique()
+    track_list = inventory_df["relativeorbit"].unique()
 
     for track in track_list:
 
         # get acquisition dates and loop through each
-        acquisition_dates = inventory_df['acquisitiondate'][
-            inventory_df['relativeorbit'] == track].unique()
+        acquisition_dates = inventory_df["acquisitiondate"][
+            inventory_df["relativeorbit"] == track
+        ].unique()
 
         # loop through dates
         for i, acquisition_date in enumerate(acquisition_dates):
 
             # get the scene ids per acquisition_date and write into a list
-            single_id = inventory_df['identifier'][
-                (inventory_df['relativeorbit'] == track) &
-                (inventory_df['acquisitiondate'] == acquisition_date)
+            single_id = inventory_df["identifier"][
+                (inventory_df["relativeorbit"] == track)
+                & (inventory_df["acquisitiondate"] == acquisition_date)
             ].tolist()
 
             # add this list to the dictionary and associate the track number
             # as dict key
-            dict_scenes[f'{track}_{i+1}'] = single_id
+            dict_scenes[f"{track}_{i+1}"] = single_id
 
     return dict_scenes
 
 
 def create_processed_df(inventory_df, list_of_scenes, outfile, out_ls, error):
 
-    df = pd.DataFrame(columns=['identifier', 'outfile', 'out_ls', 'error'])
+    df = pd.DataFrame(columns=["identifier", "outfile", "out_ls", "error"])
 
     for scene in list_of_scenes:
 
         temp_df = pd.DataFrame()
         # get scene_id
-        temp_df['identifier'] = inventory_df.identifier[
+        temp_df["identifier"] = inventory_df.identifier[
             inventory_df.identifier == scene
         ].values
         # fill outfiles/error
-        temp_df['outfile'] = outfile
-        temp_df['out_ls'] = out_ls
-        temp_df['error'] = error
+        temp_df["outfile"] = outfile
+        temp_df["out_ls"] = out_ls
+        temp_df["error"] = error
 
         # append to final df and delete temp_df for next loop
         df = df.append(temp_df)
@@ -91,38 +92,39 @@ def create_processed_df(inventory_df, list_of_scenes, outfile, out_ls, error):
 def grd_to_ard_batch(inventory_df, config_file):
 
     # load relevant config parameters
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        download_dir = Path(config_dict['download_dir'])
-        data_mount = Path(config_dict['data_mount'])
+        download_dir = Path(config_dict["download_dir"])
+        data_mount = Path(config_dict["data_mount"])
 
     # where all frames are grouped into acquisitions
     processing_dict = _create_processing_dict(inventory_df)
-    processing_df = pd.DataFrame(
-        columns=['identifier', 'outfile', 'out_ls', 'error']
-    )
+    processing_df = pd.DataFrame(columns=["identifier", "outfile", "out_ls", "error"])
 
     iter_list = []
     for _, list_of_scenes in processing_dict.items():
 
         # get the paths to the file
-        scene_paths = (
-            [Sentinel1Scene(scene).get_path(download_dir, data_mount)
-             for scene in list_of_scenes]
-        )
+        scene_paths = [
+            Sentinel1Scene(scene).get_path(download_dir, data_mount)
+            for scene in list_of_scenes
+        ]
 
         iter_list.append(scene_paths)
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=config_dict['max_workers']
+        executor=config_dict["executor_type"], max_workers=config_dict["max_workers"]
     )
 
     for task in executor.as_completed(
         func=grd_to_ard.grd_to_ard,
         iterable=iter_list,
-        fargs=([str(config_file), ])
+        fargs=(
+            [
+                str(config_file),
+            ]
+        ),
     ):
 
         list_of_scenes, outfile, out_ls, error = task.result()
@@ -141,14 +143,14 @@ def ards_to_timeseries(inventory_df, config_file):
 
     with open(config_file) as file:
         config_dict = json.load(file)
-        ard = config_dict['processing']['single_ARD']
-        ard_mt = config_dict['processing']['time-series_ARD']
+        ard = config_dict["processing"]["single_ARD"]
+        ard_mt = config_dict["processing"]["time-series_ARD"]
 
     # create all extents
     _create_extents(inventory_df, config_file)
 
     # update extents in case of ls_mask
-    if ard['create_ls_mask'] or ard_mt['apply_ls_mask']:
+    if ard["create_ls_mask"] or ard_mt["apply_ls_mask"]:
         _create_mt_ls_mask(inventory_df, config_file)
 
     # finally create time-series
@@ -157,81 +159,85 @@ def ards_to_timeseries(inventory_df, config_file):
 
 def _create_extents(inventory_df, config_file):
 
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        processing_dir = Path(config_dict['processing_dir'])
+        processing_dir = Path(config_dict["processing_dir"])
 
     iter_list = []
     for track in inventory_df.relativeorbit.unique():
 
         # get the burst directory
-        track_dir = processing_dir.joinpath(track)
+        track_dir = processing_dir / track
 
-        list_of_extents = list(track_dir.glob('*/*/*bounds.json'))
+        list_of_extents = list(track_dir.glob("*/*/*bounds.json"))
 
         # if extent does not already exist, add to iterable
-        if not track_dir.joinpath(f'{track}.min_bounds.json').exists():
+        if not (track_dir / f"{track}.min_bounds.json").exists():
             iter_list.append(list_of_extents)
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=os.cpu_count()
+        executor=config_dict["executor_type"], max_workers=os.cpu_count()
     )
 
-    out_dict = {'track': [], 'list_of_scenes': [], 'extent': []}
+    out_dict = {"track": [], "list_of_scenes": [], "extent": []}
     for task in executor.as_completed(
-            func=ts_extent.mt_extent,
-            iterable=iter_list,
-            fargs=([str(config_file), ])
+        func=ts_extent.mt_extent,
+        iterable=iter_list,
+        fargs=(
+            [
+                str(config_file),
+            ]
+        ),
     ):
         track, list_of_scenes, extent = task.result()
-        out_dict['track'].append(track)
-        out_dict['list_of_scenes'].append(list_of_scenes)
-        out_dict['extent'].append(extent)
+        out_dict["track"].append(track)
+        out_dict["list_of_scenes"].append(list_of_scenes)
+        out_dict["extent"].append(extent)
 
     return pd.DataFrame.from_dict(out_dict)
 
 
 def _create_extents_old(inventory_df, config_file):
 
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        processing_dir = Path(config_dict['processing_dir'])
+        processing_dir = Path(config_dict["processing_dir"])
 
     iter_list = []
     for track in inventory_df.relativeorbit.unique():
 
         # get the burst directory
-        track_dir = processing_dir.joinpath(track)
+        track_dir = processing_dir / track
 
         # get common burst extent
-        list_of_scenes = list(track_dir.glob('**/*img'))
+        list_of_scenes = list(track_dir.glob("**/*img"))
 
-        list_of_scenes = [
-            str(x) for x in list_of_scenes if 'layover' not in str(x)
-        ]
+        list_of_scenes = [str(x) for x in list_of_scenes if "layover" not in str(x)]
 
         # if extent does not already exist, add to iterable
-        if not track_dir.joinpath(f'{track}.extent.gpkg').exists():
+        if not (track_dir / f"{track}.extent.gpkg").exists():
             iter_list.append(list_of_scenes)
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=config_dict['max_workers']
+        executor=config_dict["executor_type"], max_workers=config_dict["max_workers"]
     )
 
-    out_dict = {'track': [], 'list_of_scenes': [], 'extent': []}
+    out_dict = {"track": [], "list_of_scenes": [], "extent": []}
     for task in executor.as_completed(
-            func=ts_extent.mt_extent,
-            iterable=iter_list,
-            fargs=([str(config_file), ])
+        func=ts_extent.mt_extent,
+        iterable=iter_list,
+        fargs=(
+            [
+                str(config_file),
+            ]
+        ),
     ):
         track, list_of_scenes, extent = task.result()
-        out_dict['track'].append(track)
-        out_dict['list_of_scenes'].append(list_of_scenes)
-        out_dict['extent'].append(extent)
+        out_dict["track"].append(track)
+        out_dict["list_of_scenes"].append(list_of_scenes)
+        out_dict["extent"].append(extent)
 
     return pd.DataFrame.from_dict(out_dict)
 
@@ -245,76 +251,71 @@ def _create_mt_ls_mask(inventory_df, config_file):
     :param config_file:
     :return:
     """
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        processing_dir = Path(config_dict['processing_dir'])
+        processing_dir = Path(config_dict["processing_dir"])
 
     iter_list = []
     for track in inventory_df.relativeorbit.unique():
 
         # get the burst directory
-        track_dir = processing_dir.joinpath(track)
+        track_dir = processing_dir / track
 
         # get common burst extent
-        list_of_masks = list(track_dir.glob('*/*/*_ls_mask.json'))
+        list_of_masks = list(track_dir.glob("*/*/*_ls_mask.json"))
 
         # if extent does not already exist, add to iterable
-        if not track_dir.joinpath(f'{track}.ls_mask.json').exists():
+        if not (track_dir / f"{track}.ls_mask.json").exists():
             iter_list.append(list_of_masks)
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=os.cpu_count()
+        executor=config_dict["executor_type"], max_workers=os.cpu_count()
     )
 
-    for task in executor.as_completed(
-            func=ts_ls_mask.mt_layover,
-            iterable=iter_list
-    ):
+    for task in executor.as_completed(func=ts_ls_mask.mt_layover, iterable=iter_list):
         task.result()
 
 
 def _create_mt_ls_mask_old(inventory_df, config_file):
 
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        processing_dir = Path(config_dict['processing_dir'])
+        processing_dir = Path(config_dict["processing_dir"])
 
     iter_list = []
     for track in inventory_df.relativeorbit.unique():
 
         # get the burst directory
-        track_dir = processing_dir.joinpath(track)
+        track_dir = processing_dir / track
 
         # get common burst extent
-        list_of_scenes = list(track_dir.glob('**/*img'))
+        list_of_scenes = list(track_dir.glob("**/*img"))
 
-        list_of_layover = [
-            str(x) for x in list_of_scenes if 'layover' in str(x)
-        ]
+        list_of_layover = [str(x) for x in list_of_scenes if "layover" in str(x)]
 
         iter_list.append(list_of_layover)
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=config_dict['max_workers']
+        executor=config_dict["executor_type"], max_workers=config_dict["max_workers"]
     )
 
-    out_dict = {
-        'track': [], 'list_of_layover': [], 'ls_mask': [], 'ls_extent': []
-    }
+    out_dict = {"track": [], "list_of_layover": [], "ls_mask": [], "ls_extent": []}
     for task in executor.as_completed(
-            func=ts_ls_mask.mt_layover,
-            iterable=iter_list,
-            fargs=([str(config_file), ])
+        func=ts_ls_mask.mt_layover,
+        iterable=iter_list,
+        fargs=(
+            [
+                str(config_file),
+            ]
+        ),
     ):
         track, list_of_layover, ls_mask, ls_extent = task.result()
-        out_dict['track'].append(track)
-        out_dict['list_of_layover'].append(list_of_layover)
-        out_dict['ls_mask'].append(list_of_layover)
-        out_dict['ls_extent'].append(ls_extent)
+        out_dict["track"].append(track)
+        out_dict["list_of_layover"].append(list_of_layover)
+        out_dict["ls_mask"].append(list_of_layover)
+        out_dict["ls_extent"].append(ls_extent)
 
     return pd.DataFrame.from_dict(out_dict)
 
@@ -349,23 +350,21 @@ def _create_timeseries(inventory_df, config_file):
     :type str/Path
     :return:
     """
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        processing_dir = Path(config_dict['processing_dir'])
+        processing_dir = Path(config_dict["processing_dir"])
 
     iter_list = []
     for track in inventory_df.relativeorbit.unique():
 
         # get the burst directory
-        track_dir = processing_dir.joinpath(track)
+        track_dir = processing_dir / track
 
-        for pol in ['VV', 'VH', 'HH', 'HV']:
+        for pol in ["VV", "VH", "HH", "HV"]:
 
             # see if there is actually any imagery in thi polarisation
             list_of_files = sorted(
-                str(file) for file in list(
-                    track_dir.glob(f'20*/*data*/*ma0*{pol}*img')
-                )
+                str(file) for file in list(track_dir.glob(f"20*/*data*/*ma0*{pol}*img"))
             )
 
             if len(list_of_files) <= 1:
@@ -373,32 +372,39 @@ def _create_timeseries(inventory_df, config_file):
 
             # create list of dims if polarisation is present
             list_of_dims = sorted(
-                str(dim) for dim in list(track_dir.glob('20*/*bs*dim'))
+                str(dim) for dim in list(track_dir.glob("20*/*bs*dim"))
             )
 
-            iter_list.append([list_of_dims, track, 'bs', pol])
+            iter_list.append([list_of_dims, track, "bs", pol])
 
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=config_dict['max_workers']
+        executor=config_dict["executor_type"], max_workers=config_dict["max_workers"]
     )
 
     out_dict = {
-        'track': [], 'list_of_dims': [], 'out_files': [],
-        'out_vrt': [], 'product': [], 'error': []
+        "track": [],
+        "list_of_dims": [],
+        "out_files": [],
+        "out_vrt": [],
+        "product": [],
+        "error": [],
     }
     for task in executor.as_completed(
-            func=ard_to_ts.gd_ard_to_ts,
-            iterable=iter_list,
-            fargs=([str(config_file), ])
+        func=ard_to_ts.gd_ard_to_ts,
+        iterable=iter_list,
+        fargs=(
+            [
+                str(config_file),
+            ]
+        ),
     ):
         track, list_of_dims, out_files, out_vrt, product, error = task.result()
-        out_dict['track'].append(track)
-        out_dict['list_of_dims'].append(list_of_dims)
-        out_dict['out_files'].append(out_files)
-        out_dict['out_vrt'].append(out_vrt)
-        out_dict['product'].append(product)
-        out_dict['error'].append(error)
+        out_dict["track"].append(track)
+        out_dict["list_of_dims"].append(list_of_dims)
+        out_dict["out_files"].append(out_files)
+        out_dict["out_vrt"].append(out_vrt)
+        out_dict["product"].append(product)
+        out_dict["error"].append(error)
 
     return pd.DataFrame.from_dict(out_dict)
 
@@ -406,88 +412,92 @@ def _create_timeseries(inventory_df, config_file):
 def timeseries_to_timescan(inventory_df, config_file):
 
     # load ard parameters
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        processing_dir = Path(config_dict['processing_dir'])
-        ard = config_dict['processing']['single_ARD']
-        ard_mt = config_dict['processing']['time-series_ARD']
-        ard_tscan = config_dict['processing']['time-scan_ARD']
+        processing_dir = Path(config_dict["processing_dir"])
+        ard = config_dict["processing"]["single_ARD"]
+        ard_mt = config_dict["processing"]["time-series_ARD"]
+        ard_tscan = config_dict["processing"]["time-scan_ARD"]
 
     # get the db scaling right
-    to_db = ard['to_db']
-    if ard['to_db'] or ard_mt['to_db']:
+    to_db = ard["to_db"]
+    if ard["to_db"] or ard_mt["to_db"]:
         to_db = True
 
-    dtype_conversion = True if ard_mt['dtype_output'] != 'float32' else False
+    dtype_conversion = True if ard_mt["dtype_output"] != "float32" else False
 
     iter_list, vrt_iter_list = [], []
     for track in inventory_df.relativeorbit.unique():
 
         # get track directory
-        track_dir = processing_dir.joinpath(track)
+        track_dir = processing_dir / track
         # define and create Timescan directory
-        timescan_dir = track_dir.joinpath('Timescan')
+        timescan_dir = track_dir / "Timescan"
         timescan_dir.mkdir(parents=True, exist_ok=True)
 
         # loop thorugh each polarization
-        for polar in ['VV', 'VH', 'HH', 'HV']:
+        for polar in ["VV", "VH", "HH", "HV"]:
 
-            if timescan_dir.joinpath(f'.bs.{polar}.processed').exists():
-                logger.info(f'Timescans for track {track} already processed.')
+            if (timescan_dir / f".bs.{polar}.processed").exists():
+                logger.info(f"Timescans for track {track} already processed.")
                 continue
 
             # get timeseries vrt
-            time_series = track_dir.joinpath(
-                f'Timeseries/Timeseries.bs.{polar}.vrt'
-            )
+            time_series = track_dir / "Timeseries" / f"Timeseries.bs.{polar}.vrt"
 
             if not time_series.exists():
                 continue
 
             # create a datelist for harmonics
-            scene_list = list(track_dir.glob(f'Timeseries/*bs.{polar}.tif'))
+            scene_list = list(track_dir.glob(f"Timeseries/*bs.{polar}.tif"))
 
             # create a datelist for harmonics calculation
             datelist = []
             for file in sorted(scene_list):
-                datelist.append(file.name.split('.')[1])
+                datelist.append(file.name.split(".")[1])
 
             # define timescan prefix
-            timescan_prefix = timescan_dir.joinpath(f'bs.{polar}')
-
-            iter_list.append([
-                time_series, timescan_prefix, ard_tscan['metrics'],
-                dtype_conversion, to_db, ard_tscan['remove_outliers'],
-                datelist
-            ])
+            timescan_prefix = timescan_dir / f"bs.{polar}"
+
+            iter_list.append(
+                [
+                    time_series,
+                    timescan_prefix,
+                    ard_tscan["metrics"],
+                    dtype_conversion,
+                    to_db,
+                    ard_tscan["remove_outliers"],
+                    datelist,
+                ]
+            )
 
         vrt_iter_list.append(timescan_dir)
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=config_dict['max_workers']
+        executor=config_dict["executor_type"], max_workers=config_dict["max_workers"]
     )
 
     # run timescan creation
-    out_dict = {'track': [], 'prefix': [], 'metrics': [], 'error': []}
-    for task in executor.as_completed(
-            func=timescan.gd_mt_metrics,
-            iterable=iter_list
-    ):
+    out_dict = {"track": [], "prefix": [], "metrics": [], "error": []}
+    for task in executor.as_completed(func=timescan.gd_mt_metrics, iterable=iter_list):
         burst, prefix, metrics, error = task.result()
-        out_dict['track'].append(burst)
-        out_dict['prefix'].append(prefix)
-        out_dict['metrics'].append(metrics)
-        out_dict['error'].append(error)
+        out_dict["track"].append(burst)
+        out_dict["prefix"].append(prefix)
+        out_dict["metrics"].append(metrics)
+        out_dict["error"].append(error)
 
     timescan_df = pd.DataFrame.from_dict(out_dict)
 
     # run vrt creation
     for task in executor.as_completed(
-            func=ras.create_tscan_vrt,
-            iterable=vrt_iter_list,
-            fargs=([str(config_file), ])
+        func=ras.create_tscan_vrt,
+        iterable=vrt_iter_list,
+        fargs=(
+            [
+                str(config_file),
+            ]
+        ),
     ):
         task.result()
 
@@ -496,30 +506,28 @@ def timeseries_to_timescan(inventory_df, config_file):
 
 def mosaic_timeseries(inventory_df, config_file):
 
-    print(' -----------------------------------')
-    logger.info('Mosaicking Time-series layers')
-    print(' -----------------------------------')
+    print(" -----------------------------------")
+    logger.info("Mosaicking Time-series layers")
+    print(" -----------------------------------")
 
     # -------------------------------------
     # 1 load project config
-    with open(config_file, 'r') as ard_file:
+    with open(config_file, "r") as ard_file:
         config_dict = json.load(ard_file)
-        processing_dir = Path(config_dict['processing_dir'])
+        processing_dir = Path(config_dict["processing_dir"])
 
     # create output folder
-    ts_dir = processing_dir.joinpath('Mosaic/Timeseries')
+    ts_dir = processing_dir / "Mosaic" / "Timeseries"
     ts_dir.mkdir(parents=True, exist_ok=True)
 
     # loop through polarisations
     iter_list, vrt_iter_list = [], []
-    for p in ['VV', 'VH', 'HH', 'HV']:
+    for p in ["VV", "VH", "HH", "HV"]:
 
         tracks = inventory_df.relativeorbit.unique()
-        nr_of_ts = len(list(
-            processing_dir.joinpath(
-                f'{tracks[0]}/Timeseries'
-            ).glob(f'*.{p}.tif')
-        ))
+        nr_of_ts = len(
+            list((processing_dir / f"{tracks[0]}" / "Timeseries").glob(f"*.{p}.tif"))
+        )
 
         if not nr_of_ts >= 1:
             continue
@@ -527,60 +535,47 @@ def mosaic_timeseries(inventory_df, config_file):
         outfiles = []
         for i in range(1, nr_of_ts + 1):
 
-            filelist = list(
-                processing_dir.glob(f'*/Timeseries/{i:02d}.*.{p}.tif')
-            )
-            filelist = [
-                str(file) for file in filelist if 'Mosaic' not in str(file)
-            ]
+            filelist = list(processing_dir.glob(f"*/Timeseries/{i:02d}.*.{p}.tif"))
+            filelist = [str(file) for file in filelist if "Mosaic" not in str(file)]
 
             # create
             datelist = []
             for file in filelist:
-                datelist.append(Path(file).name.split('.')[1])
+                datelist.append(Path(file).name.split(".")[1])
 
-            filelist = ' '.join(filelist)
+            filelist = " ".join(filelist)
             start, end = sorted(datelist)[0], sorted(datelist)[-1]
 
             if start == end:
-                outfile = ts_dir.joinpath(f'{i:02d}.{start}.bs.{p}.tif')
+                outfile = ts_dir / f"{i:02d}.{start}.bs.{p}.tif"
             else:
-                outfile = ts_dir.joinpath(f'{i:02d}.{start}-{end}.bs.{p}.tif')
+                outfile = ts_dir / f"{i:02d}.{start}-{end}.bs.{p}.tif"
 
-            check_file = outfile.parent.joinpath(
-                f'.{outfile.stem}.processed'
-            )
+            check_file = outfile.parent / f".{outfile.stem}.processed"
 
             outfiles.append(outfile)
 
             if check_file.exists():
-                logger.info(
-                    f'Mosaic layer {outfile.name} already processed.'
-                )
+                logger.info(f"Mosaic layer {outfile.name} already processed.")
                 continue
 
-            logger.info(f'Mosaicking layer {outfile.name}.')
+            logger.info(f"Mosaicking layer {outfile.name}.")
             iter_list.append([filelist, outfile, config_file])
 
         vrt_iter_list.append([ts_dir, p, outfiles])
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=config_dict['max_workers']
+        executor=config_dict["executor_type"], max_workers=config_dict["max_workers"]
     )
 
     # run mosaicking
-    for task in executor.as_completed(
-            func=mosaic.gd_mosaic,
-            iterable=iter_list
-    ):
+    for task in executor.as_completed(func=mosaic.gd_mosaic, iterable=iter_list):
         task.result()
 
     # run mosaicking vrts
     for task in executor.as_completed(
-            func=mosaic.create_timeseries_mosaic_vrt,
-            iterable=vrt_iter_list
+        func=mosaic.create_timeseries_mosaic_vrt, iterable=vrt_iter_list
     ):
         task.result()
 
@@ -588,60 +583,52 @@ def mosaic_timeseries(inventory_df, config_file):
 def mosaic_timescan(config_file):
 
     # load ard parameters
-    with open(config_file, 'r') as ard_file:
+    with open(config_file, "r") as ard_file:
         config_dict = json.load(ard_file)
-        processing_dir = Path(config_dict['processing_dir'])
-        metrics = config_dict['processing']['time-scan_ARD']['metrics']
+        processing_dir = Path(config_dict["processing_dir"])
+        metrics = config_dict["processing"]["time-scan_ARD"]["metrics"]
 
-    if 'harmonics' in metrics:
-        metrics.remove('harmonics')
-        metrics.extend(['amplitude', 'phase', 'residuals'])
+    if "harmonics" in metrics:
+        metrics.remove("harmonics")
+        metrics.extend(["amplitude", "phase", "residuals"])
 
-    if 'percentiles' in metrics:
-        metrics.remove('percentiles')
-        metrics.extend(['p95', 'p5'])
+    if "percentiles" in metrics:
+        metrics.remove("percentiles")
+        metrics.extend(["p95", "p5"])
 
     # create out directory of not existent
-    tscan_dir = processing_dir.joinpath('Mosaic/Timescan')
+    tscan_dir = processing_dir / "Mosaic" / "Timescan"
     tscan_dir.mkdir(parents=True, exist_ok=True)
 
     # loop through all pontial proucts
     iter_list = []
-    for polar, metric in itertools.product(['VV', 'HH', 'VH', 'HV'], metrics):
+    for polar, metric in itertools.product(["VV", "HH", "VH", "HV"], metrics):
 
         # create a list of files based on polarisation and metric
-        filelist = list(processing_dir.glob(
-            f'*/Timescan/*bs.{polar}.{metric}.tif'
-        ))
+        filelist = list(processing_dir.glob(f"*/Timescan/*bs.{polar}.{metric}.tif"))
 
         # break loop if there are no files
         if not len(filelist) >= 2:
             continue
 
         # get number
-        filelist = ' '.join([str(file) for file in filelist])
-        outfile = tscan_dir.joinpath(f'bs.{polar}.{metric}.tif')
-        check_file = outfile.parent.joinpath(f'.{outfile.stem}.processed')
+        filelist = " ".join([str(file) for file in filelist])
+        outfile = tscan_dir / f"bs.{polar}.{metric}.tif"
+        check_file = outfile.parent / f".{outfile.stem}.processed"
 
         if check_file.exists():
-            logger.info(
-                f'Mosaic layer {outfile.name} already processed.'
-            )
+            logger.info(f"Mosaic layer {outfile.name} already processed.")
             continue
 
         iter_list.append([filelist, outfile, config_file])
 
     # now we run with godale, which works also with 1 worker
     executor = Executor(
-        executor=config_dict['executor_type'],
-        max_workers=config_dict['max_workers']
+        executor=config_dict["executor_type"], max_workers=config_dict["max_workers"]
     )
 
     # run mosaicking
-    for task in executor.as_completed(
-            func=mosaic.gd_mosaic,
-            iterable=iter_list
-    ):
+    for task in executor.as_completed(func=mosaic.gd_mosaic, iterable=iter_list):
         task.result()
 
     ras.create_tscan_vrt(tscan_dir, config_file)
diff --git a/ost/s1/grd_to_ard.py b/ost/s1/grd_to_ard.py
index 73373d5b..bb4f7927 100644
--- a/ost/s1/grd_to_ard.py
+++ b/ost/s1/grd_to_ard.py
@@ -38,11 +38,11 @@ def grd_to_ard(filelist, config_file):
 
     # ----------------------------------------------------
     # 1 load relevant config parameters
-    with open(config_file, 'r') as file:
+    with open(config_file, "r") as file:
         config_dict = json.load(file)
-        ard = config_dict['processing']['single_ARD']
-        processing_dir = Path(config_dict['processing_dir'])
-        subset = config_dict['subset']
+        ard = config_dict["processing"]["single_ARD"]
+        processing_dir = Path(config_dict["processing_dir"])
+        subset = config_dict["subset"]
 
     # ----------------------------------------------------
     # 2 define final destination dir/file and ls mask
@@ -52,26 +52,23 @@ def grd_to_ard(filelist, config_file):
     acquisition_date = first.start_date
     track = first.rel_orbit
 
-    logger.info(
-        f'Processing acquisition from {acquisition_date} over track {track}.'
-    )
+    logger.info(f"Processing acquisition from {acquisition_date} over track {track}.")
 
     # construct namespace for out directory etc.
-    out_dir = processing_dir.joinpath(f'{track}/{acquisition_date}')
+    out_dir = processing_dir / f"{track}/{acquisition_date}"
     out_dir.mkdir(parents=True, exist_ok=True)
-    file_id = f'{acquisition_date}_{track}'
-    out_final = out_dir.joinpath(f'{file_id}_bs')
-    out_ls_mask = out_dir.joinpath(f'{file_id}_LS')
+    file_id = f"{acquisition_date}_{track}"
+    out_final = out_dir / f"{file_id}_bs"
+    out_ls_mask = out_dir / f"{file_id}_LS"
 
-    suf = '.tif' if ard['to_tif'] else '.dim'
+    suf = ".tif" if ard["to_tif"] else ".dim"
 
     # ----------------------------------------------------
     # 3 check if already processed
-    if out_dir.joinpath('.processed').exists() and \
-            out_final.with_suffix(suf).exists():
+    if (out_dir / ".processed").exists() and out_final.with_suffix(suf).exists():
         logger.info(
-            f'Acquisition from {acquisition_date} of track {track} '
-            f'already processed'
+            f"Acquisition from {acquisition_date} of track {track} "
+            f"already processed"
         )
 
         if out_ls_mask.with_suffix(suf).exists():
@@ -85,11 +82,11 @@ def grd_to_ard(filelist, config_file):
     # 4 run the processing routine
 
     # this might happen in the create_ard from s1scene class
-    if not config_dict['temp_dir']:
-        temp_dir = processing_dir.joinpath('temp')
+    if not config_dict["temp_dir"]:
+        temp_dir = processing_dir / "temp"
         temp_dir.mkdir(parents=True, exist_ok=True)
     else:
-        temp_dir = config_dict['temp_dir']
+        temp_dir = config_dict["temp_dir"]
 
     with TemporaryDirectory(prefix=f"{temp_dir}/") as temp:
 
@@ -106,31 +103,29 @@ def grd_to_ard(filelist, config_file):
 
                 # unzip for faster import?
                 unpack = None
-                if Path(file).suffix == '.zip':
-                    with zipfile.ZipFile(file, 'r') as zip_ref:
+                if Path(file).suffix == ".zip":
+                    with zipfile.ZipFile(file, "r") as zip_ref:
                         zip_ref.extractall(temp)
 
-                    file = temp.joinpath(f'{file.stem}.SAFE')
+                    file = temp / f"{file.stem}.SAFE"
                     unpack = True
 
                 # create namespace for temporary imported product
-                grd_import = temp.joinpath(f'{file.stem}_imported')
+                grd_import = temp / f"{file.stem}_imported"
 
                 # create namespace for import log
-                logfile = out_dir.joinpath(f'{file.stem}.Import.errLog')
+                logfile = out_dir / f"{file.stem}.Import.errLog"
 
                 # set subset temporally to false for import routine
-                config_dict['subset'] = False
+                config_dict["subset"] = False
                 # frame import
                 try:
-                    grd.grd_frame_import(
-                        file, grd_import, logfile, config_dict
-                    )
+                    grd.grd_frame_import(file, grd_import, logfile, config_dict)
                 except (GPTRuntimeError, NotValidFileError) as error:
                     logger.info(error)
                     return filelist, None, None, error
 
-                config_dict['subset'] = subset
+                config_dict["subset"] = subset
 
                 if unpack:
                     h.remove_folder_content(file)
@@ -138,15 +133,15 @@ def grd_to_ard(filelist, config_file):
 
             # create list of scenes for full acquisition in
             # preparation of slice assembly
-            scenelist = ' '.join(
-                [str(file) for file in list(temp.glob('*imported.dim'))]
+            scenelist = " ".join(
+                [str(file) for file in list(temp.glob("*imported.dim"))]
             )
 
             # create namespace for temporary slice assembled import product
-            grd_import = temp.joinpath(f'{file_id}_imported')
+            grd_import = temp / f"{file_id}_imported"
 
             # create namespace for slice assembled log
-            logfile = out_dir.joinpath(f'{file_id}._slice_assembly.errLog')
+            logfile = out_dir / f"{file_id}._slice_assembly.errLog"
 
             # run slice assembly
             try:
@@ -157,22 +152,21 @@ def grd_to_ard(filelist, config_file):
 
             # delete imported frames
             for file in filelist:
-                h.delete_dimap(temp.joinpath(f'{file.stem}_imported'))
+                h.delete_dimap(temp / f"{file.stem}_imported")
 
             # subset mode after slice assembly
             if subset:
 
                 # create namespace for temporary subset product
-                grd_subset = temp.joinpath(f'{file_id}_imported_subset')
+                grd_subset = temp / f"{file_id}_imported_subset"
 
                 # create namespace for subset log
-                logfile = out_dir.joinpath(f'{file_id}._slice_assembly.errLog')
+                logfile = out_dir / f"{file_id}._slice_assembly.errLog"
 
                 # run subset routine
                 try:
                     grd.grd_subset_georegion(
-                        grd_import.with_suffix('.dim'), grd_subset, logfile,
-                        config_dict
+                        grd_import.with_suffix(".dim"), grd_subset, logfile, config_dict
                     )
                 except (GPTRuntimeError, NotValidFileError) as error:
                     logger.info(error)
@@ -191,18 +185,18 @@ def grd_to_ard(filelist, config_file):
 
             # unzip for faster import
             unpack = None
-            if Path(file).suffix == '.zip':
-                with zipfile.ZipFile(file, 'r') as zip_ref:
+            if Path(file).suffix == ".zip":
+                with zipfile.ZipFile(file, "r") as zip_ref:
                     zip_ref.extractall(temp)
 
-                file = temp.joinpath(f'{file.stem}.SAFE')
+                file = temp / f"{file.stem}.SAFE"
                 unpack = True
 
             # create namespace for temporary imported product
-            grd_import = temp.joinpath(f'{file_id}_imported')
+            grd_import = temp / f"{file_id}_imported"
 
             # create namespace for import log
-            logfile = out_dir.joinpath(f'{file_id}.Import.errLog')
+            logfile = out_dir / f"{file_id}.Import.errLog"
 
             # run frame import
             try:
@@ -216,19 +210,19 @@ def grd_to_ard(filelist, config_file):
                 file.rmdir()
 
         # set input for next step
-        infile = grd_import.with_suffix('.dim')
+        infile = grd_import.with_suffix(".dim")
 
         # ---------------------------------------------------------------------
         # 4.2 GRD Border Noise
-        if ard['remove_border_noise'] and not subset:
+        if ard["remove_border_noise"] and not subset:
 
             # loop through possible polarisations
-            for polarisation in ['VV', 'VH', 'HH', 'HV']:
+            for polarisation in ["VV", "VH", "HH", "HV"]:
 
                 # get input file
-                file = list(temp.glob(
-                    f'{file_id}_imported*data/Intensity_{polarisation}.img'
-                ))
+                file = list(
+                    temp.glob(f"{file_id}_imported*data/Intensity_{polarisation}.img")
+                )
 
                 # remove border noise
                 if len(file) == 1:
@@ -239,10 +233,10 @@ def grd_to_ard(filelist, config_file):
         # 4.3 Calibration
 
         # create namespace for temporary calibrated product
-        calibrated = temp.joinpath(f'{file_id}_cal')
+        calibrated = temp / f"{file_id}_cal"
 
         # create namespace for calibration log
-        logfile = out_dir.joinpath(f'{file_id}.calibration.errLog')
+        logfile = out_dir / f"{file_id}.calibration.errLog"
 
         # run calibration
         try:
@@ -252,20 +246,20 @@ def grd_to_ard(filelist, config_file):
             return filelist, None, None, error
 
         # delete input
-        h.delete_dimap(infile.with_suffix(''))
+        h.delete_dimap(infile.with_suffix(""))
 
         # input for next step
-        infile = calibrated.with_suffix('.dim')
+        infile = calibrated.with_suffix(".dim")
 
         # ---------------------------------------------------------------------
         # 4.4 Multi-looking
-        if int(ard['resolution']) >= 20:
+        if int(ard["resolution"]) >= 20:
 
             # create namespace for temporary multi-looked product
-            multi_looked = temp.joinpath(f'{file_id}_ml')
+            multi_looked = temp / f"{file_id}_ml"
 
             # create namespace for multi-loook log
-            logfile = out_dir.joinpath(f'{file_id}.multilook.errLog')
+            logfile = out_dir / f"{file_id}.multilook.errLog"
 
             # run multi-looking
             try:
@@ -275,43 +269,43 @@ def grd_to_ard(filelist, config_file):
                 return filelist, None, None, error
 
             # delete input
-            h.delete_dimap(infile.with_suffix(''))
+            h.delete_dimap(infile.with_suffix(""))
 
             # define input for next step
-            infile = multi_looked.with_suffix('.dim')
+            infile = multi_looked.with_suffix(".dim")
 
         # ---------------------------------------------------------------------
         # 4.5 Layover shadow mask
         out_ls = None  # set to none for final return statement
-        if ard['create_ls_mask'] is True:
+        if ard["create_ls_mask"] is True:
 
             # create namespace for temporary ls mask product
-            ls_mask = temp.joinpath(f'{file_id}_ls_mask')
+            ls_mask = temp / f"{file_id}_ls_mask"
 
             # create namespace for ls mask log
-            logfile = out_dir.joinpath(f'{file_id}.ls_mask.errLog')
+            logfile = out_dir / f"{file_id}.ls_mask.errLog"
 
             # run ls mask routine
             try:
                 common.ls_mask(infile, ls_mask, logfile, config_dict)
-                out_ls = out_ls_mask.with_suffix('.dim')
+                out_ls = out_ls_mask.with_suffix(".dim")
             except (GPTRuntimeError, NotValidFileError) as error:
                 logger.info(error)
                 return filelist, None, None, error
 
             # polygonize
-            ls_raster = list(ls_mask.with_suffix('.data').glob('*img'))[0]
-            ras.polygonize_ls(ls_raster, ls_mask.with_suffix('.json'))
+            ls_raster = list(ls_mask.with_suffix(".data").glob("*img"))[0]
+            ras.polygonize_ls(ls_raster, ls_mask.with_suffix(".json"))
 
         # ---------------------------------------------------------------------
         # 4.6 Speckle filtering
-        if ard['remove_speckle']:
+        if ard["remove_speckle"]:
 
             # create namespace for temporary speckle filtered product
-            filtered = temp.joinpath(f'{file_id}_spk')
+            filtered = temp / f"{file_id}_spk"
 
             # create namespace for speckle filter log
-            logfile = out_dir.joinpath(f'{file_id}.Speckle.errLog')
+            logfile = out_dir / f"{file_id}.Speckle.errLog"
 
             # run speckle filter
             try:
@@ -321,45 +315,43 @@ def grd_to_ard(filelist, config_file):
                 return filelist, None, None, error
 
             # delete input
-            h.delete_dimap(infile.with_suffix(''))
+            h.delete_dimap(infile.with_suffix(""))
 
             # define input for next step
-            infile = filtered.with_suffix('.dim')
+            infile = filtered.with_suffix(".dim")
 
         # ---------------------------------------------------------------------
         # 4.7 Terrain flattening
-        if ard['product_type'] == 'RTC-gamma0':
+        if ard["product_type"] == "RTC-gamma0":
 
             # create namespace for temporary terrain flattened product
-            flattened = temp.joinpath(f'{file_id}_flat')
+            flattened = temp / f"{file_id}_flat"
 
             # create namespace for terrain flattening log
-            logfile = out_dir.joinpath(f'{file_id}.tf.errLog')
+            logfile = out_dir / f"{file_id}.tf.errLog"
 
             # run terrain flattening
             try:
-                common.terrain_flattening(
-                    infile, flattened, logfile, config_dict
-                )
+                common.terrain_flattening(infile, flattened, logfile, config_dict)
             except (GPTRuntimeError, NotValidFileError) as error:
                 logger.info(error)
                 return filelist, None, None, error
 
             # delete input file
-            h.delete_dimap(infile.with_suffix(''))
+            h.delete_dimap(infile.with_suffix(""))
 
             # define input for next step
-            infile = flattened.with_suffix('.dim')
+            infile = flattened.with_suffix(".dim")
 
         # ---------------------------------------------------------------------
         # 4.8 Linear to db
-        if ard['to_db']:
+        if ard["to_db"]:
 
             # create namespace for temporary db scaled product
-            db_scaled = temp.joinpath(f'{file_id}_db')
+            db_scaled = temp / f"{file_id}_db"
 
             # create namespace for db scaled log
-            logfile = out_dir.joinpath(f'{file_id}.db.errLog')
+            logfile = out_dir / f"{file_id}.db.errLog"
 
             # run db scaling routine
             try:
@@ -369,19 +361,19 @@ def grd_to_ard(filelist, config_file):
                 return filelist, None, None, error
 
             # delete input file
-            h.delete_dimap(infile.with_suffix(''))
+            h.delete_dimap(infile.with_suffix(""))
 
             # set input for next step
-            infile = db_scaled.with_suffix('.dim')
+            infile = db_scaled.with_suffix(".dim")
 
         # ---------------------------------------------------------------------
         # 4.9 Geocoding
 
         # create namespace for temporary geocoded product
-        geocoded = temp.joinpath(f'{file_id}_bs')
+        geocoded = temp / f"{file_id}_bs"
 
         # create namespace for geocoding log
-        logfile = out_dir.joinpath(f'{file_id}_bs.errLog')
+        logfile = out_dir / f"{file_id}_bs.errLog"
 
         # run geocoding
         try:
@@ -391,54 +383,55 @@ def grd_to_ard(filelist, config_file):
             return filelist, None, None, error
 
         # delete input file
-        h.delete_dimap(infile.with_suffix(''))
+        h.delete_dimap(infile.with_suffix(""))
 
         # define final destination
-        out_final = out_dir.joinpath(f'{file_id}_bs')
+        out_final = out_dir / f"{file_id}_bs"
 
         # ---------------------------------------------------------------------
         # 4.11 Create an outline
-        ras.image_bounds(geocoded.with_suffix('.data'))
+        ras.image_bounds(geocoded.with_suffix(".data"))
 
         # ---------------------------------------------------------------------
         # 4.11 Copy LS Mask vector to data dir
-        if ard['create_ls_mask'] is True:
-            ls_mask.with_suffix('.json').rename(
-                geocoded.with_suffix('.data')
-                .joinpath(ls_mask.name).with_suffix('.json')
+        if ard["create_ls_mask"] is True:
+            ls_mask.with_suffix(".json").rename(
+                geocoded.with_suffix(".data")
+                .joinpath(ls_mask.name)
+                .with_suffix(".json")
             )
 
         # ---------------------------------------------------------------------
         # 4.12 Move to output directory
-        h.move_dimap(geocoded, out_final, ard['to_tif'])
+        h.move_dimap(geocoded, out_final, ard["to_tif"])
 
     # ---------------------------------------------------------------------
     # 5 write processed file to keep track of files already processed
-    with open(out_dir.joinpath('.processed'), 'w') as file:
-        file.write('passed all tests \n')
+    with (out_dir / ".processed").open("w") as file:
+        file.write("passed all tests \n")
 
-    return filelist, out_final.with_suffix('.dim'), out_ls, None
+    return filelist, out_final.with_suffix(".dim"), out_ls, None
 
 
-def ard_to_rgb(infile, outfile, driver='GTiff', to_db=True, shrink_factor=1):
-    if infile.suffix != '.dim':
-        raise TypeError('File needs to be in BEAM-DIMAP format')
+def ard_to_rgb(infile, outfile, driver="GTiff", to_db=True, shrink_factor=1):
+    if infile.suffix != ".dim":
+        raise TypeError("File needs to be in BEAM-DIMAP format")
 
-    data_dir = infile.with_suffix('.data')
+    data_dir = infile.with_suffix(".data")
 
-    if list(data_dir.glob('*VV.img')):
-        co_pol = list(data_dir.glob('*VV*.img'))[0]
-    elif list(data_dir.glob('*HH.img')):
-        co_pol = list(data_dir.glob('*HH*.img'))[0]
+    if list(data_dir.glob("*VV.img")):
+        co_pol = list(data_dir.glob("*VV*.img"))[0]
+    elif list(data_dir.glob("*HH.img")):
+        co_pol = list(data_dir.glob("*HH*.img"))[0]
     else:
-        raise RuntimeError('No co-polarised band found.')
+        raise RuntimeError("No co-polarised band found.")
 
-    if list(data_dir.glob('*VH.img')):
-        cross_pol = list(data_dir.glob('*VH*.img'))[0]
-    elif list(data_dir.glob('*HV.img')):
-        cross_pol = list(data_dir.glob('*HV*.img'))[0]
+    if list(data_dir.glob("*VH.img")):
+        cross_pol = list(data_dir.glob("*VH*.img"))[0]
+    elif list(data_dir.glob("*HV.img")):
+        cross_pol = list(data_dir.glob("*HV*.img"))[0]
     else:
-        cross_pol = Path('/no/foo/no')
+        cross_pol = Path("/no/foo/no")
 
     if cross_pol.exists():
 
@@ -454,8 +447,8 @@ def ard_to_rgb(infile, outfile, driver='GTiff', to_db=True, shrink_factor=1):
 
                 if co.shape != cr.shape:
                     raise RuntimeError(
-                        'Dimensions of co- and cross-polarised bands '
-                        'do not match')
+                        "Dimensions of co- and cross-polarised bands " "do not match"
+                    )
 
                 new_height = int(co.height / shrink_factor)
                 new_width = int(co.width / shrink_factor)
@@ -478,25 +471,26 @@ def ard_to_rgb(infile, outfile, driver='GTiff', to_db=True, shrink_factor=1):
                     co_array = ras.convert_to_db(co_array)
                     cr_array = ras.convert_to_db(cr_array)
 
-                if driver == 'JPEG':
-                    co_array = ras.scale_to_int(co_array, -20, 0, 'uint8')
-                    cr_array = ras.scale_to_int(cr_array, -25, -5, 'uint8')
-                    ratio_array = ras.scale_to_int(ratio_array, 1, 15, 'uint8')
-                    meta.update(dtype='uint8')
+                if driver == "JPEG":
+                    co_array = ras.scale_to_int(co_array, -20, 0, "uint8")
+                    cr_array = ras.scale_to_int(cr_array, -25, -5, "uint8")
+                    ratio_array = ras.scale_to_int(ratio_array, 1, 15, "uint8")
+                    meta.update(dtype="uint8")
 
-                with rasterio.open(outfile, 'w', **meta) as dst:
+                with rasterio.open(outfile, "w", **meta) as dst:
 
                     # write file
-                    for k, arr in [
-                        (1, co_array), (2, cr_array), (3, ratio_array)
-                    ]:
-                        dst.write(arr[0, ], indexes=k)
+                    for k, arr in [(1, co_array), (2, cr_array), (3, ratio_array)]:
+                        dst.write(
+                            arr[
+                                0,
+                            ],
+                            indexes=k,
+                        )
 
     # greyscale
     else:
-        logger.info(
-            'No cross-polarised band found. Creating 1-band greyscale'
-            'image.')
+        logger.info("No cross-polarised band found. Creating 1-band greyscale" "image.")
 
         with rasterio.open(co_pol) as co:
 
@@ -518,9 +512,9 @@ def ard_to_rgb(infile, outfile, driver='GTiff', to_db=True, shrink_factor=1):
                 # turn to db
                 co_array = ras.convert_to_db(co_array)
 
-            if driver == 'JPEG':
-                co_array = ras.scale_to_int(co_array, -20, 0, 'uint8')
-                meta.update(dtype='uint8')
+            if driver == "JPEG":
+                co_array = ras.scale_to_int(co_array, -20, 0, "uint8")
+                meta.update(dtype="uint8")
 
-            with rasterio.open(outfile, 'w', **meta) as dst:
+            with rasterio.open(outfile, "w", **meta) as dst:
                 dst.write(co_array)
diff --git a/ost/s1/grd_wrappers.py b/ost/s1/grd_wrappers.py
index 65c5ae70..4a179882 100644
--- a/ost/s1/grd_wrappers.py
+++ b/ost/s1/grd_wrappers.py
@@ -5,13 +5,7 @@
 import logging
 from retrying import retry
 from pathlib import Path
-
-try:
-    import gdal
-except ModuleNotFoundError as e:
-    from osgeo import gdal
-except ModuleNotFoundError as e:
-    raise e
+from osgeo import gdal
 
 from ost.helpers import helpers as h
 from ost.helpers.errors import GPTRuntimeError, NotValidFileError
@@ -44,41 +38,41 @@ def grd_frame_import(infile, outfile, logfile, config_dict):
         infile = Path(infile)
 
     # get relevant config parameters
-    ard = config_dict['processing']['single_ARD']
-    polars = ard['polarisation'].replace(' ', '')
-    cpus = config_dict['snap_cpu_parallelism']
-    subset = config_dict['subset']
+    ard = config_dict["processing"]["single_ARD"]
+    polars = ard["polarisation"].replace(" ", "")
+    cpus = config_dict["snap_cpu_parallelism"]
+    subset = config_dict["subset"]
 
     try:
-        aoi = config_dict['aoi']
+        aoi = config_dict["aoi"]
     except KeyError:
-        aoi = ''
+        aoi = ""
 
     logger.debug(
-        f'Importing {infile.name} by applying precise orbit file and '
-        f'removing thermal noise'
+        f"Importing {infile.name} by applying precise orbit file and "
+        f"removing thermal noise"
     )
 
     # get path to graph
     if subset:
-        graph = OST_ROOT.joinpath('graphs/S1_GRD2ARD/1_AO_TNR_SUB.xml')
+        graph = OST_ROOT / "graphs" / "S1_GRD2ARD" / "1_AO_TNR_SUB.xml"
         # construct command
         command = (
-            f'{GPT_FILE} {graph} -x -q {2 * cpus} '
-            f'-Pinput=\'{str(infile)}\' '
-            f'-Pregion=\'{aoi}\' '
-            f'-Ppolarisation={polars} '
-            f'-Poutput=\'{str(outfile)}\''
+            f"{GPT_FILE} {graph} -x -q {2 * cpus} "
+            f"-Pinput='{str(infile)}' "
+            f"-Pregion='{aoi}' "
+            f"-Ppolarisation={polars} "
+            f"-Poutput='{str(outfile)}'"
         )
     else:
         # construct path ot graph
-        graph = OST_ROOT.joinpath('graphs/S1_GRD2ARD/1_AO_TNR.xml')
+        graph = OST_ROOT / "graphs" / "S1_GRD2ARD" / "1_AO_TNR.xml"
         # construct command
         command = (
-            f'{GPT_FILE} {graph} -x -q {2 * cpus} '
-            f'-Pinput=\'{str(infile)}\' '
-            f'-Ppolarisation={polars} '
-            f'-Poutput=\'{str(outfile)}\''
+            f"{GPT_FILE} {graph} -x -q {2 * cpus} "
+            f"-Pinput='{str(infile)}' "
+            f"-Ppolarisation={polars} "
+            f"-Poutput='{str(outfile)}'"
         )
 
     # run command
@@ -86,22 +80,20 @@ def grd_frame_import(infile, outfile, logfile, config_dict):
 
     # handle errors and logs
     if return_code == 0:
-        logger.debug('Succesfully imported GRD product')
+        logger.debug("Succesfully imported GRD product")
     else:
         # read logfile
         raise GPTRuntimeError(
-            f'GRD frame import exited with error {return_code}. '
-            f'See {logfile} for Snap\'s error output.'
+            f"GRD frame import exited with error {return_code}. "
+            f"See {logfile} for Snap's error output."
         )
 
     # do check routine
     return_code = h.check_out_dimap(outfile)
     if return_code == 0:
-        return str(outfile.with_suffix('.dim'))
+        return str(outfile.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
@@ -117,7 +109,7 @@ def slice_assembly(filelist, outfile, logfile, config_dict):
     :return:
     """
 
-    '''A wrapper of SNAP's slice assembly routine
+    """A wrapper of SNAP's slice assembly routine
 
     This function assembles consecutive frames acquired at the same date.
     Can be either GRD or SLC products
@@ -129,20 +121,20 @@ def slice_assembly(filelist, outfile, logfile, config_dict):
                  file written in BEAM-Dimap format
         logfile: string or os.path object for the file
                  where SNAP'S STDOUT/STDERR is written to
-    '''
+    """
 
     # get relevant config parameters
-    ard = config_dict['processing']['single_ARD']
-    polars = ard['polarisation'].replace(' ', '')
-    cpus = config_dict['snap_cpu_parallelism']
+    ard = config_dict["processing"]["single_ARD"]
+    polars = ard["polarisation"].replace(" ", "")
+    cpus = config_dict["snap_cpu_parallelism"]
 
-    logger.debug('Assembling consecutive frames:')
+    logger.debug("Assembling consecutive frames:")
 
     # construct command
     command = (
-        f'{GPT_FILE} SliceAssembly -x -q {2*cpus} '
-        f'-PselectedPolarisations={polars} '
-        f'-t \'{str(outfile)}\' {filelist}'
+        f"{GPT_FILE} SliceAssembly -x -q {2*cpus} "
+        f"-PselectedPolarisations={polars} "
+        f"-t '{str(outfile)}' {filelist}"
     )
 
     # run command and get return code
@@ -150,21 +142,19 @@ def slice_assembly(filelist, outfile, logfile, config_dict):
 
     # handle errors and logs
     if return_code == 0:
-        logger.debug('Succesfully assembled products')
+        logger.debug("Succesfully assembled products")
     else:
         raise GPTRuntimeError(
-            f'ERROR: Slice Assembly exited with error {return_code}. '
-            f'See {logfile} for Snap Error output'
+            f"ERROR: Slice Assembly exited with error {return_code}. "
+            f"See {logfile} for Snap Error output"
         )
 
     # do check routine
     return_code = h.check_out_dimap(outfile)
     if return_code == 0:
-        return str(outfile.with_suffix('.dim'))
+        return str(outfile.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
@@ -182,22 +172,22 @@ def grd_subset_georegion(infile, outfile, logfile, config_dict):
     """
 
     # get relevant config parameters
-    cpus = config_dict['snap_cpu_parallelism']
+    cpus = config_dict["snap_cpu_parallelism"]
 
     try:
-        aoi = config_dict['aoi']
+        aoi = config_dict["aoi"]
     except KeyError:
-        aoi = ''
+        aoi = ""
 
-    logger.debug('Subsetting imported imagery.')
+    logger.debug("Subsetting imported imagery.")
 
     # extract window from scene
     command = (
-        f'{GPT_FILE} Subset -x -q {2*cpus} '
-        f'-PcopyMetadata=true '
-        f'-PgeoRegion=\'{aoi}\' '
-        f'-Ssource=\'{str(infile)}\' '
-        f'-t \'{str(outfile)}\''
+        f"{GPT_FILE} Subset -x -q {2*cpus} "
+        f"-PcopyMetadata=true "
+        f"-PgeoRegion='{aoi}' "
+        f"-Ssource='{str(infile)}' "
+        f"-t '{str(outfile)}'"
     )
 
     # run command and get return code
@@ -205,21 +195,19 @@ def grd_subset_georegion(infile, outfile, logfile, config_dict):
 
     # handle errors and logs
     if return_code == 0:
-        logger.debug('Succesfully subsetted product.')
+        logger.debug("Succesfully subsetted product.")
     else:
         raise GPTRuntimeError(
-            f'Subsetting exited with error {return_code}. '
-            f'See {logfile} for Snap\'s error message.'
+            f"Subsetting exited with error {return_code}. "
+            f"See {logfile} for Snap's error message."
         )
 
     # do check routine
     return_code = h.check_out_dimap(outfile)
     if return_code == 0:
-        return str(outfile.with_suffix('.dim'))
+        return str(outfile.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
@@ -244,7 +232,7 @@ def grd_remove_border(infile):
     :return:
     """
 
-    logger.debug(f'Removing the GRD Border Noise for {infile.name}.')
+    logger.debug(f"Removing the GRD Border Noise for {infile.name}.")
     currtime = time.time()
 
     # read raster file and get number of columns adn rows
@@ -253,9 +241,7 @@ def grd_remove_border(infile):
     rows = raster.RasterYSize
 
     # create 3000xrows array for the left part of the image
-    array_left = np.array(
-        raster.GetRasterBand(1).ReadAsArray(0, 0, 3000, rows)
-    )
+    array_left = np.array(raster.GetRasterBand(1).ReadAsArray(0, 0, 3000, rows))
 
     cols_left = 3000
     for x in range(3000):
@@ -272,7 +258,7 @@ def grd_remove_border(infile):
             break
 
     # write array_left to disk
-    logger.debug(f'Number of colums set to 0 on the left side: {cols_left}.')
+    logger.debug(f"Number of colums set to 0 on the left side: {cols_left}.")
 
     raster.GetRasterBand(1).WriteArray(array_left[:, :+cols_left], 0, 0)
     del array_left
@@ -299,13 +285,9 @@ def grd_remove_border(infile):
             break
 
     col_right_start = cols - 3000 + cols_right
-    logger.debug(
-        f'Number of columns set to 0 on the right side: {3000 - cols_right}.'
-    )
-    logger.debug(f'Amount of columns kept: {col_right_start}.')
-    raster.GetRasterBand(1).WriteArray(
-        array_right[:, cols_right:], col_right_start, 0
-    )
+    logger.debug(f"Number of columns set to 0 on the right side: {3000 - cols_right}.")
+    logger.debug(f"Amount of columns kept: {col_right_start}.")
+    raster.GetRasterBand(1).WriteArray(array_right[:, cols_right:], col_right_start, 0)
 
     logger.debug(h.timer(currtime))
 
@@ -322,30 +304,30 @@ def calibration(infile, outfile, logfile, config_dict):
     """
 
     # get relevant config parameters
-    product_type = config_dict['processing']['single_ARD']['product_type']
-    cpus = config_dict['snap_cpu_parallelism']
+    product_type = config_dict["processing"]["single_ARD"]["product_type"]
+    cpus = config_dict["snap_cpu_parallelism"]
 
     # transform calibration parameter to snap readable
-    sigma0, beta0, gamma0 = 'false', 'false', 'false'
-
-    if product_type == 'GTC-sigma0':
-        sigma0 = 'true'
-    elif product_type == 'GTC-gamma0':
-        gamma0 = 'true'
-    elif product_type == 'RTC-gamma0':
-        beta0 = 'true'
+    sigma0, beta0, gamma0 = "false", "false", "false"
+
+    if product_type == "GTC-sigma0":
+        sigma0 = "true"
+    elif product_type == "GTC-gamma0":
+        gamma0 = "true"
+    elif product_type == "RTC-gamma0":
+        beta0 = "true"
     else:
-        raise TypeError('Wrong product type selected.')
+        raise TypeError("Wrong product type selected.")
 
-    logger.debug(f'Calibrating the product to {product_type}.')
+    logger.debug(f"Calibrating the product to {product_type}.")
 
     # construct command string
     command = (
-        f'{GPT_FILE} Calibration -x -q {2*cpus} '
-        f' -PoutputBetaBand=\'{beta0}\' '
-        f' -PoutputGammaBand=\'{gamma0}\' '
-        f' -PoutputSigmaBand=\'{sigma0}\' '
-        f' -t \'{str(outfile)}\' \'{str(infile)}\''
+        f"{GPT_FILE} Calibration -x -q {2*cpus} "
+        f" -PoutputBetaBand='{beta0}' "
+        f" -PoutputGammaBand='{gamma0}' "
+        f" -PoutputSigmaBand='{sigma0}' "
+        f" -t '{str(outfile)}' '{str(infile)}'"
     )
 
     # run command and get return code
@@ -353,21 +335,19 @@ def calibration(infile, outfile, logfile, config_dict):
 
     # handle errors and logs
     if return_code == 0:
-        logger.debug(f'Calibration to {product_type} successful.')
+        logger.debug(f"Calibration to {product_type} successful.")
     else:
         raise GPTRuntimeError(
-            f'Calibration exited with error {return_code}. '
-            f'See {logfile} for Snap\'s error message.'
+            f"Calibration exited with error {return_code}. "
+            f"See {logfile} for Snap's error message."
         )
 
     # do check routine
     return_code = h.check_out_dimap(outfile)
     if return_code == 0:
-        return str(outfile.with_suffix('.dim'))
+        return str(outfile.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
@@ -381,21 +361,21 @@ def multi_look(infile, outfile, logfile, config_dict):
     :return:
     """
 
-    ard = config_dict['processing']['single_ARD']
-    cpus = config_dict['snap_cpu_parallelism']
-    ml_factor = int(int(ard['resolution']) / 10)
+    ard = config_dict["processing"]["single_ARD"]
+    cpus = config_dict["snap_cpu_parallelism"]
+    ml_factor = int(int(ard["resolution"]) / 10)
 
     logger.debug(
-        'Multi-looking the image with {az_looks} looks in '
-        'azimuth and {rg_looks} looks in range.'
+        "Multi-looking the image with {az_looks} looks in "
+        "azimuth and {rg_looks} looks in range."
     )
 
     # construct command string
     command = (
-        f'{GPT_FILE} Multilook -x -q {2*cpus} '
-        f'-PnAzLooks={ml_factor} '
-        f'-PnRgLooks={ml_factor} '
-        f'-t \'{str(outfile)}\' {str(infile)}'
+        f"{GPT_FILE} Multilook -x -q {2*cpus} "
+        f"-PnAzLooks={ml_factor} "
+        f"-PnRgLooks={ml_factor} "
+        f"-t '{str(outfile)}' {str(infile)}"
     )
 
     # run command and get return code
@@ -403,18 +383,16 @@ def multi_look(infile, outfile, logfile, config_dict):
 
     # handle errors and logs
     if return_code == 0:
-        logger.debug('Succesfully multi-looked product.')
+        logger.debug("Succesfully multi-looked product.")
     else:
         raise GPTRuntimeError(
-            f' ERROR: Multi-look exited with error {return_code}. '
-            f'See {logfile} for Snap\'s error message.'
+            f" ERROR: Multi-look exited with error {return_code}. "
+            f"See {logfile} for Snap's error message."
         )
 
     # do check routine
     return_code = h.check_out_dimap(outfile)
     if return_code == 0:
-        return str(outfile.with_suffix('.dim'))
+        return str(outfile.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
diff --git a/ost/s1/refine_inventory.py b/ost/s1/refine_inventory.py
index 92f07c91..1bcfdaa3 100644
--- a/ost/s1/refine_inventory.py
+++ b/ost/s1/refine_inventory.py
@@ -1,6 +1,4 @@
-
 import itertools
-import fiona.crs
 import geopandas as gpd
 import logging
 import warnings
@@ -32,18 +30,17 @@ def _remove_double_entries(inventory_df):
 
     # filter footprint data frame for obit direction and polarisation &
     # get unique entries
-    idx = inventory_df.groupby(
-        inventory_df['identifier'].str.slice(0, 63)
-    )['ingestiondate'].transform(max) == inventory_df['ingestiondate']
+    idx = (
+        inventory_df.groupby(inventory_df["identifier"].str.slice(0, 63))[
+            "ingestiondate"
+        ].transform(max)
+        == inventory_df["ingestiondate"]
+    )
 
     # re-initialize GDF geometry due to groupby function
-    gdf = gpd.GeoDataFrame(
-        inventory_df[idx], geometry='geometry', crs='epsg:4326'
-    )
+    gdf = gpd.GeoDataFrame(inventory_df[idx], geometry="geometry", crs="epsg:4326")
 
-    logger.info(
-        f'{len(inventory_df[idx])} frames remain after double entry removal'
-    )
+    logger.info(f"{len(inventory_df[idx])} frames remain after double entry removal")
     return gdf
 
 
@@ -69,19 +66,17 @@ def _remove_outside_aoi(aoi_gdf, inventory_df):
     cols = inventory_df.columns
 
     # 1) get only intersecting footprints (double, since we do this before)
-    inventory_df = gpd.sjoin(
-        inventory_df, aoi_gdf, how='left', op='intersects'
-    )
+    inventory_df = gpd.sjoin(inventory_df, aoi_gdf, how="left", op="intersects")
 
     # if aoi  gdf has an id field we need to rename the changed id_left field
-    if 'id_left' in inventory_df.columns:
+    if "id_left" in inventory_df.columns:
         # rename id_left to id
         inventory_df.columns = [
-            'id' if x == 'id_left' else x for x in inventory_df.columns
+            "id" if x == "id_left" else x for x in inventory_df.columns
         ]
 
     # remove duplicates (in case of more than one polygon in AOI)
-    inventory_df.drop_duplicates('identifier', inplace=True)
+    inventory_df.drop_duplicates("identifier", inplace=True)
 
     return inventory_df[cols]
 
@@ -105,15 +100,19 @@ def _handle_equator_crossing(inventory_df):
     """
 
     # get the relativeorbitnumbers that change with equator crossing
-    tracks = inventory_df.lastrelativeorbitnumber[
-        inventory_df['relativeorbit'] != inventory_df[
-            'lastrelativeorbitnumber']].unique().tolist()
+    tracks = (
+        inventory_df.lastrelativeorbitnumber[
+            inventory_df["relativeorbit"] != inventory_df["lastrelativeorbitnumber"]
+        ]
+        .unique()
+        .tolist()
+    )
 
     for track in tracks:
 
         # get dates
         dates = inventory_df.acquisitiondate[
-            (inventory_df['relativeorbit'] == track)
+            (inventory_df["relativeorbit"] == track)
         ].unique()
 
         for date in dates:
@@ -131,12 +130,12 @@ def _handle_equator_crossing(inventory_df):
 
             # get index of
             idx = inventory_df[
-                (inventory_df['acquisitiondate'] == date) &
-                (inventory_df['relativeorbit'] == track)
+                (inventory_df["acquisitiondate"] == date)
+                & (inventory_df["relativeorbit"] == track)
             ].index
 
             # reset relative orbit number
-            inventory_df.set_value(idx, 'relativeorbit', str(int(track) - 1))
+            inventory_df.set_value(idx, "relativeorbit", str(int(track) - 1))
 
     return inventory_df
 
@@ -160,36 +159,33 @@ def _exclude_marginal_tracks(aoi_gdf, inventory_df, area_reduce=0.1):
 
     """
 
-    warnings.filterwarnings(
-        'ignore', 'Geometry is in a geographic CRS', UserWarning
-    )
+    warnings.filterwarnings("ignore", "Geometry is in a geographic CRS", UserWarning)
     # get Area of AOI
     aoi_area = aoi_gdf.area.sum()
 
     # create a list of tracks for that date (sometimes more than one)
-    tracklist = inventory_df['relativeorbit'].unique()
+    tracklist = inventory_df["relativeorbit"].unique()
 
     for track in tracklist:
 
         trackunion = inventory_df.geometry[
-            inventory_df['relativeorbit'] != track].unary_union
+            inventory_df["relativeorbit"] != track
+        ].unary_union
         intersect_track = aoi_gdf.geometry.intersection(trackunion).area.sum()
 
         if intersect_track >= aoi_area - area_reduce:
-            logger.info(f'Excluding track {track}')
-            inventory_refined = inventory_df[
-                inventory_df['relativeorbit'] != track]
+            logger.info(f"Excluding track {track}")
+            inventory_refined = inventory_df[inventory_df["relativeorbit"] != track]
 
     # see if there is actually any marginal track
     try:
         inventory_df = inventory_refined
-        logger.info(f'{len(inventory_df)} frames remain after non-AOI overlap')
+        logger.info(f"{len(inventory_df)} frames remain after non-AOI overlap")
     except NameError:
         pass
     else:
         logger.info(
-            'All remaining tracks fully overlap the AOI. '
-            'Not removing anything.'
+            "All remaining tracks fully overlap the AOI. " "Not removing anything."
         )
     return inventory_df
 
@@ -218,39 +214,39 @@ def _remove_incomplete_tracks(aoi_gdf, inventory_df):
     out_frame = gpd.GeoDataFrame(columns=inventory_df.columns)
 
     # create a list of tracks for that date (sometimes more than one)
-    tracklist = inventory_df['relativeorbit'].unique()
+    tracklist = inventory_df["relativeorbit"].unique()
 
     for track in tracklist:
 
         # get area of AOI intersect for all acq.s of this track
-        trackunion = inventory_df['geometry'][
-            inventory_df['relativeorbit'] == track
+        trackunion = inventory_df["geometry"][
+            inventory_df["relativeorbit"] == track
         ].unary_union
 
         intersect_track = aoi_gdf.geometry.intersection(trackunion).area.sum()
 
         # loop through dates
         for date in sorted(
-                inventory_df['acquisitiondate'][
-                    inventory_df['relativeorbit'] == track
-                ].unique(),
-                reverse=False
+            inventory_df["acquisitiondate"][
+                inventory_df["relativeorbit"] == track
+            ].unique(),
+            reverse=False,
         ):
 
-            gdf_date = inventory_df[(inventory_df['relativeorbit'] == track) &
-                                    (inventory_df['acquisitiondate'] == date)]
+            gdf_date = inventory_df[
+                (inventory_df["relativeorbit"] == track)
+                & (inventory_df["acquisitiondate"] == date)
+            ]
 
             # get area of AOI intersect for all acq.s of this track
             date_union = gdf_date.geometry.unary_union
-            intersect_date = aoi_gdf.geometry.intersection(
-                date_union).area.sum()
+            intersect_date = aoi_gdf.geometry.intersection(date_union).area.sum()
 
             if intersect_track <= intersect_date + 0.15:
                 out_frame = out_frame.append(gdf_date)
 
     logger.info(
-        f' {len(out_frame)} frames remain after'
-        f' removal of non-full AOI crossing'
+        f" {len(out_frame)} frames remain after" f" removal of non-full AOI crossing"
     )
     return out_frame
 
@@ -273,22 +269,24 @@ def _handle_non_continous_swath(inventory_df):
     """
 
     tracks = inventory_df.lastrelativeorbitnumber.unique()
-    inventory_df['slicenumber'] = inventory_df['slicenumber'].astype(int)
+    inventory_df["slicenumber"] = inventory_df["slicenumber"].astype(int)
 
     for track in tracks:
 
         dates = inventory_df.acquisitiondate[
-            inventory_df['relativeorbit'] == track].unique()
+            inventory_df["relativeorbit"] == track
+        ].unique()
 
         for date in dates:
 
             subdf = inventory_df[
-                (inventory_df['acquisitiondate'] == date) &
-                (inventory_df['relativeorbit'] == track)
-            ].sort_values('slicenumber')
+                (inventory_df["acquisitiondate"] == date)
+                & (inventory_df["relativeorbit"] == track)
+            ].sort_values("slicenumber")
 
-            if (len(subdf) <= int(subdf.slicenumber.max()) -
-                    int(subdf.slicenumber.min())):
+            if len(subdf) <= int(subdf.slicenumber.max()) - int(
+                subdf.slicenumber.min()
+            ):
 
                 i = 1
                 last_slice = int(subdf.slicenumber.min()) - 1
@@ -299,9 +297,9 @@ def _handle_non_continous_swath(inventory_df):
                         i += 1
 
                     uuid = row.uuid
-                    new_id = f'{row.relativeorbit}.{i}'
-                    idx = inventory_df[inventory_df['uuid'] == uuid].index
-                    inventory_df.set_value(idx, 'relativeorbit', new_id)
+                    new_id = f"{row.relativeorbit}.{i}"
+                    idx = inventory_df[inventory_df["uuid"] == uuid].index
+                    inventory_df.set_value(idx, "relativeorbit", new_id)
                     last_slice = row.slicenumber
 
     return inventory_df
@@ -324,9 +322,7 @@ def _forward_search(aoi_gdf, inventory_df, area_reduce=0):
     out_frame = gpd.GeoDataFrame(columns=inventory_df.columns)
 
     # loop through dates
-    for date in sorted(
-            inventory_df['acquisitiondate'].unique(), reverse=False
-    ):
+    for date in sorted(inventory_df["acquisitiondate"].unique(), reverse=False):
 
         # set starting date for curent mosaic
         if start_date is None:
@@ -337,14 +333,17 @@ def _forward_search(aoi_gdf, inventory_df, area_reduce=0):
                 break
 
         # create a list of tracks for that date (sometimes more than one)
-        tracklist = inventory_df['relativeorbit'][
-            (inventory_df['acquisitiondate'] == date)].unique()
+        tracklist = inventory_df["relativeorbit"][
+            (inventory_df["acquisitiondate"] == date)
+        ].unique()
 
         for track in tracklist:
 
             # get all footprints for each date
-            gdf = inventory_df[(inventory_df['acquisitiondate'] == date) &
-                               (inventory_df['relativeorbit'] == track)]
+            gdf = inventory_df[
+                (inventory_df["acquisitiondate"] == date)
+                & (inventory_df["relativeorbit"] == track)
+            ]
 
             # get a unified geometry for date/track combination
             union = gdf.geometry.unary_union
@@ -371,7 +370,7 @@ def _forward_search(aoi_gdf, inventory_df, area_reduce=0):
                 start_date = None
                 gdf_union = None
 
-    return datelist, gpd.GeoDataFrame(out_frame, geometry='geometry')
+    return datelist, gpd.GeoDataFrame(out_frame, geometry="geometry")
 
 
 def _backward_search(aoi_gdf, inventory_df, datelist, area_reduce=0):
@@ -397,8 +396,8 @@ def _backward_search(aoi_gdf, inventory_df, datelist, area_reduce=0):
 
         # extract scenes for single mosaics
         gdf = inventory_df[
-            (inventory_df['acquisitiondate'] <= dates[1]) &
-            (inventory_df['acquisitiondate'] >= dates[0])
+            (inventory_df["acquisitiondate"] <= dates[1])
+            & (inventory_df["acquisitiondate"] >= dates[0])
         ]
 
         # we create an emtpy list and fill with tracks used for the mosaic,
@@ -406,11 +405,10 @@ def _backward_search(aoi_gdf, inventory_df, datelist, area_reduce=0):
         included_tracks = []
 
         # loop through dates backwards
-        for date in sorted(gdf['acquisitiondate'].unique(), reverse=True):
+        for date in sorted(gdf["acquisitiondate"].unique(), reverse=True):
 
             # create a list of tracks for that date (sometimes more than one)
-            tracklist = gdf['relativeorbit'][
-                (gdf['acquisitiondate'] == date)].unique()
+            tracklist = gdf["relativeorbit"][(gdf["acquisitiondate"] == date)].unique()
 
             for track in tracklist:
 
@@ -421,14 +419,12 @@ def _backward_search(aoi_gdf, inventory_df, datelist, area_reduce=0):
 
                     # get all footprints for each date and track
                     track_gdf = gdf[
-                        (gdf['acquisitiondate'] == date) &
-                        (gdf['relativeorbit'] == track)
+                        (gdf["acquisitiondate"] == date)
+                        & (gdf["relativeorbit"] == track)
                     ]
 
                     # re-initialize GDF due to groupby fucntion
-                    track_gdf = gpd.GeoDataFrame(
-                        track_gdf, geometry='geometry'
-                    )
+                    track_gdf = gpd.GeoDataFrame(track_gdf, geometry="geometry")
 
                     # get a unified geometry for date/track combination
                     union = track_gdf.geometry.unary_union
@@ -453,9 +449,7 @@ def _backward_search(aoi_gdf, inventory_df, datelist, area_reduce=0):
                     if intersect_area >= aoi_area - area_reduce:
                         # cleanup scenes
                         out_frame = out_frame.append(temp_df)
-                        temp_df = gpd.GeoDataFrame(
-                            columns=inventory_df.columns
-                        )
+                        temp_df = gpd.GeoDataFrame(columns=inventory_df.columns)
                         gdf_union = None
 
                         # stop for loop
@@ -465,19 +459,19 @@ def _backward_search(aoi_gdf, inventory_df, datelist, area_reduce=0):
             if intersect_area >= aoi_area - area_reduce:
                 break
 
-    return gpd.GeoDataFrame(out_frame, geometry='geometry', crs='epsg:4326')
+    return gpd.GeoDataFrame(out_frame, geometry="geometry", crs="epsg:4326")
 
 
 def search_refinement(
-        aoi,
-        inventory_df,
-        inventory_dir,
-        exclude_marginal=True,
-        full_aoi_crossing=True,
-        mosaic_refine=True,
-        area_reduce=0.05,
-        complete_coverage=True
-    ):
+    aoi,
+    inventory_df,
+    inventory_dir,
+    exclude_marginal=True,
+    full_aoi_crossing=True,
+    mosaic_refine=True,
+    area_reduce=0.05,
+    complete_coverage=True,
+):
     """
     :param aoi:
     :param inventory_df:
@@ -500,19 +494,17 @@ def search_refinement(
 
     """
 
-    warnings.filterwarnings(
-        'ignore', 'Geometry is in a geographic CRS', UserWarning
-    )
+    warnings.filterwarnings("ignore", "Geometry is in a geographic CRS", UserWarning)
 
     # create AOI GeoDataframe and calulate area
     aoi_gdf = vec.wkt_to_gdf(aoi)
     aoi_area = aoi_gdf.area.sum()
 
     # get all polarisations apparent in the inventory
-    pols = inventory_df['polarisationmode'].unique()
+    pols = inventory_df["polarisationmode"].unique()
 
     # get orbit directions apparent in the inventory
-    orbit_directions = inventory_df['orbitdirection'].unique()
+    orbit_directions = inventory_df["orbitdirection"].unique()
 
     # create inventoryDict
     inventory_dict = {}
@@ -521,16 +513,20 @@ def search_refinement(
     # loop through all possible combinations
     for pol, orb in itertools.product(pols, orbit_directions):
 
-        logger.info('Coverage analysis for {} tracks in {} polarisation.'
-                    .format(orb, pol))
+        logger.info(
+            "Coverage analysis for {} tracks in {} polarisation.".format(orb, pol)
+        )
 
         # subset the footprint for orbit direction and polarisations
         inv_df_sorted = inventory_df[
-            (inventory_df['polarisationmode'] == pol) &
-            (inventory_df['orbitdirection'] == orb)]
+            (inventory_df["polarisationmode"] == pol)
+            & (inventory_df["orbitdirection"] == orb)
+        ]
 
-        logger.info('{} frames for {} tracks in {} polarisation.'.format(
-            len(inv_df_sorted), orb, pol)
+        logger.info(
+            "{} frames for {} tracks in {} polarisation.".format(
+                len(inv_df_sorted), orb, pol
+            )
         )
 
         # calculate intersected area
@@ -539,7 +535,7 @@ def search_refinement(
 
         # we do a first check if the scenes do not fully cover the AOI
         if (intersect_area <= aoi_area - area_reduce) and complete_coverage:
-            print(' WARNING: Set of footprints does not fully cover AOI. ')
+            print(" WARNING: Set of footprints does not fully cover AOI. ")
 
         # otherwise we go on
         else:
@@ -549,7 +545,7 @@ def search_refinement(
 
             inventory_refined = _remove_outside_aoi(aoi_gdf, inventory_refined)
 
-            if orb == 'ASCENDING':
+            if orb == "ASCENDING":
                 inventory_refined = _handle_equator_crossing(inventory_refined)
 
             # get number of tracks
@@ -562,7 +558,8 @@ def search_refinement(
 
             if full_aoi_crossing is True:
                 inventory_refined = _remove_incomplete_tracks(
-                    aoi_gdf, inventory_refined)
+                    aoi_gdf, inventory_refined
+                )
 
             inventory_refined = _handle_non_continous_swath(inventory_refined)
 
@@ -579,20 +576,18 @@ def search_refinement(
             inventory_refined.drop_duplicates(inplace=True)
 
             if len(inventory_refined) != 0:
-                pols = ''.join(pol.split())
+                pols = "".join(pol.split())
 
                 if datelist:
-                    out = inventory_dir.joinpath(
-                        f'{len(datelist)}_{orb}_{pols}.gpkg'
-                    )
+                    out = inventory_dir / f"{len(datelist)}_{orb}_{pols}.gpkg"
                 else:
-                    out = inventory_dir.joinpath(f'{orb}_{pols}.gpkg')
+                    out = inventory_dir / f"{orb}_{pols}.gpkg"
 
-                inventory_refined.to_file(out, driver='GPKG')
+                inventory_refined.to_file(out, driver="GPKG")
 
-                inventory_dict[f'{orb}_{pols}'] = inventory_refined
-                coverage_dict[f'{orb}_{pols}'] = len(datelist)
+                inventory_dict[f"{orb}_{pols}"] = inventory_refined
+                coverage_dict[f"{orb}_{pols}"] = len(datelist)
 
-            logger.info(f'Found {len(datelist)} full coverage mosaics.')
+            logger.info(f"Found {len(datelist)} full coverage mosaics.")
 
     return inventory_dict, coverage_dict
diff --git a/ost/s1/s1scene.py b/ost/s1/s1scene.py
index 3fb509d0..d0a43f18 100644
--- a/ost/s1/s1scene.py
+++ b/ost/s1/s1scene.py
@@ -14,6 +14,7 @@
 
 # ------ bug of rasterio --------
 import os
+
 if "GDAL_DATA" in list(os.environ.keys()):
     del os.environ["GDAL_DATA"]
 if "PROJ_LIB" in list(os.environ.keys()):
@@ -43,13 +44,12 @@
 
 logger = logging.getLogger(__name__)
 
-CONNECTION_ERROR = 'We failed to connect to the server. Reason: '
-CONNECTION_ERROR_2 = 'The server couldn\'t fulfill the request. Error code: '
+CONNECTION_ERROR = "We failed to connect to the server. Reason: "
+CONNECTION_ERROR_2 = "The server couldn't fulfill the request. Error code: "
 
 
 class Sentinel1Scene:
-
-    def __init__(self, scene_id, ard_type='OST_GTC', log_level=logging.INFO):
+    def __init__(self, scene_id, ard_type="OST_GTC", log_level=logging.INFO):
 
         # set log level
         set_log_level(log_level)
@@ -77,34 +77,33 @@ def __init__(self, scene_id, ard_type='OST_GTC', log_level=logging.INFO):
         # Calculate the relative orbit out of absolute orbit
         # (from Peter Meadows (ESA) @
         # http://forum.step.esa.int/t/sentinel-1-relative-orbit-from-filename/7042)
-        if self.mission_id == 'S1A':
+        if self.mission_id == "S1A":
             self.orbit_offset = 73
             self.satellite = "Sentinel-1A"
-        elif self.mission_id == 'S1B':
+        elif self.mission_id == "S1B":
             self.orbit_offset = 27
             self.satellite = "Sentinel-1B"
 
-        self.rel_orbit = (((int(self.abs_orbit)
-                            - int(self.orbit_offset)) % 175) + 1)
+        self.rel_orbit = ((int(self.abs_orbit) - int(self.orbit_offset)) % 175) + 1
 
         # get acquisition mode
-        if self.mode_beam == 'IW':
+        if self.mode_beam == "IW":
             self.acq_mode = "Interferometric Wide Swath"
         elif self.mode_beam.startswith("S"):
             self.acq_mode = "Stripmap"
-        elif self.mode_beam == 'EW':
+        elif self.mode_beam == "EW":
             self.acq_mode = "Extra-Wide swath"
-        elif self.mode_beam == 'WV':
+        elif self.mode_beam == "WV":
             self.acq_mode = "Wave"
 
         # get product type
-        if self.product_type == 'GRD':
+        if self.product_type == "GRD":
             self.p_type = "Ground Range Detected (GRD)"
-        elif self.product_type == 'SLC':
+        elif self.product_type == "SLC":
             self.p_type = "Single-Look Complex (SLC)"
-        elif self.product_type == 'OCN':
+        elif self.product_type == "OCN":
             self.p_type = "Ocean"
-        elif self.product_type == 'RAW':
+        elif self.product_type == "RAW":
             self.p_type = "Raw Data (RAW)"
 
         # set initial product paths to None
@@ -157,60 +156,57 @@ def info_dict(self):
     def download(self, download_dir, mirror=None):
 
         if not mirror:
-            logger.info('One or more of your scenes need to be downloaded.')
-            print(' Select the server from where you want to download:')
-            print(' (1) Copernicus Apihub (ESA, rolling archive)')
-            print(' (2) Alaska Satellite Facility (NASA, full archive)')
-            print(' (3) PEPS (CNES, 1 year rolling archive)')
-            print(' (4) ONDA DIAS (ONDA DIAS full archive for'
-                  ' SLC - or GRD from 30 June 2019)')
+            logger.info("One or more of your scenes need to be downloaded.")
+            print(" Select the server from where you want to download:")
+            print(" (1) Copernicus Apihub (ESA, rolling archive)")
+            print(" (2) Alaska Satellite Facility (NASA, full archive)")
+            print(" (3) PEPS (CNES, 1 year rolling archive)")
+            print(
+                " (4) ONDA DIAS (ONDA DIAS full archive for"
+                " SLC - or GRD from 30 June 2019)"
+            )
             # print(' (5) Alaska Satellite Facility (using WGET'
             #      ' - unstable - use only if 2 fails)')
-            mirror = input(' Type 1, 2, 3, or 4: ')
+            mirror = input(" Type 1, 2, 3, or 4: ")
 
         from ost.s1 import download
 
         if isinstance(download_dir, str):
             download_dir = Path(download_dir)
 
-        if mirror == '1':
+        if mirror == "1":
             uname, pword = scihub.ask_credentials()
             opener = scihub.connect(uname=uname, pword=pword)
             df = pd.DataFrame(
-                {'identifier': [self.scene_id],
-                 'uuid': [self.scihub_uuid(opener)]
-                 }
+                {"identifier": [self.scene_id], "uuid": [self.scihub_uuid(opener)]}
             )
 
-        elif mirror == '2':
+        elif mirror == "2":
             uname, pword = asf.ask_credentials()
-            df = pd.DataFrame({'identifier': [self.scene_id]})
+            df = pd.DataFrame({"identifier": [self.scene_id]})
 
-        elif mirror == '3':
+        elif mirror == "3":
             uname, pword = peps.ask_credentials()
             df = pd.DataFrame(
-                {'identifier': [self.scene_id],
-                 'uuid': [self.peps_uuid(uname=uname, pword=pword)]
-                 }
+                {
+                    "identifier": [self.scene_id],
+                    "uuid": [self.peps_uuid(uname=uname, pword=pword)],
+                }
             )
-        elif mirror == '4':
+        elif mirror == "4":
             uname, pword = onda.ask_credentials()
             opener = onda.connect(uname=uname, pword=pword)
             df = pd.DataFrame(
-                {'identifier': [self.scene_id],
-                 'uuid': [self.ondadias_uuid(opener)]
-                 }
+                {"identifier": [self.scene_id], "uuid": [self.ondadias_uuid(opener)]}
             )
         else:
-            raise ValueError('You entered the wrong mirror.')
+            raise ValueError("You entered the wrong mirror.")
         # else:  # ASF
         #    df = pd.DataFrame({'identifier': [self.scene_id]})
         #    download.download_sentinel1(df, download_dir, mirror)
         #    return
 
-        download.download_sentinel1(
-            df, download_dir, mirror, uname=uname, pword=pword
-        )
+        download.download_sentinel1(df, download_dir, mirror, uname=uname, pword=pword)
 
         # delete credentials
         del uname, pword
@@ -221,15 +217,21 @@ def download_path(self, download_dir, mkdir=False):
         if isinstance(download_dir, str):
             download_dir = Path(download_dir)
 
-        download_path = Path(download_dir).joinpath(
-            f'SAR/{self.product_type}/{self.year}/{self.month}/{self.day}'
+        download_path = (
+            Path(download_dir)
+            / "SAR"
+            / f"{self.product_type}"
+            / f"{self.year}"
+            / f"{self.month}"
+            / f"{self.day}"
         )
+
         # make dir if not existent
         if mkdir:
             download_path.mkdir(parents=True, exist_ok=True)
 
         # get file_path
-        file_path = download_path.joinpath(f'{self.scene_id}.zip')
+        file_path = download_path / f"{self.scene_id}.zip"
 
         self.product_dl_path = file_path
 
@@ -240,18 +242,31 @@ def _creodias_path(self, data_mount):
         if isinstance(data_mount, str):
             data_mount = Path(data_mount)
 
-        path = Path(data_mount).joinpath(
-            f'Sentinel-1/SAR/{self.product_type}/{self.year}/'
-            f'{self.month}/{self.day}/{self.scene_id}.SAFE'
+        path = (
+            Path(data_mount)
+            / "Sentinel-1"
+            / "SAR"
+            / f"{self.product_type}"
+            / f"{self.year}"
+            / f"{self.month}"
+            / f"{self.day}"
+            / f"{self.scene_id}.SAFE"
         )
 
         return path
 
     def _onda_path(self, data_mount):
 
-        path = Path(data_mount).joinpath(
-            f'S1/LEVEL-1/{self.onda_class}/{self.year}/{self.month}/'
-            f'{self.day}/{self.scene_id}.zip/{self.scene_id}.SAFE'
+        path = (
+            Path(data_mount)
+            / "S1"
+            / "LEVEL-1"
+            / f"{self.onda_class}"
+            / f"{self.year}"
+            / f"{self.month}"
+            / f"{self.day}"
+            / f"{self.scene_id}.zip"
+            / f"{self.scene_id}.SAFE"
         )
 
         return path
@@ -269,7 +284,7 @@ def get_path(self, download_dir=None, data_mount=None):
             self.download_path(download_dir=download_dir, mkdir=False)
 
             # check if scene is succesfully downloaded
-            if self.product_dl_path.with_suffix('.downloaded').exists():
+            if self.product_dl_path.with_suffix(".downloaded").exists():
                 path = self.product_dl_path
 
         if data_mount and not path:
@@ -279,8 +294,7 @@ def get_path(self, download_dir=None, data_mount=None):
                 data_mount = Path(data_mount)
 
             # check creodias folder structure
-            if self._creodias_path(data_mount).joinpath(
-                    'manifest.safe').exists():
+            if (self._creodias_path(data_mount) / "manifest.safe").exists():
                 path = self._creodias_path(data_mount)
             # check for ondadias folder structure
             elif self._onda_path(data_mount).exists():
@@ -292,12 +306,10 @@ def get_path(self, download_dir=None, data_mount=None):
     def scihub_uuid(self, opener):
 
         # construct the basic the url
-        base_url = (
-            'https://apihub.copernicus.eu/apihub/odata/v1/Products?$filter='
-        )
+        base_url = "https://apihub.copernicus.eu/apihub/odata/v1/Products?$filter="
 
         # request
-        action = urllib.parse.quote(f'Name eq \'{self.scene_id}\'')
+        action = urllib.parse.quote(f"Name eq '{self.scene_id}'")
 
         # construct the download url
         url = base_url + action
@@ -306,16 +318,16 @@ def scihub_uuid(self, opener):
             # get the request
             req = opener.open(url)
         except URLError as error:
-            if hasattr(error, 'reason'):
-                logger.info(f'{CONNECTION_ERROR}{error.reason}')
+            if hasattr(error, "reason"):
+                logger.info(f"{CONNECTION_ERROR}{error.reason}")
                 sys.exit()
-            elif hasattr(error, 'code'):
-                logger.info(f'{CONNECTION_ERROR_2}{error.reason}')
+            elif hasattr(error, "code"):
+                logger.info(f"{CONNECTION_ERROR_2}{error.reason}")
                 sys.exit()
         else:
             # write the request to to the response variable
             # (i.e. the xml coming back from scihub)
-            response = req.read().decode('utf-8')
+            response = req.read().decode("utf-8")
 
             # return uuid from response
             return response.split("Products('")[1].split("')")[0]
@@ -323,43 +335,42 @@ def scihub_uuid(self, opener):
     def scihub_url(self, opener):
 
         # return the full url
-        return f'{APIHUB_BASEURL}(\'{self.scihub_uuid(opener)}\')/$value'
+        return f"{APIHUB_BASEURL}('{self.scihub_uuid(opener)}')/$value"
 
     def scihub_md5(self, opener):
 
         # return the md5 checksum
         return (
-            f'{APIHUB_BASEURL}(\'{self.scihub_uuid(opener)}\')'
-            f'/Checksum/Value/$value'
+            f"{APIHUB_BASEURL}('{self.scihub_uuid(opener)}')" f"/Checksum/Value/$value"
         )
 
     def scihub_online_status(self, opener):
 
         # get url for product
-        url = f'{APIHUB_BASEURL}(\'{self.scihub_uuid(opener)}\')/Online/$value'
+        url = f"{APIHUB_BASEURL}('{self.scihub_uuid(opener)}')/Online/$value"
 
         # check if something is coming back from our request
         try:
             # get the request
             req = opener.open(url)
         except URLError as error:
-            if hasattr(error, 'reason'):
-                logger.info(f'{CONNECTION_ERROR}{error.reason}')
+            if hasattr(error, "reason"):
+                logger.info(f"{CONNECTION_ERROR}{error.reason}")
                 sys.exit()
-            elif hasattr(error, 'code'):
-                logger.info(f'{CONNECTION_ERROR_2}{error.reason}')
+            elif hasattr(error, "code"):
+                logger.info(f"{CONNECTION_ERROR_2}{error.reason}")
                 sys.exit()
         else:
             # write the request to to the response variable
             # (i.e. the xml coming back from scihub)
-            response = req.read().decode('utf-8')
+            response = req.read().decode("utf-8")
 
-            if response == 'true':
+            if response == "true":
                 response = True
-            elif response == 'false':
+            elif response == "false":
                 response = False
             else:
-                raise TypeError('Wrong response type.')
+                raise TypeError("Wrong response type.")
 
             return response
 
@@ -367,31 +378,29 @@ def scihub_trigger_production(self, opener):
 
         # get uuid and construct url for scihub's apihub
         uuid = self.scihub_uuid(opener)
-        url = f'{APIHUB_BASEURL}(\'{uuid}\')/$value'
+        url = f"{APIHUB_BASEURL}('{uuid}')/$value"
 
         try:
             # get the request
             req = opener.open(url)
         except URLError as error:
-            if hasattr(error, 'reason'):
-                logger.info(f'{CONNECTION_ERROR}{error.reason}')
+            if hasattr(error, "reason"):
+                logger.info(f"{CONNECTION_ERROR}{error.reason}")
                 sys.exit()
-            elif hasattr(error, 'code'):
-                logger.info(f'{CONNECTION_ERROR_2}{error.reason}')
+            elif hasattr(error, "code"):
+                logger.info(f"{CONNECTION_ERROR_2}{error.reason}")
                 sys.exit()
         else:
             # write the request to to the response variable
             # (i.e. the xml coming back from scihub)
             code = req.getcode()
             if code == 202:
-                logging.info(
-                    f'Production of {self.scene_id} successfully requested.'
-                )
+                logging.info(f"Production of {self.scene_id} successfully requested.")
 
             return code
 
     def _scihub_annotation_url(self, opener):
-        """ Retrieve the urls for the product annotation files
+        """Retrieve the urls for the product annotation files
 
         :param opener:
         :type opener:
@@ -403,13 +412,11 @@ def _scihub_annotation_url(self, opener):
         uuid = self.scihub_uuid(opener)
 
         logger.info(
-            f'Retrieving URLs of annotation files for S1 product: '
-            f'{self.scene_id}.'
+            f"Retrieving URLs of annotation files for S1 product: " f"{self.scene_id}."
         )
-        scihub_url = 'https://apihub.copernicus.eu/apihub/odata/v1/Products'
+        scihub_url = "https://apihub.copernicus.eu/apihub/odata/v1/Products"
         anno_path = (
-            f'(\'{uuid}\')/Nodes(\'{self.scene_id}.SAFE\')'
-            f'/Nodes(\'annotation\')/Nodes'
+            f"('{uuid}')/Nodes('{self.scene_id}.SAFE')" f"/Nodes('annotation')/Nodes"
         )
 
         # construct anno url path
@@ -420,29 +427,27 @@ def _scihub_annotation_url(self, opener):
             # get the request
             req = opener.open(url)
         except URLError as error:
-            if hasattr(error, 'reason'):
-                logger.info(f'{CONNECTION_ERROR}{error.reason}')
+            if hasattr(error, "reason"):
+                logger.info(f"{CONNECTION_ERROR}{error.reason}")
                 sys.exit()
-            elif hasattr(error, 'code'):
-                logger.info(f'{CONNECTION_ERROR_2}{error.reason}')
+            elif hasattr(error, "code"):
+                logger.info(f"{CONNECTION_ERROR_2}{error.reason}")
                 sys.exit()
         else:
             # read out the response
-            response = req.read().decode('utf-8')
+            response = req.read().decode("utf-8")
 
             # parse the response
             dom = xml.dom.minidom.parseString(response)
 
             # loop through each entry (with all metadata)
             url_list = []
-            for node in dom.getElementsByTagName('entry'):
-                download_url = node.getElementsByTagName(
-                    'id'
-                )[0].firstChild.nodeValue
+            for node in dom.getElementsByTagName("entry"):
+                download_url = node.getElementsByTagName("id")[0].firstChild.nodeValue
 
                 # if we find an xml we append to the list of relevant files
-                if download_url[-6:-2] == '.xml':
-                    url_list.append(f'{download_url}/$value')
+                if download_url[-6:-2] == ".xml":
+                    url_list.append(f"{download_url}/$value")
 
             return url_list
 
@@ -458,8 +463,13 @@ def scihub_annotation_get(self, uname=None, pword=None):
 
         # create emtpy geodataframe to be filled with burst infos
         column_names = [
-            'SceneID', 'Track', 'Date', 'SwathID', 'AnxTime', 'BurstNr',
-            'geometry'
+            "SceneID",
+            "Track",
+            "Date",
+            "SwathID",
+            "AnxTime",
+            "BurstNr",
+            "geometry",
         ]
         gdf_final = gpd.GeoDataFrame(columns=column_names)
 
@@ -472,28 +482,27 @@ def scihub_annotation_get(self, uname=None, pword=None):
                 # get the request
                 req = opener.open(url)
             except URLError as error:
-                if hasattr(error, 'reason'):
-                    logger.info(f'{CONNECTION_ERROR}{error.reason}')
+                if hasattr(error, "reason"):
+                    logger.info(f"{CONNECTION_ERROR}{error.reason}")
                     sys.exit()
-                elif hasattr(error, 'code'):
-                    logger.info(f'{CONNECTION_ERROR_2}{error.reason}')
+                elif hasattr(error, "code"):
+                    logger.info(f"{CONNECTION_ERROR_2}{error.reason}")
                     sys.exit()
             else:
                 # write the request to to the response variable
                 # (i.e. the xml coming back from scihub)
-                response = req.read().decode('utf-8')
+                response = req.read().decode("utf-8")
 
                 et_root = eTree.fromstring(response)
 
                 # parse the xml page from the response
                 gdf = burst_extract(
-                    self.scene_id, self.rel_orbit, self.start_date,
-                    eTree.parse(et_root)
+                    self.scene_id, self.rel_orbit, self.start_date, eTree.parse(et_root)
                 )
 
                 gdf_final = gdf_final.append(gdf)
 
-        return gdf_final.drop_duplicates(['AnxTime'], keep='first')
+        return gdf_final.drop_duplicates(["AnxTime"], keep="first")
 
     def zip_annotation_get(self, download_dir, data_mount=None):
         """
@@ -505,17 +514,24 @@ def zip_annotation_get(self, download_dir, data_mount=None):
 
         from ost.s1.burst_inventory import burst_extract
 
-        column_names = ['SceneID', 'Track', 'Date', 'SwathID', 'AnxTime',
-                        'BurstNr', 'geometry']
+        column_names = [
+            "SceneID",
+            "Track",
+            "Date",
+            "SwathID",
+            "AnxTime",
+            "BurstNr",
+            "geometry",
+        ]
 
         # crs for empty dataframe
-        crs = 'epsg:4326'
+        crs = "epsg:4326"
         gdf_final = gpd.GeoDataFrame(columns=column_names, crs=crs)
 
         file = self.get_path(download_dir, data_mount)
 
         # extract info from archive
-        archive = zipfile.ZipFile(file, 'r')
+        archive = zipfile.ZipFile(file, "r")
         namelist = archive.namelist()
         anno_files = fnmatch.filter(namelist, "*/annotation/s*.xml")
 
@@ -524,43 +540,44 @@ def zip_annotation_get(self, download_dir, data_mount=None):
             anno_string = archive.open(anno_file)
 
             gdf = burst_extract(
-                self.scene_id, self.rel_orbit, self.start_date,
-                eTree.parse(anno_string)
+                self.scene_id, self.rel_orbit, self.start_date, eTree.parse(anno_string)
             )
 
             gdf_final = gdf_final.append(gdf)
 
-        return gdf_final.drop_duplicates(['AnxTime'], keep='first')
+        return gdf_final.drop_duplicates(["AnxTime"], keep="first")
 
     def safe_annotation_get(self, download_dir, data_mount=None):
 
         from ost.s1.burst_inventory import burst_extract
 
-        column_names = ['SceneID', 'Track', 'Date', 'SwathID',
-                        'AnxTime', 'BurstNr', 'geometry']
+        column_names = [
+            "SceneID",
+            "Track",
+            "Date",
+            "SwathID",
+            "AnxTime",
+            "BurstNr",
+            "geometry",
+        ]
         gdf_final = gpd.GeoDataFrame(columns=column_names)
 
-        file_path = self.get_path(
-            download_dir=download_dir, data_mount=data_mount
-        )
+        file_path = self.get_path(download_dir=download_dir, data_mount=data_mount)
 
-        for anno_file in list(file_path.glob('annotation/*xml')):
+        for anno_file in list(file_path.glob("annotation/*xml")):
             # parse the xml page from the response
             gdf = burst_extract(
-                self.scene_id, self.rel_orbit, self.start_date,
-                eTree.parse(anno_file)
+                self.scene_id, self.rel_orbit, self.start_date, eTree.parse(anno_file)
             )
             gdf_final = gdf_final.append(gdf)
 
-        return gdf_final.drop_duplicates(['AnxTime'], keep='first')
+        return gdf_final.drop_duplicates(["AnxTime"], keep="first")
 
     # onda dias uuid extractor
     def ondadias_uuid(self, opener):
 
         # construct the basic the url
-        base_url = (
-            'https://catalogue.onda-dias.eu/dias-catalogue/Products?$search='
-        )
+        base_url = "https://catalogue.onda-dias.eu/dias-catalogue/Products?$search="
 
         # construct the download url
         action = '"' + self.scene_id + '.zip"'
@@ -570,16 +587,16 @@ def ondadias_uuid(self, opener):
             # get the request
             req = opener.open(url)
         except URLError as error:
-            if hasattr(error, 'reason'):
-                logger.info(f'{CONNECTION_ERROR}{error.reason}')
+            if hasattr(error, "reason"):
+                logger.info(f"{CONNECTION_ERROR}{error.reason}")
                 sys.exit()
-            elif hasattr(error, 'code'):
-                logger.info(f'{CONNECTION_ERROR_2}{error.reason}')
+            elif hasattr(error, "code"):
+                logger.info(f"{CONNECTION_ERROR_2}{error.reason}")
                 sys.exit()
         else:
             # write the request to to the response variable
             # (i.e. the xml coming back from onda dias)
-            response = req.read().decode('utf-8')
+            response = req.read().decode("utf-8")
 
             # parse the uuid from the response (a messy pseudo xml)
             uuid = response.split('":"')[3].split('","')[0]
@@ -593,25 +610,25 @@ def asf_url(self):
         :rtype: str
         """
         # base url of ASF
-        asf_url = 'https://datapool.asf.alaska.edu'
+        asf_url = "https://datapool.asf.alaska.edu"
 
         # get ASF style mission id
-        if self.mission_id == 'S1A':
-            mission = 'SA'
-        elif self.mission_id == 'S1B':
-            mission = 'SB'
+        if self.mission_id == "S1A":
+            mission = "SA"
+        elif self.mission_id == "S1B":
+            mission = "SB"
         else:
-            raise ValueError('Wrong mission id.')
+            raise ValueError("Wrong mission id.")
 
         # get relevant product type in ASF style
-        if self.product_type == 'SLC':
+        if self.product_type == "SLC":
             product_type = self.product_type
-        elif self.product_type == 'GRD':
-            product_type = f'GRD_{self.resolution_class}{self.pol_mode[0]}'
+        elif self.product_type == "GRD":
+            product_type = f"GRD_{self.resolution_class}{self.pol_mode[0]}"
         else:
-            raise ValueError('Wrong product type.')
+            raise ValueError("Wrong product type.")
 
-        return f'{asf_url}/{product_type}/{mission}/{self.scene_id}.zip'
+        return f"{asf_url}/{product_type}/{mission}/{self.scene_id}.zip"
 
     def peps_uuid(self, uname, pword):
         """Retrieval of the PEPS UUID from the Peps server
@@ -622,8 +639,8 @@ def peps_uuid(self, uname, pword):
         """
         # construct product url
         url = (
-            f'https://peps.cnes.fr/resto/api/collections/S1/search.json?q='
-            f'{self.scene_id}'
+            f"https://peps.cnes.fr/resto/api/collections/S1/search.json?q="
+            f"{self.scene_id}"
         )
 
         # get response
@@ -631,15 +648,13 @@ def peps_uuid(self, uname, pword):
 
         # check response
         if response.status_code == 401:
-            raise ValueError(' ERROR: Username/Password are incorrect.')
+            raise ValueError(" ERROR: Username/Password are incorrect.")
         elif response.status_code != 200:
             response.raise_for_status()
 
         data = json.loads(response.text)
-        peps_uuid = data['features'][0]['id']
-        download_url = (
-            data['features'][0]['properties']['services']['download']['url']
-        )
+        peps_uuid = data["features"][0]["id"]
+        download_url = data["features"][0]["properties"]["services"]["download"]["url"]
 
         return peps_uuid, download_url
 
@@ -659,13 +674,13 @@ def peps_online_status(self, uname, pword):
 
         # check response
         if status == 401:
-            raise ValueError(' ERROR: Username/Password are incorrect.')
+            raise ValueError(" ERROR: Username/Password are incorrect.")
         elif status == 404:
-            raise ValueError(' ERROR: File not found.')
+            raise ValueError(" ERROR: File not found.")
         elif status == 200:
-            status = 'online'
+            status = "online"
         elif status == 202:
-            status = 'onTape'
+            status = "onTape"
         else:
             response.raise_for_status()
 
@@ -674,14 +689,16 @@ def peps_online_status(self, uname, pword):
     def get_ard_parameters(self, ard_type):
 
         # find respective template for selected ARD type
-        template_file = OST_ROOT.joinpath(
-            f"graphs/ard_json/"
-            f"{self.product_type.lower()}."
-            f"{ard_type.replace('-', '_').lower()}.json"
+        template_file = (
+            OST_ROOT
+            / "graphs"
+            / "ard_json"
+            / f"{self.product_type.lower()}.{ard_type.replace('-', '_').lower()}.json"
         )
+
         # open and load parameters
-        with open(template_file, 'r') as ard_file:
-            self.ard_parameters = json.load(ard_file)['processing']
+        with open(template_file, "r") as ard_file:
+            self.ard_parameters = json.load(ard_file)["processing"]
 
     def update_ard_parameters(self, ard_type=None):
         """
@@ -697,13 +714,11 @@ def update_ard_parameters(self, ard_type=None):
         check_ard_parameters(self.ard_parameters)
 
         # re-create project dict with update ard parameters
-        self.config_dict.update(
-            processing=self.ard_parameters
-        )
+        self.config_dict.update(processing=self.ard_parameters)
 
         # dump to json file
         if self.config_file:
-            with open(self.config_file, 'w') as outfile:
+            with open(self.config_file, "w") as outfile:
                 json.dump(self.config_dict, outfile, indent=4)
 
     def set_external_dem(self, dem_file, ellipsoid_correction=True):
@@ -718,28 +733,31 @@ def set_external_dem(self, dem_file, ellipsoid_correction=True):
 
         # check if file exists
         if not Path(dem_file).exists():
-            raise FileNotFoundError(f'No file found at {dem_file}.')
+            raise FileNotFoundError(f"No file found at {dem_file}.")
 
         # get no data value
         with rasterio.open(dem_file) as file:
             dem_nodata = file.nodata
 
         # get resampling
-        img_res = self.ard_parameters['single_ARD']['dem']['image_resampling']
-        dem_res = self.ard_parameters['single_ARD']['dem']['dem_resampling']
+        img_res = self.ard_parameters["single_ARD"]["dem"]["image_resampling"]
+        dem_res = self.ard_parameters["single_ARD"]["dem"]["dem_resampling"]
 
         # update ard parameters
-        dem_dict = dict({'dem_name': 'External DEM',
-                         'dem_file': dem_file,
-                         'dem_nodata': dem_nodata,
-                         'dem_resampling': dem_res,
-                         'image_resampling': img_res,
-                         'egm_correction': ellipsoid_correction,
-                         'out_projection': 'WGS84(DD)'
-                         })
+        dem_dict = dict(
+            {
+                "dem_name": "External DEM",
+                "dem_file": dem_file,
+                "dem_nodata": dem_nodata,
+                "dem_resampling": dem_res,
+                "image_resampling": img_res,
+                "egm_correction": ellipsoid_correction,
+                "out_projection": "WGS84(DD)",
+            }
+        )
 
         # update ard_parameters
-        self.ard_parameters['single_ARD']['dem'] = dem_dict
+        self.ard_parameters["single_ARD"]["dem"] = dem_dict
 
     def create_ard(self, infile, out_dir, subset=None, overwrite=False):
         """
@@ -751,10 +769,10 @@ def create_ard(self, infile, out_dir, subset=None, overwrite=False):
         :return:
         """
 
-        if self.product_type != 'GRD':
+        if self.product_type != "GRD":
             raise ValueError(
-                'The create_ard method for single products is currently '
-                'only available for GRD products'
+                "The create_ard method for single products is currently "
+                "only available for GRD products"
             )
 
         if isinstance(infile, str):
@@ -764,23 +782,23 @@ def create_ard(self, infile, out_dir, subset=None, overwrite=False):
             out_dir = Path(out_dir)
 
         # set config param necessary for processing
-        self.config_dict['processing_dir'] = str(out_dir)
-        self.config_dict['temp_dir'] = str(out_dir.joinpath('temp'))
-        self.config_dict['snap_cpu_parallelism'] = os.cpu_count()
-        self.config_dict['subset'] = False
+        self.config_dict["processing_dir"] = str(out_dir)
+        self.config_dict["temp_dir"] = str(out_dir / "temp")
+        self.config_dict["snap_cpu_parallelism"] = os.cpu_count()
+        self.config_dict["subset"] = False
 
         if subset:
-            self.config_dict['subset'] = True
-            self.config_dict['aoi'] = subset
+            self.config_dict["subset"] = True
+            self.config_dict["aoi"] = subset
 
         # create directories
         out_dir.mkdir(parents=True, exist_ok=True)
-        out_dir.joinpath('temp').mkdir(parents=True, exist_ok=True)
+        (out_dir / "temp").mkdir(parents=True, exist_ok=True)
 
         if overwrite:
-            file_dir = out_dir.joinpath(f'{self.rel_orbit}/{self.start_date}')
-            if file_dir.joinpath('.processed').exists():
-                file_dir.joinpath('.processed').unlink()
+            file_dir = out_dir / f"{self.rel_orbit}" / f"{self.start_date}"
+            if (file_dir / ".processed").exists():
+                (file_dir / ".processed").unlink()
 
         # --------------------------------------------
         # 2 Check if within SRTM coverage
@@ -789,45 +807,43 @@ def create_ard(self, infile, out_dir, subset=None, overwrite=False):
         center_lat = self._get_center_lat(infile)
         if float(center_lat) > 59 or float(center_lat) < -59:
 
-            if 'SRTM' in self.ard_parameters['single_ARD']['dem']['dem_name']:
+            if "SRTM" in self.ard_parameters["single_ARD"]["dem"]["dem_name"]:
 
                 logger.info(
-                    'Scene is outside SRTM coverage. Snap will therefore use '
-                    'the Copernicus 30m Global DEM. '
+                    "Scene is outside SRTM coverage. Snap will therefore use "
+                    "the Copernicus 30m Global DEM. "
                 )
 
-                self.ard_parameters['single_ARD']['dem'][
-                    'dem_name'] = 'Copernicus 30m Global DEM'
+                self.ard_parameters["single_ARD"]["dem"][
+                    "dem_name"
+                ] = "Copernicus 30m Global DEM"
 
-            if self.ard_parameters['single_ARD']['dem'][
-                'out_projection'
-            ] == 4326:
+            if self.ard_parameters["single_ARD"]["dem"]["out_projection"] == 4326:
 
                 logger.info(
-                    'The scene\'s location is towards the poles. '
-                    'Consider to use a stereographic projection.'
+                    "The scene's location is towards the poles. "
+                    "Consider to use a stereographic projection."
                 )
 
                 epsg = input(
-                    'Type an alternative EPSG code for the projection of the '
-                    'output data or just press enter for keeping Lat/Lon '
-                    'coordinate system (e.g. 3413 for NSIDC Sea Ice Polar '
-                    'Stereographic North projection, or 3976 for '
-                    'NSIDC Sea Ice Polar Stereographic South projection'
+                    "Type an alternative EPSG code for the projection of the "
+                    "output data or just press enter for keeping Lat/Lon "
+                    "coordinate system (e.g. 3413 for NSIDC Sea Ice Polar "
+                    "Stereographic North projection, or 3976 for "
+                    "NSIDC Sea Ice Polar Stereographic South projection"
                 )
 
                 if not epsg:
                     epsg = 4326
 
-                self.ard_parameters['single_ARD']['dem'][
-                    'out_projection'] = int(epsg)
+                self.ard_parameters["single_ARD"]["dem"]["out_projection"] = int(epsg)
 
         # --------------------------------------------
         # 3 Check ard parameters in case they have been updated,
         #   and write them to json file
 
         # set config file to output directory
-        self.config_file = out_dir.joinpath('processing.json')
+        self.config_file = out_dir / "processing.json"
 
         # write ard parameters, and check if they are correct
         self.update_ard_parameters()
@@ -840,7 +856,8 @@ def create_ard(self, infile, out_dir, subset=None, overwrite=False):
         # --------------------------------------------
         # 5 run the burst to ard batch routine
         filelist, out_bs, out_ls, error = grd_to_ard(
-            [infile], self.config_file,
+            [infile],
+            self.config_file,
         )
 
         # print error if any
@@ -848,15 +865,16 @@ def create_ard(self, infile, out_dir, subset=None, overwrite=False):
             logger.info(error)
         else:
             # remove temp folder
-            h.remove_folder_content(out_dir.joinpath('temp'))
-            out_dir.joinpath('temp').rmdir()
+            tmp_folder = out_dir / "temp"
+            h.remove_folder_content(tmp_folder)
+            tmp_folder.rmdir()
             self.ard_dimap = out_bs
 
-    def create_rgb(self, outfile, driver='GTiff'):
+    def create_rgb(self, outfile, driver="GTiff"):
 
         # invert ot db from create_ard workflow for rgb creation
         # (otherwise we do it double)
-        if self.ard_parameters['single_ARD']['to_db']:
+        if self.ard_parameters["single_ARD"]["to_db"]:
             to_db = False
         else:
             to_db = True
@@ -864,20 +882,18 @@ def create_rgb(self, outfile, driver='GTiff'):
         ard_to_rgb(self.ard_dimap, outfile, driver, to_db)
         self.ard_rgb = outfile
 
-    def create_rgb_thumbnail(self, outfile,  shrink_factor=25):
+    def create_rgb_thumbnail(self, outfile, shrink_factor=25):
 
         # invert ot db from create_ard workflow for rgb creation
         # (otherwise we do it double)
-        if self.ard_parameters['single_ARD']['to_db']:
+        if self.ard_parameters["single_ARD"]["to_db"]:
             to_db = False
         else:
             to_db = True
 
         self.rgb_thumbnail = outfile
-        driver = 'JPEG'
-        ard_to_rgb(
-            self.ard_dimap, self.rgb_thumbnail, driver, to_db, shrink_factor
-        )
+        driver = "JPEG"
+        ard_to_rgb(self.ard_dimap, self.rgb_thumbnail, driver, to_db, shrink_factor)
 
     def visualise_rgb(self, shrink_factor=25):
 
@@ -886,50 +902,43 @@ def visualise_rgb(self, shrink_factor=25):
     # other functions
     def _get_center_lat(self, scene_path=None):
 
-        if scene_path.suffix == '.zip':
+        if scene_path.suffix == ".zip":
             zip_archive = zipfile.ZipFile(str(scene_path))
-            manifest = zip_archive.read(
-                f'{self.scene_id}.SAFE/manifest.safe'
-            )
-        elif scene_path.suffix == '.SAFE':
-            with open(scene_path.joinpath('manifest.safe'), 'rb') as file:
+            manifest = zip_archive.read(f"{self.scene_id}.SAFE/manifest.safe")
+        elif scene_path.suffix == ".SAFE":
+            with (scene_path / "manifest.safe").open("rb") as file:
                 manifest = file.read()
         else:
-            raise ValueError('Invalid file.')
+            raise ValueError("Invalid file.")
 
         root = eTree.fromstring(manifest)
         coordinates = None
         for child in root:
-            metadata = child.findall('metadataObject')
+            metadata = child.findall("metadataObject")
             for meta in metadata:
-                for wrap in meta.findall('metadataWrap'):
-                    for data in wrap.findall('xmlData'):
+                for wrap in meta.findall("metadataWrap"):
+                    for data in wrap.findall("xmlData"):
                         for frame_set in data.findall(
-                            '{http://www.esa.int/safe/sentinel-1.0}'
-                            'frameSet'
+                            "{http://www.esa.int/safe/sentinel-1.0}" "frameSet"
                         ):
                             for frame in frame_set.findall(
-                                '{http://www.esa.int/safe/sentinel-1.0}'
-                                'frame'
+                                "{http://www.esa.int/safe/sentinel-1.0}" "frame"
                             ):
                                 for footprint in frame.findall(
-                                    '{http://www.esa.int/'
-                                    'safe/sentinel-1.0}footPrint'
+                                    "{http://www.esa.int/" "safe/sentinel-1.0}footPrint"
                                 ):
                                     for coords in footprint.findall(
-                                        '{http://www.opengis.net/gml}'
-                                        'coordinates'
+                                        "{http://www.opengis.net/gml}" "coordinates"
                                     ):
-                                        coordinates = (
-                                            coords.text.split(' '))
+                                        coordinates = coords.text.split(" ")
 
         if coordinates:
             sums, i = 0, 0
             for i, coords in enumerate(coordinates):
-                sums = sums + float(coords.split(',')[0])
+                sums = sums + float(coords.split(",")[0])
 
             return sums / (i + 1)
         else:
             raise RuntimeError(
-                'Could not find any coordinates within the metadata file'
+                "Could not find any coordinates within the metadata file"
             )
diff --git a/ost/s1/search.py b/ost/s1/search.py
index 948be75c..f34ffca7 100644
--- a/ost/s1/search.py
+++ b/ost/s1/search.py
@@ -74,71 +74,73 @@
 # set up logger
 logger = logging.getLogger(__name__)
 
-CONNECTION_ERROR = 'We failed to connect to the server. Reason: '
-CONNECTION_ERROR_2 = 'The server couldn\'t fulfill the request. Error code: '
+CONNECTION_ERROR = "We failed to connect to the server. Reason: "
+CONNECTION_ERROR_2 = "The server couldn't fulfill the request. Error code: "
 
 
 def _read_xml(dom):
 
     acq_list = []
     # loop through each entry (with all metadata)
-    for node in dom.getElementsByTagName('entry'):
+    for node in dom.getElementsByTagName("entry"):
 
         # we get all the date entries
         dict_date = {
-            s.getAttribute('name'):
-                dateutil.parser.parse(s.firstChild.data).astimezone(
-                    dateutil.tz.tzutc()
-                )
-            for s in node.getElementsByTagName('date')
+            s.getAttribute("name"): dateutil.parser.parse(s.firstChild.data).astimezone(
+                dateutil.tz.tzutc()
+            )
+            for s in node.getElementsByTagName("date")
         }
 
         # we get all the int entries
         dict_int = {
-            s.getAttribute('name'): s.firstChild.data
-            for s in node.getElementsByTagName('int')
+            s.getAttribute("name"): s.firstChild.data
+            for s in node.getElementsByTagName("int")
         }
 
         # we create a filter for the str entries (we do not want all)
         # and get them
         dict_str = {
-            s.getAttribute('name'): s.firstChild.data
-            for s in node.getElementsByTagName('str')
+            s.getAttribute("name"): s.firstChild.data
+            for s in node.getElementsByTagName("str")
         }
 
         # merge the dicts and append to the catalogue list
         acq = dict(dict_date, **dict_int, **dict_str)
 
         # fill in emtpy fields in dict by using identifier
-        if 'swathidentifier' not in acq.keys():
-            acq['swathidentifier'] = acq['identifier'].split("_")[1]
-        if 'producttype' not in acq.keys():
-            acq['producttype'] = acq['identifier'].split("_")[2]
-        if 'slicenumber' not in acq.keys():
-            acq['slicenumber'] = 0
+        if "swathidentifier" not in acq.keys():
+            acq["swathidentifier"] = acq["identifier"].split("_")[1]
+        if "producttype" not in acq.keys():
+            acq["producttype"] = acq["identifier"].split("_")[2]
+        if "slicenumber" not in acq.keys():
+            acq["slicenumber"] = 0
 
         # append all scenes from this page to a list
-        acq_list.append([acq['identifier'],
-                         acq['polarisationmode'],
-                         acq['orbitdirection'],
-                         acq['beginposition'].strftime('%Y%m%d'),
-                         acq['relativeorbitnumber'],
-                         acq['orbitnumber'],
-                         acq['producttype'],
-                         acq['slicenumber'],
-                         acq['size'],
-                         acq['beginposition'].isoformat(),
-                         acq['endposition'].isoformat(),
-                         acq['lastrelativeorbitnumber'],
-                         acq['lastorbitnumber'],
-                         acq['uuid'],
-                         acq['platformidentifier'],
-                         acq['missiondatatakeid'],
-                         acq['swathidentifier'],
-                         acq['ingestiondate'].isoformat(),
-                         acq['sensoroperationalmode'],
-                         loads(acq['footprint'])]
-                        )
+        acq_list.append(
+            [
+                acq["identifier"],
+                acq["polarisationmode"],
+                acq["orbitdirection"],
+                acq["beginposition"].strftime("%Y%m%d"),
+                acq["relativeorbitnumber"],
+                acq["orbitnumber"],
+                acq["producttype"],
+                acq["slicenumber"],
+                acq["size"],
+                acq["beginposition"].isoformat(),
+                acq["endposition"].isoformat(),
+                acq["lastrelativeorbitnumber"],
+                acq["lastorbitnumber"],
+                acq["uuid"],
+                acq["platformidentifier"],
+                acq["missiondatatakeid"],
+                acq["swathidentifier"],
+                acq["ingestiondate"].isoformat(),
+                acq["sensoroperationalmode"],
+                loads(acq["footprint"]),
+            ]
+        )
 
     # transform all results from that page to a gdf
     return acq_list
@@ -152,16 +154,29 @@ def _query_scihub(opener, query):
 
     # create empty GDF
     columns = [
-        'identifier', 'polarisationmode', 'orbitdirection',
-        'acquisitiondate', 'relativeorbitnumber', 'orbitnumber',
-        'producttype', 'slicenumber', 'size', 'beginposition',
-        'endposition', 'lastrelativeorbitnumber', 'lastorbitnumber',
-        'uuid', 'platformidentifier', 'missiondatatakeid',
-        'swathidentifier', 'ingestiondate', 'sensoroperationalmode',
-        'geometry'
+        "identifier",
+        "polarisationmode",
+        "orbitdirection",
+        "acquisitiondate",
+        "relativeorbitnumber",
+        "orbitnumber",
+        "producttype",
+        "slicenumber",
+        "size",
+        "beginposition",
+        "endposition",
+        "lastrelativeorbitnumber",
+        "lastorbitnumber",
+        "uuid",
+        "platformidentifier",
+        "missiondatatakeid",
+        "swathidentifier",
+        "ingestiondate",
+        "sensoroperationalmode",
+        "geometry",
     ]
 
-    crs = 'epsg:4326'
+    crs = "epsg:4326"
     geo_df = gpd.GeoDataFrame(columns=columns, crs=crs)
 
     # we need this for the paging
@@ -170,21 +185,21 @@ def _query_scihub(opener, query):
     while next_page:
 
         # construct the final url
-        url = query + f'&rows={rows}&start={index}'
+        url = query + f"&rows={rows}&start={index}"
         try:
             # get the request
             req = opener.open(url)
         except URLError as error:
-            if hasattr(error, 'reason'):
-                logger.info(f'{CONNECTION_ERROR}{error.reason}')
+            if hasattr(error, "reason"):
+                logger.info(f"{CONNECTION_ERROR}{error.reason}")
                 sys.exit()
-            elif hasattr(error, 'code'):
-                logger.info(f'{CONNECTION_ERROR_2}{error.code}')
+            elif hasattr(error, "code"):
+                logger.info(f"{CONNECTION_ERROR_2}{error.code}")
                 sys.exit()
         else:
             # write the request to to the response variable
             # (i.e. the xml coming back from scihub)
-            response = req.read().decode('utf-8')
+            response = req.read().decode("utf-8")
 
             # parse the xml page from the response
             dom = xml.dom.minidom.parseString(response)
@@ -211,43 +226,56 @@ def _to_shapefile(gdf, outfile, append=False):
         # in case we want to append, we load the old one and add the new one
         if append:
             columns = [
-                'id', 'identifier', 'polarisationmode',
-                'orbitdirection', 'acquisitiondate', 'relativeorbit',
-                'orbitnumber', 'product_type', 'slicenumber', 'size',
-                'beginposition', 'endposition',
-                'lastrelativeorbitnumber', 'lastorbitnumber',
-                'uuid', 'platformidentifier', 'missiondatatakeid',
-                'swathidentifier', 'ingestiondate',
-                'sensoroperationalmode', 'geometry'
+                "id",
+                "identifier",
+                "polarisationmode",
+                "orbitdirection",
+                "acquisitiondate",
+                "relativeorbit",
+                "orbitnumber",
+                "product_type",
+                "slicenumber",
+                "size",
+                "beginposition",
+                "endposition",
+                "lastrelativeorbitnumber",
+                "lastorbitnumber",
+                "uuid",
+                "platformidentifier",
+                "missiondatatakeid",
+                "swathidentifier",
+                "ingestiondate",
+                "sensoroperationalmode",
+                "geometry",
             ]
 
             # get existing geodataframe from file
             old_df = gpd.read_file(outfile)
             old_df.columns = columns
             # drop id
-            old_df.drop('id', axis=1, inplace=True)
+            old_df.drop("id", axis=1, inplace=True)
             # append new results
             gdf.columns = columns[1:]
             gdf = old_df.append(gdf)
 
             # remove duplicate entries
-            gdf.drop_duplicates(subset='identifier', inplace=True)
+            gdf.drop_duplicates(subset="identifier", inplace=True)
 
         # remove old file
         os.remove(outfile)
-        os.remove('{}.cpg'.format(outfile[:-4]))
-        os.remove('{}.prj'.format(outfile[:-4]))
-        os.remove('{}.shx'.format(outfile[:-4]))
-        os.remove('{}.dbf'.format(outfile[:-4]))
+        os.remove("{}.cpg".format(outfile[:-4]))
+        os.remove("{}.prj".format(outfile[:-4]))
+        os.remove("{}.shx".format(outfile[:-4]))
+        os.remove("{}.dbf".format(outfile[:-4]))
 
     # calculate new index
-    gdf.insert(loc=0, column='id', value=range(1, 1 + len(gdf)))
+    gdf.insert(loc=0, column="id", value=range(1, 1 + len(gdf)))
 
     # write to new file
     if len(gdf.index) >= 1:
         gdf.to_file(outfile)
     else:
-        logger.info('No scenes found in this AOI during this time')
+        logger.info("No scenes found in this AOI during this time")
 
 
 def _to_geopackage(gdf, outfile, append=False):
@@ -258,120 +286,135 @@ def _to_geopackage(gdf, outfile, append=False):
         # in case we want to append, we load the old one and add the new one
         if append:
             columns = [
-                'id', 'identifier', 'polarisationmode',
-                'orbitdirection', 'acquisitiondate', 'relativeorbit',
-                'orbitnumber', 'product_type', 'slicenumber', 'size',
-                'beginposition', 'endposition',
-                'lastrelativeorbitnumber', 'lastorbitnumber',
-                'uuid', 'platformidentifier', 'missiondatatakeid',
-                'swathidentifier', 'ingestiondate',
-                'sensoroperationalmode', 'geometry'
+                "id",
+                "identifier",
+                "polarisationmode",
+                "orbitdirection",
+                "acquisitiondate",
+                "relativeorbit",
+                "orbitnumber",
+                "product_type",
+                "slicenumber",
+                "size",
+                "beginposition",
+                "endposition",
+                "lastrelativeorbitnumber",
+                "lastorbitnumber",
+                "uuid",
+                "platformidentifier",
+                "missiondatatakeid",
+                "swathidentifier",
+                "ingestiondate",
+                "sensoroperationalmode",
+                "geometry",
             ]
 
             # get existing geodataframe from file
             old_df = gpd.read_file(outfile)
             old_df.columns = columns
             # drop id
-            old_df.drop('id', axis=1, inplace=True)
+            old_df.drop("id", axis=1, inplace=True)
             # append new results
             gdf.columns = columns[1:]
             gdf = old_df.append(gdf)
 
             # remove duplicate entries
-            gdf.drop_duplicates(subset='identifier', inplace=True)
+            gdf.drop_duplicates(subset="identifier", inplace=True)
 
         # remove old file
         Path(outfile).unlink()
 
     # calculate new index
-    gdf.insert(loc=0, column='id', value=range(1, 1 + len(gdf)))
+    gdf.insert(loc=0, column="id", value=range(1, 1 + len(gdf)))
 
     # write to new file
     if len(gdf.index) > 0:
-        gdf.to_file(outfile, driver='GPKG')
+        gdf.to_file(outfile, driver="GPKG")
     else:
-        logger.info('No scenes found in this AOI during this time')
+        logger.info("No scenes found in this AOI during this time")
 
 
 def _to_postgis(gdf, db_connect, outtable):
 
     # check if tablename already exists
-    db_connect.cursor.execute('SELECT EXISTS (SELECT * FROM '
-                              'information_schema.tables WHERE '
-                              'LOWER(table_name) = '
-                              'LOWER(\'{}\'))'.format(outtable))
+    db_connect.cursor.execute(
+        "SELECT EXISTS (SELECT * FROM "
+        "information_schema.tables WHERE "
+        "LOWER(table_name) = "
+        "LOWER('{}'))".format(outtable)
+    )
     result = db_connect.cursor.fetchall()
     if result[0][0] is False:
-        logger.info(
-            f'Table {outtable} does not exist in the database. Creating it...'
-        )
-        db_connect.pgCreateS1('{}'.format(outtable))
+        logger.info(f"Table {outtable} does not exist in the database. Creating it...")
+        db_connect.pgCreateS1("{}".format(outtable))
         maxid = 1
     else:
         try:
-            maxid = db_connect.pgSQL(f'SELECT max(id) FROM {outtable}')
+            maxid = db_connect.pgSQL(f"SELECT max(id) FROM {outtable}")
             maxid = maxid[0][0]
             if maxid is None:
                 maxid = 0
 
             logger.info(
-                f'Table {outtable} already exists with {maxid} entries. '
-                f'Will add all non-existent results to this table.'
+                f"Table {outtable} already exists with {maxid} entries. "
+                f"Will add all non-existent results to this table."
             )
             maxid = maxid + 1
-        except:
+        except Exception:
             raise RuntimeError(
-                f'Existent table {outtable} does not seem to be compatible '
-                f'with Sentinel-1 data.'
+                f"Existent table {outtable} does not seem to be compatible "
+                f"with Sentinel-1 data."
             )
 
     # add an index as first column
-    gdf.insert(loc=0, column='id', value=range(maxid, maxid + len(gdf)))
+    gdf.insert(loc=0, column="id", value=range(maxid, maxid + len(gdf)))
     db_connect.pgSQLnoResp(
-        f'SELECT UpdateGeometrySRID(\'{outtable.lower()}\', \'geometry\', 0);'
+        f"SELECT UpdateGeometrySRID('{outtable.lower()}', 'geometry', 0);"
     )
 
     # construct the SQL INSERT line
     for _index, row in gdf.iterrows():
 
-        row['geometry'] = dumps(row['footprint'])
-        row.drop('footprint', inplace=True)
+        row["geometry"] = dumps(row["footprint"])
+        row.drop("footprint", inplace=True)
         identifier = row.identifier
         uuid = row.uuid
         line = tuple(row.tolist())
 
         # first check if scene is already in the table
-        result = db_connect.pgSQL('SELECT uuid FROM {} WHERE '
-                                  'uuid = \'{}\''.format(outtable, uuid))
+        result = db_connect.pgSQL(
+            "SELECT uuid FROM {} WHERE " "uuid = '{}'".format(outtable, uuid)
+        )
         try:
             result[0][0]
         except IndexError:
-            logger.info(f'Inserting scene {identifier} to {outtable}')
+            logger.info(f"Inserting scene {identifier} to {outtable}")
             db_connect.pgInsert(outtable, line)
             # apply the dateline correction routine
             db_connect.pgDateline(outtable, uuid)
             maxid += 1
         else:
-            logger.info(
-               f'Scene {identifier} already exists within table {outtable}.'
-            )
+            logger.info(f"Scene {identifier} already exists within table {outtable}.")
 
-    logger.info(f'Inserted {len(gdf)} entries into {outtable}.')
-    logger.info(f'Table {outtable} now contains {maxid - 1} entries.')
-    logger.info('Optimising database table.')
+    logger.info(f"Inserted {len(gdf)} entries into {outtable}.")
+    logger.info(f"Table {outtable} now contains {maxid - 1} entries.")
+    logger.info("Optimising database table.")
 
     # drop index if existent
     try:
-        db_connect.pgSQLnoResp('DROP INDEX {}_gix;'.format(outtable.lower()))
-    except:
+        db_connect.pgSQLnoResp("DROP INDEX {}_gix;".format(outtable.lower()))
+    except Exception:
         pass
 
     # create geometry index and vacuum analyze
-    db_connect.pgSQLnoResp('SELECT UpdateGeometrySRID(\'{}\', '
-                           '\'geometry\', 4326);'.format(outtable.lower()))
-    db_connect.pgSQLnoResp('CREATE INDEX {}_gix ON {} USING GIST '
-                           '(geometry);'.format(outtable, outtable.lower()))
-    db_connect.pgSQLnoResp('VACUUM ANALYZE {};'.format(outtable.lower()))
+    db_connect.pgSQLnoResp(
+        "SELECT UpdateGeometrySRID('{}', " "'geometry', 4326);".format(outtable.lower())
+    )
+    db_connect.pgSQLnoResp(
+        "CREATE INDEX {}_gix ON {} USING GIST "
+        "(geometry);".format(outtable, outtable.lower())
+    )
+    db_connect.pgSQLnoResp("VACUUM ANALYZE {};".format(outtable.lower()))
 
 
 def check_availability(inventory_gdf, download_dir, data_mount):
@@ -383,20 +426,25 @@ def check_availability(inventory_gdf, download_dir, data_mount):
     :param data_mount:
     :return:
     """
-    
+
     from ost import Sentinel1Scene
 
     # add download path, or set to None if not found
-    inventory_gdf['download_path'] = inventory_gdf.identifier.apply(
+    inventory_gdf["download_path"] = inventory_gdf.identifier.apply(
         lambda row: str(Sentinel1Scene(row).get_path(download_dir, data_mount))
     )
-    
+
     return inventory_gdf
 
 
-def scihub_catalogue(query_string, output, append=False,
-                     uname=None, pword=None,
-                     base_url='https://apihub.copernicus.eu/apihub'):
+def scihub_catalogue(
+    query_string,
+    output,
+    append=False,
+    uname=None,
+    pword=None,
+    base_url="https://apihub.copernicus.eu/apihub",
+):
     """This is the main search function on scihub
 
     :param query_string:
@@ -411,21 +459,21 @@ def scihub_catalogue(query_string, output, append=False,
     output = str(output)
 
     # get connected to scihub
-    hub = f'{base_url}/search?q='
+    hub = f"{base_url}/search?q="
     opener = scihub.connect(uname, pword, base_url)
-    query = f'{hub}{query_string}'
-    
+    query = f"{hub}{query_string}"
+
     # get the catalogue in a dict
     gdf = _query_scihub(opener, query)
 
     if output[-4:] == ".shp":
-        logger.info(f'Writing inventory data to shape file: {output}')
+        logger.info(f"Writing inventory data to shape file: {output}")
         _to_shapefile(gdf, output, append)
     elif output[-5:] == ".gpkg":
-        logger.info(f'Writing inventory data to geopackage file: {output}')
+        logger.info(f"Writing inventory data to geopackage file: {output}")
         _to_geopackage(gdf, output, append)
     else:
-        logger.info(f'Writing inventory data toPostGIS table: {output}')
+        logger.info(f"Writing inventory data toPostGIS table: {output}")
         db_connect = pgHandler()
         _to_postgis(gdf, db_connect, output)
 
@@ -459,61 +507,78 @@ def scihub_catalogue(query_string, output, append=False,
     PARSER = argparse.ArgumentParser(description=DESCRIPT, epilog=EPILOG)
 
     # username/password scihub
-    PARSER.add_argument("-u", "--username",
-                        help=" Your username of scihub.copernicus.eu ",
-                        default=None)
-    PARSER.add_argument("-p", "--password",
-                        help=" Your secret password of scihub.copernicus.eu ",
-                        default=None)
-    PARSER.add_argument("-a", "--areaofinterest",
-                        help=(' The Area of Interest (path to a shapefile'
-                              'or ISO3 country code)'),
-                        dest='aoi', default='*',
-                        type=lambda x: helpers.is_valid_aoi(PARSER, x))
-    PARSER.add_argument("-b", "--begindate",
-                        help=" The Start Date (format: YYYY-MM-DD) ",
-                        default="2014-10-01",
-                        type=lambda x: helpers.is_valid_date(PARSER, x))
-    PARSER.add_argument("-e", "--enddate",
-                        help=" The End Date (format: YYYY-MM-DD)",
-                        default=NOW,
-                        type=lambda x: helpers.is_valid_date(PARSER, x))
-    PARSER.add_argument("-t", "--producttype",
-                        help=" The Product Type (RAW, SLC, GRD, *) ",
-                        default='*')
-    PARSER.add_argument("-m", "--polarisation",
-                        help=" The Polarisation Mode (VV, VH, HH, HV, *) ",
-                        default='*')
-    PARSER.add_argument("-b", "--beammode",
-                        help=" The Beam Mode (IW, EW, SM, *) ",
-                        default='*')
+    PARSER.add_argument(
+        "-u", "--username", help=" Your username of scihub.copernicus.eu ", default=None
+    )
+    PARSER.add_argument(
+        "-p",
+        "--password",
+        help=" Your secret password of scihub.copernicus.eu ",
+        default=None,
+    )
+    PARSER.add_argument(
+        "-a",
+        "--areaofinterest",
+        help=(" The Area of Interest (path to a shapefile" "or ISO3 country code)"),
+        dest="aoi",
+        default="*",
+        type=lambda x: helpers.is_valid_aoi(PARSER, x),
+    )
+    PARSER.add_argument(
+        "-b",
+        "--begindate",
+        help=" The Start Date (format: YYYY-MM-DD) ",
+        default="2014-10-01",
+        type=lambda x: helpers.is_valid_date(PARSER, x),
+    )
+    PARSER.add_argument(
+        "-e",
+        "--enddate",
+        help=" The End Date (format: YYYY-MM-DD)",
+        default=NOW,
+        type=lambda x: helpers.is_valid_date(PARSER, x),
+    )
+    PARSER.add_argument(
+        "-t", "--producttype", help=" The Product Type (RAW, SLC, GRD, *) ", default="*"
+    )
+    PARSER.add_argument(
+        "-m",
+        "--polarisation",
+        help=" The Polarisation Mode (VV, VH, HH, HV, *) ",
+        default="*",
+    )
+    PARSER.add_argument(
+        "-b", "--beammode", help=" The Beam Mode (IW, EW, SM, *) ", default="*"
+    )
 
     # output parameters
-    PARSER.add_argument("-o", "--output",
-                        help=(' Output format/file. Can be a shapefile'
-                              ' (ending with .shp), a SQLite file'
-                              ' (ending with .sqlite) or a PostGreSQL table'
-                              ' (connection needs to be configured). '),
-                        required=True)
+    PARSER.add_argument(
+        "-o",
+        "--output",
+        help=(
+            " Output format/file. Can be a shapefile"
+            " (ending with .shp), a SQLite file"
+            " (ending with .sqlite) or a PostGreSQL table"
+            " (connection needs to be configured). "
+        ),
+        required=True,
+    )
 
     ARGS = PARSER.parse_args()
 
     # execute full search
-    if ARGS.aoi != '*' and ARGS.aoi[-4] == 'shp':
+    if ARGS.aoi != "*" and ARGS.aoi[-4] == "shp":
         AOI = os.path.abspath(ARGS.aoi)
     else:
-        AOI = '*'
+        AOI = "*"
 
     # construct the search command (do not change)
     AOI = scihub.create_aoi_str(AOI)
     TOI = scihub.create_toi_str(ARGS.begindate, ARGS.enddate)
-    PRODUCT_SPECS = scihub.create_s1_product_specs(ARGS.producttype,
-                                                   ARGS.polarisation,
-                                                   ARGS.beam)
-
-
-    QUERY = urllib.parse.quote(
-        f'Sentinel-1 AND {PRODUCT_SPECS} AND {AOI} AND {TOI}'
+    PRODUCT_SPECS = scihub.create_s1_product_specs(
+        ARGS.producttype, ARGS.polarisation, ARGS.beam
     )
+
+    QUERY = urllib.parse.quote(f"Sentinel-1 AND {PRODUCT_SPECS} AND {AOI} AND {TOI}")
     # execute full search
     scihub_catalogue(QUERY, ARGS.output, ARGS.username, ARGS.password)
diff --git a/ost/s1/slc_wrappers.py b/ost/s1/slc_wrappers.py
index 20b2bb89..56e7517e 100644
--- a/ost/s1/slc_wrappers.py
+++ b/ost/s1/slc_wrappers.py
@@ -14,14 +14,7 @@
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
-def burst_import(
-        infile,
-        outfile,
-        logfile,
-        swath,
-        burst,
-        config_dict
-):
+def burst_import(infile, outfile, logfile, swath, burst, config_dict):
     """A wrapper of SNAP import of a single Sentinel-1 SLC burst
 
     This function takes an original Sentinel-1 scene (either zip or
@@ -39,57 +32,53 @@ def burst_import(
     """
 
     # get polarisations to import
-    ard = config_dict['processing']['single_ARD']
-    bs_polar = ard['polarisation'].replace(' ', ',')
-    coh_polar = ard['coherence_bands'].replace(' ', ',')
-    subset = config_dict['subset']
+    ard = config_dict["processing"]["single_ARD"]
+    bs_polar = ard["polarisation"].replace(" ", ",")
+    coh_polar = ard["coherence_bands"].replace(" ", ",")
+    subset = config_dict["subset"]
 
-    region = config_dict['aoi'] if subset else ''
+    region = config_dict["aoi"] if subset else ""
 
-    if ard['coherence']:
+    if ard["coherence"]:
         polars = bs_polar if len(bs_polar) >= len(coh_polar) else coh_polar
     else:
         polars = bs_polar
 
     # get cpus
-    cpus = config_dict['snap_cpu_parallelism']
+    cpus = config_dict["snap_cpu_parallelism"]
 
     # get path to graph
-    graph = OST_ROOT.joinpath('graphs/S1_SLC2ARD/S1_SLC_BurstSplit_AO.xml')
+    graph = OST_ROOT / "graphs" / "S1_SLC2ARD" / "S1_SLC_BurstSplit_AO.xml"
 
-    logger.debug(
-        f'Importing Burst {burst} from Swath {swath} from scene {infile.name}'
-    )
+    logger.debug(f"Importing Burst {burst} from Swath {swath} from scene {infile.name}")
 
     command = (
-        f'{GPT_FILE} {graph} -x -q {2 * cpus} '
-        f'-Pinput={str(infile)} '
-        f'-Ppolar={polars} '
-        f'-Pswath={swath} '
-        f'-Pburst={burst} '
-        f'-Pregion=\'{region}\' '
-        f'-Poutput={str(outfile)}'
+        f"{GPT_FILE} {graph} -x -q {2 * cpus} "
+        f"-Pinput={str(infile)} "
+        f"-Ppolar={polars} "
+        f"-Pswath={swath} "
+        f"-Pburst={burst} "
+        f"-Pregion='{region}' "
+        f"-Poutput={str(outfile)}"
     )
 
-    logger.debug(f'Executing command: {command}')
+    logger.debug(f"Executing command: {command}")
     return_code = h.run_command(command, logfile)
 
     if return_code == 0:
-        logger.debug('Succesfully imported burst.')
+        logger.debug("Succesfully imported burst.")
     else:
         raise GPTRuntimeError(
-            f'Frame import exited with error {return_code}. '
-            f'See {logfile} for Snap\'s error message.'
+            f"Frame import exited with error {return_code}. "
+            f"See {logfile} for Snap's error message."
         )
 
     # do check routine
     return_code = h.check_out_dimap(outfile)
     if return_code == 0:
-        return str(outfile.with_suffix('.dim'))
+        return str(outfile.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
@@ -108,25 +97,23 @@ def ha_alpha(infile, outfile, logfile, config_dict):
     """
 
     # get relevant config parameters
-    ard = config_dict['processing']['single_ARD']
-    remove_pol_speckle = ard['remove_pol_speckle']
-    pol_speckle_dict = ard['pol_speckle_filter']
-    cpus = config_dict['snap_cpu_parallelism']
+    ard = config_dict["processing"]["single_ARD"]
+    remove_pol_speckle = ard["remove_pol_speckle"]
+    pol_speckle_dict = ard["pol_speckle_filter"]
+    cpus = config_dict["snap_cpu_parallelism"]
 
     if remove_pol_speckle:
-        graph = OST_ROOT.joinpath(
-            'graphs/S1_SLC2ARD/S1_SLC_Deb_Spk_Halpha.xml'
-        )
+        graph = OST_ROOT / "graphs" / "S1_SLC2ARD" / "S1_SLC_Deb_Spk_Halpha.xml"
         logger.debug(
-            'Applying the polarimetric speckle filter and'
-            ' calculating the H-alpha dual-pol decomposition'
+            "Applying the polarimetric speckle filter and"
+            " calculating the H-alpha dual-pol decomposition"
         )
 
         command = (
-            f'{GPT_FILE} {graph} -x -q {2 * cpus} '
-            f'-Pinput={str(infile)} '
-            f'-Poutput={str(outfile)} '
-            f"-Pfilter=\'{pol_speckle_dict['polarimetric_filter']}\' "
+            f"{GPT_FILE} {graph} -x -q {2 * cpus} "
+            f"-Pinput={str(infile)} "
+            f"-Poutput={str(outfile)} "
+            f"-Pfilter='{pol_speckle_dict['polarimetric_filter']}' "
             f'-Pfilter_size=\'{pol_speckle_dict["filter_size"]}\' '
             f'-Pnr_looks={pol_speckle_dict["num_of_looks"]} '
             f'-Pwindow_size={pol_speckle_dict["window_size"]} '
@@ -135,45 +122,36 @@ def ha_alpha(infile, outfile, logfile, config_dict):
             f'-Psigma={pol_speckle_dict["sigma"]}'
         )
     else:
-        graph = OST_ROOT.joinpath(
-            'graphs/S1_SLC2ARD/S1_SLC_Deb_Halpha.xml'
-        )
+        graph = OST_ROOT / "graphs" / "S1_SLC2ARD" / "S1_SLC_Deb_Halpha.xml"
 
-        logger.debug('Calculating the H-alpha dual polarisation')
+        logger.debug("Calculating the H-alpha dual polarisation")
         command = (
-            f'{GPT_FILE} {graph} -x -q {2 * cpus} '
+            f"{GPT_FILE} {graph} -x -q {2 * cpus} "
             f'-Pinput="{str(infile)}" '
             f'-Poutput="{str(outfile)}"'
         )
 
-    logger.debug(f'Executing command: {command}')
+    logger.debug(f"Executing command: {command}")
     return_code = h.run_command(command, logfile)
 
     if return_code == 0:
-        logger.debug('Succesfully created H/A/Alpha product')
+        logger.debug("Succesfully created H/A/Alpha product")
     else:
         raise GPTRuntimeError(
-            f'H/Alpha exited with an error {return_code}. '
-            f'See {logfile} for Snap\'s error message.'
+            f"H/Alpha exited with an error {return_code}. "
+            f"See {logfile} for Snap's error message."
         )
 
     # do check routine
     return_code = h.check_out_dimap(outfile)
     if return_code == 0:
-        return str(outfile.with_suffix('.dim'))
+        return str(outfile.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
-def calibration(
-        infile,
-        outfile,
-        logfile,
-        config_dict
-):
+def calibration(infile, outfile, logfile, config_dict):
     """A wrapper around SNAP's radiometric calibration
 
     This function takes OST imported Sentinel-1 product and generates
@@ -193,22 +171,22 @@ def calibration(
     """
 
     # get relevant config parameters
-    ard = config_dict['processing']['single_ARD']
-    cpus = config_dict['snap_cpu_parallelism']
-    dem_dict = ard['dem']
-    region = ''
+    ard = config_dict["processing"]["single_ARD"]
+    cpus = config_dict["snap_cpu_parallelism"]
+    dem_dict = ard["dem"]
+    region = ""
 
     # calculate Multi-Look factors
     azimuth_looks = 1  # int(np.floor(ard['resolution'] / 10 ))
     range_looks = 6  # int(azimuth_looks * 5)
 
     # construct command dependent on selected product type
-    if ard['product_type'] == 'RTC-gamma0':
-        logger.debug('Calibrating the product to a RTC product.')
+    if ard["product_type"] == "RTC-gamma0":
+        logger.debug("Calibrating the product to a RTC product.")
 
         # get graph for RTC generation
-        graph = OST_ROOT.joinpath(
-            'graphs/S1_SLC2ARD/S1_SLC_TNR_CalBeta_Deb_ML_TF_Sub.xml'
+        graph = (
+            OST_ROOT / "graphs" / "S1_SLC2ARD" / "S1_SLC_TNR_CalBeta_Deb_ML_TF_Sub.xml"
         )
 
         # construct command
@@ -216,73 +194,71 @@ def calibration(
             f"{GPT_FILE} {graph} -x -q {2 * cpus} "
             f"-Prange_looks={range_looks} "
             f"-Pazimuth_looks={azimuth_looks} "
-            f"-Pdem=\'{dem_dict['dem_name']}\' "
-            f"-Pdem_file=\'{dem_dict['dem_file']}\' "
+            f"-Pdem='{dem_dict['dem_name']}' "
+            f"-Pdem_file='{dem_dict['dem_file']}' "
             f"-Pdem_nodata={dem_dict['dem_nodata']} "
             f"-Pdem_resampling={dem_dict['dem_resampling']} "
             f"-Pegm_correction={dem_dict['egm_correction']} "
-            f"-Pregion=\'{region}\' "
+            f"-Pregion='{region}' "
             f"-Pinput={str(infile)} "
             f"-Poutput={str(outfile)}"
         )
 
-    elif ard['product_type'] == 'GTC-gamma0':
-        logger.debug('Calibrating the product to a GTC product (Gamma0).')
+    elif ard["product_type"] == "GTC-gamma0":
+        logger.debug("Calibrating the product to a GTC product (Gamma0).")
 
         # get graph for GTC-gammao0 generation
-        graph = OST_ROOT.joinpath(
-            'graphs/S1_SLC2ARD/S1_SLC_TNR_CalGamma_Deb_ML_Sub.xml'
+        graph = (
+            OST_ROOT / "graphs" / "S1_SLC2ARD" / "S1_SLC_TNR_CalGamma_Deb_ML_Sub.xml"
         )
 
         # construct command
         command = (
-            f'{GPT_FILE} {graph} -x -q {2 * cpus} '
-            f'-Prange_looks={range_looks} '
-            f'-Pazimuth_looks={azimuth_looks} '
+            f"{GPT_FILE} {graph} -x -q {2 * cpus} "
+            f"-Prange_looks={range_looks} "
+            f"-Pazimuth_looks={azimuth_looks} "
             f'-Pregion="{region}" '
             f'-Pinput="{str(infile)}" '
             f'-Poutput="{str(outfile)}"'
         )
 
-    elif ard['product_type'] == 'GTC-sigma0':
-        logger.debug('Calibrating the product to a GTC product (Sigma0).')
+    elif ard["product_type"] == "GTC-sigma0":
+        logger.debug("Calibrating the product to a GTC product (Sigma0).")
 
         # get graph for GTC-sigma0 generation
-        graph = OST_ROOT.joinpath(
-            'graphs/S1_SLC2ARD/S1_SLC_TNR_CalSigma_Deb_ML_Sub.xml'
+        graph = (
+            OST_ROOT / "graphs" / "S1_SLC2ARD" / "S1_SLC_TNR_CalSigma_Deb_ML_Sub.xml"
         )
 
         # construct command
         command = (
-            f'{GPT_FILE} {graph} -x -q {2 * cpus} '
-            f'-Prange_looks={range_looks} '
-            f'-Pazimuth_looks={azimuth_looks} '
+            f"{GPT_FILE} {graph} -x -q {2 * cpus} "
+            f"-Prange_looks={range_looks} "
+            f"-Pazimuth_looks={azimuth_looks} "
             f'-Pregion="{region}" '
             f'-Pinput="{str(infile)}" '
             f'-Poutput="{str(outfile)}"'
         )
     else:
-        raise TypeError('Wrong product type selected.')
+        raise TypeError("Wrong product type selected.")
 
-    logger.debug(f'Command: {command}')
+    logger.debug(f"Command: {command}")
     return_code = h.run_command(command, logfile)
 
     if return_code == 0:
-        logger.debug('Succesfully calibrated product')
+        logger.debug("Succesfully calibrated product")
     else:
         raise GPTRuntimeError(
-            f'Calibration exited with an error {return_code}. '
-            f'See {logfile} for Snap\'s error output.'
+            f"Calibration exited with an error {return_code}. "
+            f"See {logfile} for Snap's error output."
         )
 
     # do check routine
     return_code = h.check_out_dimap(outfile)
     if return_code == 0:
-        return str(outfile.with_suffix('.dim'))
+        return str(outfile.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
@@ -303,43 +279,41 @@ def coreg(master, slave, outfile, logfile, config_dict):
     """
 
     # get relevant config parameters
-    cpus = config_dict['snap_cpu_parallelism']
-    dem_dict = config_dict['processing']['single_ARD']['dem']
+    cpus = config_dict["snap_cpu_parallelism"]
+    dem_dict = config_dict["processing"]["single_ARD"]["dem"]
 
-    logger.debug(f'Co-registering {master} and {slave}')
+    logger.debug(f"Co-registering {master} and {slave}")
 
     # construct command
     command = (
-        f'{GPT_FILE} Back-Geocoding -x -q {2*cpus} '
+        f"{GPT_FILE} Back-Geocoding -x -q {2*cpus} "
         f'-PdemName=\'{dem_dict["dem_name"]}\' '
         f'-PdemResamplingMethod=\'{dem_dict["dem_resampling"]}\' '
         f'-PexternalDEMFile=\'{dem_dict["dem_file"]}\' '
         f'-PexternalDEMNoDataValue=\'{dem_dict["dem_nodata"]}\' '
-        f'-PmaskOutAreaWithoutElevation=false '
-        f'-PresamplingType=BILINEAR_INTERPOLATION '
-        f'-t \'{str(outfile)}\''
+        f"-PmaskOutAreaWithoutElevation=false "
+        f"-PresamplingType=BILINEAR_INTERPOLATION "
+        f"-t '{str(outfile)}'"
         f' "{master}" "{slave}"'
     )
 
-    logger.debug(f'Executing command: {command}')
+    logger.debug(f"Executing command: {command}")
     return_code = h.run_command(command, logfile)
 
     if return_code == 0:
-        logger.debug('Succesfully coregistered product.')
+        logger.debug("Succesfully coregistered product.")
     else:
         raise GPTRuntimeError(
-            f'Co-registration exited with an error {return_code}. '
-            f'See {logfile} for Snap error output.'
+            f"Co-registration exited with an error {return_code}. "
+            f"See {logfile} for Snap error output."
         )
 
     # do check routine
     return_code = h.check_out_dimap(outfile)
     if return_code == 0:
-        return str(outfile.with_suffix('.dim'))
+        return str(outfile.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
@@ -360,42 +334,40 @@ def coreg2(master, slave, outfile, logfile, config_dict):
     """
 
     # get relevant config parameters
-    cpus = config_dict['snap_cpu_parallelism']
-    dem_dict = config_dict['processing']['single_ARD']['dem']
+    cpus = config_dict["snap_cpu_parallelism"]
+    dem_dict = config_dict["processing"]["single_ARD"]["dem"]
 
     # get path to graph
-    graph = OST_ROOT.joinpath('graphs/S1_SLC2ARD/S1_SLC_Coreg.xml')
+    graph = OST_ROOT / "graphs" / "S1_SLC2ARD" / "S1_SLC_Coreg.xml"
 
-    logger.debug(f'Co-registering {master} and {slave}')
+    logger.debug(f"Co-registering {master} and {slave}")
     command = (
         f"{GPT_FILE} {graph} -x -q {2*cpus} "
         f" -Pmaster={master} "
         f" -Pslave={slave} "
-        f" -Pdem_name=\'{dem_dict['dem_name']}\' "
-        f" -Pdem_file=\'{dem_dict['dem_file']}\' "
-        f" -Pdem_nodata=\'{dem_dict['dem_nodata']}\' "
-        f" -Pdem_resampling=\'{dem_dict['dem_resampling']}\' "
+        f" -Pdem_name='{dem_dict['dem_name']}' "
+        f" -Pdem_file='{dem_dict['dem_file']}' "
+        f" -Pdem_nodata='{dem_dict['dem_nodata']}' "
+        f" -Pdem_resampling='{dem_dict['dem_resampling']}' "
         f" -Poutput={str(outfile)}"
     )
 
     return_code = h.run_command(command, logfile)
 
     if return_code == 0:
-        logger.debug('Successfully co-registered product.')
+        logger.debug("Successfully co-registered product.")
     else:
         raise GPTRuntimeError(
-            f'Co-registration exited with an error {return_code}. '
-            f'See {logfile} for Snap\'s error message.'
+            f"Co-registration exited with an error {return_code}. "
+            f"See {logfile} for Snap's error message."
         )
 
     # do check routine
     return_code = h.check_out_dimap(outfile)
     if return_code == 0:
-        return str(outfile.with_suffix('.dim'))
+        return str(outfile.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
 
 
 @retry(stop_max_attempt_number=3, wait_fixed=1)
@@ -413,40 +385,38 @@ def coherence(infile, outfile, logfile, config_dict):
     """
 
     # get relevant config parameters
-    ard = config_dict['processing']['single_ARD']
-    polars = ard['coherence_bands'].replace(' ', '')
-    cpus = config_dict['snap_cpu_parallelism']
+    ard = config_dict["processing"]["single_ARD"]
+    polars = ard["coherence_bands"].replace(" ", "")
+    cpus = config_dict["snap_cpu_parallelism"]
 
     # get path to graph
-    graph = OST_ROOT.joinpath('graphs/S1_SLC2ARD/S1_SLC_Coh_Deb.xml')
+    graph = OST_ROOT / "graphs" / "S1_SLC2ARD" / "S1_SLC_Coh_Deb.xml"
 
-    logger.debug('Coherence estimation')
+    logger.debug("Coherence estimation")
 
     command = (
         f"{GPT_FILE} {graph} -x -q {2 * cpus} "
         f"-Pazimuth_window={ard['coherence_azimuth']} "
         f"-Prange_window={ard['coherence_range']} "
-        f'-Ppolar=\'{polars}\' '
+        f"-Ppolar='{polars}' "
         f'-Pinput="{str(infile)}" '
         f'-Poutput="{str(outfile)}"'
     )
 
-    logger.debug(f'Executing command: {command}')
+    logger.debug(f"Executing command: {command}")
     return_code = h.run_command(command, logfile)
 
     if return_code == 0:
-        logger.debug('Succesfully created coherence product.')
+        logger.debug("Succesfully created coherence product.")
     else:
         raise GPTRuntimeError(
-            f'Coherence exited with an error {return_code}. '
-            f'See {logfile} for Snap\'s error message.'
+            f"Coherence exited with an error {return_code}. "
+            f"See {logfile} for Snap's error message."
         )
 
     # do check routine
     return_code = h.check_out_dimap(outfile)
     if return_code == 0:
-        return str(outfile.with_suffix('.dim'))
+        return str(outfile.with_suffix(".dim"))
     else:
-        raise NotValidFileError(
-            f'Product did not pass file check: {return_code}'
-        )
+        raise NotValidFileError(f"Product did not pass file check: {return_code}")
diff --git a/ost/s1/ts.py b/ost/s1/ts.py
index 8f91265a..ed90e7a5 100644
--- a/ost/s1/ts.py
+++ b/ost/s1/ts.py
@@ -16,31 +16,33 @@
 
 
 def create_datelist(path_to_timeseries):
-    '''Create a text file of acquisition dates within your time-series
+    """Create a text file of acquisition dates within your time-series
 
     Args:
         path_to_timeseries (str): path to an OST time-series directory
-    '''
+    """
 
-    files = glob.glob('{}/*VV*tif'.format(path_to_timeseries))
-    dates = sorted([os.path.basename(file).split('.')[1] for file in files])
+    files = glob.glob("{}/*VV*tif".format(path_to_timeseries))
+    dates = sorted([os.path.basename(file).split(".")[1] for file in files])
 
-    with open('{}/datelist.txt'.format(path_to_timeseries), 'w') as file:
+    with open("{}/datelist.txt".format(path_to_timeseries), "w") as file:
         for date in dates:
-            file.write(str(datetime.strftime(datetime.strptime(
-                date, '%y%m%d'), '%Y-%m-%d')) + ' \n')
+            file.write(
+                str(datetime.strftime(datetime.strptime(date, "%y%m%d"), "%Y-%m-%d"))
+                + " \n"
+            )
 
 
 def create_ts_animation(ts_dir, temp_dir, outfile, shrink_factor):
 
-    for file in sorted(glob.glob(opj(ts_dir, '*VV.tif'))):
+    for file in sorted(glob.glob(opj(ts_dir, "*VV.tif"))):
 
-        file_index = os.path.basename(file).split('.')[0]
-        date = os.path.basename(file).split('.')[1]
+        file_index = os.path.basename(file).split(".")[0]
+        date = os.path.basename(file).split(".")[1]
         file_vv = file
-        file_vh = glob.glob(opj(ts_dir, '{}.*VH.tif'.format(file_index)))[0]
+        file_vh = glob.glob(opj(ts_dir, "{}.*VH.tif".format(file_index)))[0]
 
-        out_temp = opj(temp_dir, '{}.jpg'.format(date))
+        out_temp = opj(temp_dir, "{}.jpg".format(date))
 
         with rasterio.open(file_vv) as vv_pol:
 
@@ -48,16 +50,14 @@ def create_ts_animation(ts_dir, temp_dir, outfile, shrink_factor):
             out_meta = vv_pol.meta.copy()
 
             # !!!assure that dimensions match ####
-            new_height = int(vv_pol.height/shrink_factor)
-            new_width = int(vv_pol.width/shrink_factor)
+            new_height = int(vv_pol.height / shrink_factor)
+            new_width = int(vv_pol.width / shrink_factor)
             out_shape = (vv_pol.count, new_height, new_width)
 
             out_meta.update(height=new_height, width=new_width)
 
             # create empty array
-            arr = np.zeros((int(out_meta['height']),
-                            int(out_meta['width']),
-                            int(3)))
+            arr = np.zeros((int(out_meta["height"]), int(out_meta["width"]), int(3)))
             # read vv array
             arr[:, :, 0] = vv_pol.read(out_shape=out_shape, resampling=5)
 
@@ -69,34 +69,33 @@ def create_ts_animation(ts_dir, temp_dir, outfile, shrink_factor):
         arr[:, :, 2] = np.subtract(arr[:, :, 0], arr[:, :, 1])
 
         # rescale_to_datatype to uint8
-        arr[:, :, 0] = ras.scale_to_int(arr[:, :, 0], -20., 0., 'uint8')
-        arr[:, :, 1] = ras.scale_to_int(arr[:, :, 1], -25., -5., 'uint8')
-        arr[:, :, 2] = ras.scale_to_int(arr[:, :, 2], 1., 15., 'uint8')
+        arr[:, :, 0] = ras.scale_to_int(arr[:, :, 0], -20.0, 0.0, "uint8")
+        arr[:, :, 1] = ras.scale_to_int(arr[:, :, 1], -25.0, -5.0, "uint8")
+        arr[:, :, 2] = ras.scale_to_int(arr[:, :, 2], 1.0, 15.0, "uint8")
 
         # update outfile's metadata
-        out_meta.update({'driver': 'JPEG',
-                         'dtype': 'uint8',
-                         'count': 3})
+        out_meta.update({"driver": "JPEG", "dtype": "uint8", "count": 3})
 
         # transpose array to gdal format
         arr = np.transpose(arr, [2, 0, 1])
 
         # write array to disk
-        with rasterio.open(out_temp, 'w', **out_meta) as out:
-            out.write(arr.astype('uint8'))
+        with rasterio.open(out_temp, "w", **out_meta) as out:
+            out.write(arr.astype("uint8"))
 
         # add date
-        label_height = np.floor(np.divide(int(out_meta['height']), 15))
-        cmd = 'convert -background \'#0008\' -fill white -gravity center \
+        label_height = np.floor(np.divide(int(out_meta["height"]), 15))
+        cmd = "convert -background '#0008' -fill white -gravity center \
               -size {}x{} caption:\"{}\" {} +swap -gravity north \
-              -composite {}'.format(out_meta['width'], label_height,
-                                    date, out_temp, out_temp)
+              -composite {}".format(
+            out_meta["width"], label_height, date, out_temp, out_temp
+        )
         os.system(cmd)
 
     # create gif
-    lst_of_files = ' '.join(sorted(glob.glob(opj(temp_dir, '*jpg'))))
-    cmd = 'convert -delay 200 -loop 20 {} {}'.format(lst_of_files, outfile)
+    lst_of_files = " ".join(sorted(glob.glob(opj(temp_dir, "*jpg"))))
+    cmd = "convert -delay 200 -loop 20 {} {}".format(lst_of_files, outfile)
     os.system(cmd)
 
-    for file in glob.glob(opj(temp_dir, '*jpg')):
+    for file in glob.glob(opj(temp_dir, "*jpg")):
         os.remove(file)
diff --git a/setup.py b/setup.py
index f40f519c..03abb43b 100755
--- a/setup.py
+++ b/setup.py
@@ -1,50 +1,105 @@
-import pathlib
+from pathlib import Path
 from setuptools import setup, find_packages
+from setuptools.command.develop import develop
+from subprocess import check_call
+
+# check that gdal has already been installed prior to ost
+try:
+    from osgeo import gdal
+except ModuleNotFoundError:
+    raise ImportError(
+        "please install a GDAL distribution>=1.7 prior to install OpenSarTollkit"
+    )
+
+# to make flake8 happy
+gdal.__version__
+
+# the version number
+version = "0.12.5"
 
 # The directory containing this file
-HERE = pathlib.Path(__file__).parent
+HERE = Path(__file__).parent
+
+DESCRIPTION = "High-level functionality for the inventory, download and pre-processing of Sentinel-1 data"
+LONG_DESCRIPTION = (HERE / "README.rst").read_text()
+
+
+class DevelopCmd(develop):
+    """overwrite normal develop pip command to install the pre-commit"""
+
+    def run(self):
+        check_call(
+            [
+                "pre-commit",
+                "install",
+                "--install-hooks",
+                "-t",
+                "pre-commit",
+                "-t",
+                "commit-msg",
+            ]
+        )
+        super(DevelopCmd, self).run()
 
-# The text of the README file
-README = (HERE / "README.rst").read_text()
 
 setup(
-    name='opensartoolkit',
+    name="opensartoolkit",
+    version=version,
+    license="MIT License",
+    description=DESCRIPTION,
+    long_description=LONG_DESCRIPTION,
+    long_description_content_type="text/x-rst",
+    author="Andreas Vollrath",
+    author_email="opensarkit@gmail.com",
+    url="https://github.com/ESA-PhiLab/OpenSarToolkit",
+    download_url=f"https://github.com/ESA-PhiLab/OpenSarToolkit/archive/{version}.tar.gz",
+    keywords=[
+        "Sentinel-1",
+        "ESA",
+        "SAR",
+        "Radar",
+        "Earth Observation",
+        "Remote Sensing",
+        "Synthetic Aperture Radar",
+    ],
     packages=find_packages(),
     include_package_data=True,
-    version='0.12.5',
-    description='High-level functionality for the inventory, download '
-                'and pre-processing of Sentinel-1 data',
     install_requires=[
-        'descartes',
-        'fiona',
-        'gdal>=2',
-        'godale',
-        'geopandas>=0.8',
-        'pyproj>=2.1',
-        'jupyterlab',
-        'matplotlib',
-        'numpy',
-        'pandas',
-        'psycopg2-binary',
-        'rasterio',
-        'requests',
-        'scipy',
-        'shapely',
-        'tqdm',
-        'imageio',
-        'rtree',
-        'retrying'
+        "pyproj",
+        "descartes",
+        "godale",
+        "geopandas>=0.8",
+        "jupyterlab",
+        "psycopg2-binary",
+        "rasterio",
+        "requests",
+        "scipy",
+        "tqdm",
+        "imageio",
+        "rtree",
+        "retrying",
     ],
-    url='https://github.com/ESA-PhiLab/OpenSarToolkit',
-    author='Andreas Vollrath',
-    author_email="opensarkit@gmail.com",
-    license='MIT License',
-    keywords=['Sentinel-1', 'ESA', 'SAR', 'Radar',
-              'Earth Observation', 'Remote Sensing',
-              'Synthetic Aperture Radar'],
-    long_description=README,
-    long_description_content_type="text/markdown",
+    extras_require={
+        "dev": [
+            "pre-commit",
+            "pytest",
+            "coverage",
+            "nbsphinx",
+            "pydata-sphinx-theme",
+            "sphinx-copybutton",
+        ]
+    },
+    cmdclass={"develop": DevelopCmd},
     zip_safe=False,
-    setup_requires=['pytest-runner'],
-    tests_require=['pytest']
+    classifiers=[
+        "Development Status :: 5 - Production/Stable",
+        "Intended Audience :: Science/Research",
+        "License :: OSI Approved :: MIT License",
+        "Natural Language :: English",
+        "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
+        "Topic :: Scientific/Engineering :: GIS",
+    ],
 )


Test patch:
diff --git a/docs/source/example/latest_S1_scene.ipynb b/docs/source/example/latest_S1_scene.ipynb
new file mode 100644
index 00000000..3f2b9f7a
--- /dev/null
+++ b/docs/source/example/latest_S1_scene.ipynb
@@ -0,0 +1,442 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "6zVUMYgV537n"
+   },
+   "source": [
+    "<font face=\"Calibri\" size=\"2\"> <i>Open SAR Toolkit - Tutorial 3, version 1.2, July 2020. Andreas Vollrath, ESA/ESRIN phi-lab</i>\n",
+    "</font>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "kSGwC9r8537o"
+   },
+   "source": [
+    "![title](auxiliary/header_image.PNG)\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "# OST Tutorial III\n",
+    "## Process the latest Sentinel-1 GRD product for a given point\n",
+    "\n",
+    "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ESA-PhiLab/OST_Notebooks/blob/master/3%20-%20Latest%20Sentinel-1%20scene.ipynb)\n",
+    "\n",
+    "--------\n",
+    "\n",
+    "**Short description**\n",
+    "\n",
+    "This notebook demonstrates the interaction between the *Sentinel1* class for data inventory and download, and the *Sentinel1Scene* class, together, for the generation of the latest Sentinel-1 product over a given point coordinate. \n",
+    "\n",
+    "--------\n",
+    "\n",
+    "**Requirements**\n",
+    "\n",
+    "- a PC/Mac with at least 16GB of RAM\n",
+    "- about 4GB of free disk space\n",
+    "- a Copernicus Open Data Hub user account, ideally valid for at least 7 days (https://scihub.copernicus.eu)\n",
+    "--------\n",
+    "\n",
+    "**NOTE:** all cells that have an * after its number can be executed without changing any code. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "gLvPAMdI537p"
+   },
+   "source": [
+    "### 0\\* - Install OST and dependencies \n",
+    "\n",
+    "**NOTE:** Applies only if you haven't fully installed OST and its dependencies yet, e.g. on Google Colab, so uncomment the lines in this case."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "Z21YR74D537q"
+   },
+   "outputs": [],
+   "source": [
+    "# !apt-get -y install wget\n",
+    "# !wget https://raw.githubusercontent.com/ESA-PhiLab/OST_Notebooks/master/install_ost.sh\n",
+    "# !bash install_ost.sh"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "DpqUWHp0537t"
+   },
+   "source": [
+    "### 1* - Import python libraries necessary for processing"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "KRplYtUo537u"
+   },
+   "outputs": [],
+   "source": [
+    "# this is for the path handling and especially useful if you are on Windows\n",
+    "from pathlib import Path\n",
+    "from pprint import pprint\n",
+    "\n",
+    "# we will need this for our time of period definition\n",
+    "from datetime import datetime, timedelta\n",
+    "\n",
+    "# this is the s1Project class, that basically handles all the workflow from beginning to the end\n",
+    "from ost import Sentinel1, Sentinel1Scene"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "QP9IBFg7537x"
+   },
+   "source": [
+    "### 2 - Data selection parameters\n",
+    "\n",
+    "**NOTE:** In case you want to process a different area, all you want to change is the lat and lon values in line 6 \n",
+    "\n",
+    "\n",
+    "As already covered in OST Tutorial 2, we need a minimum of 3 basic parameters to initialise the *Sentinel1* class.\n",
+    "\n",
+    "**1 Area of Interest:** \n",
+    "\n",
+    "In this case we only search for a *specific spot on earth, i.e. Rome, Italy*, that is defined by the *Latitude* and *Longitude*. We then create a Well-Known-Text formatted string.\n",
+    "\n",
+    "**2 Time of Interest:**\n",
+    "\n",
+    "In this example, the datetime class is used to set the start date to 30 days before today to assure we get any scene within our time of interest.\n",
+    "\n",
+    "**3 Project directory**\n",
+    "\n",
+    "Set this to anything you like if not happy with the default one.\n",
+    "\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "cB40bzfX537x"
+   },
+   "outputs": [],
+   "source": [
+    "# ----------------------------\n",
+    "# Area of interest\n",
+    "# ----------------------------\n",
+    "\n",
+    "# Here we can either point to a shapefile or as well use\n",
+    "lat, lon = 41.8919, 12.5113\n",
+    "aoi = \"POINT ({} {})\".format(lon, lat)\n",
+    "\n",
+    "# ----------------------------\n",
+    "# Time of interest\n",
+    "# ----------------------------\n",
+    "# we set only the start date to today - 30 days\n",
+    "start = (datetime.today() - timedelta(days=30)).strftime(\"%Y-%m-%d\")\n",
+    "\n",
+    "# ----------------------------\n",
+    "# Processing directory\n",
+    "# ----------------------------\n",
+    "# get home folder\n",
+    "home = Path.home()\n",
+    "# create a processing directory within the home folder\n",
+    "project_dir = home / \"OST_Tutorials\" / \"Tutorial_3\"\n",
+    "\n",
+    "# ------------------------------\n",
+    "# Print out AOI and start date\n",
+    "# ------------------------------\n",
+    "print(\n",
+    "    \"AOI: \" + aoi,\n",
+    ")\n",
+    "print(\"TOI start: \" + start)\n",
+    "print(\"Our project directory is located at: \" + str(project_dir))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "JNToZ_1u5370"
+   },
+   "source": [
+    "### 3* - Initialize the Sentinel1 project class\n",
+    "\n",
+    "After initialisation of our class, where we explicitley add the GRD product type argument, we do a rough search over our AOI for the last 30 days. We print the first 5 entries and plot all images for visualization by using the *Sentinel1* class attribute *inventory* and method *plot_inventory*."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "yRey1rVI5371"
+   },
+   "outputs": [],
+   "source": [
+    "# ---------------------------------------------------\n",
+    "# for plotting purposes we use this iPython magic\n",
+    "%matplotlib inline\n",
+    "%pylab inline\n",
+    "pylab.rcParams[\"figure.figsize\"] = (19, 19)\n",
+    "# ---------------------------------------------------\n",
+    "\n",
+    "# create s1Project class instance\n",
+    "s1_project = Sentinel1(\n",
+    "    project_dir=project_dir, aoi=aoi, start=start, product_type=\"GRD\"\n",
+    ")\n",
+    "\n",
+    "# search command\n",
+    "s1_project.search()\n",
+    "# uncomment in case you have issues with the registration procedure\n",
+    "# ost_s1.search(base_url='https://scihub.copernicus.eu/dhus')\n",
+    "print(\"We found {} products.\".format(len(s1_project.inventory.identifier.unique())))\n",
+    "# combine OST class attribute with pandas head command to print out the first 5 rows of the\n",
+    "print(s1_project.inventory.head(5))\n",
+    "\n",
+    "# we plot the full Inventory on a map\n",
+    "s1_project.plot_inventory(transparency=0.1)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "2MWfH4ma5374"
+   },
+   "source": [
+    "### 4* - Select the latest scene found during the search\n",
+    "\n",
+    "Here we use some python-panda syntax on our rough data inventory to filter out the latest scene and create store it in a new dataframe. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "UiUl2GKY5375"
+   },
+   "outputs": [],
+   "source": [
+    "pylab.rcParams[\"figure.figsize\"] = (13, 13)\n",
+    "\n",
+    "# we give our inventory a shorter name iDf (for inventory Dataframe)\n",
+    "iDf = s1_project.inventory.copy()\n",
+    "\n",
+    "# we select the latest scene based on the metadata entry endposition\n",
+    "latest_df = iDf[iDf.endposition == iDf.endposition.max()]\n",
+    "\n",
+    "# we print out a little info on the date of the\n",
+    "print(\" INFO: Latest scene found for {}\".format(latest_df[\"acquisitiondate\"].values[0]))\n",
+    "\n",
+    "# we use the plotInventory method in combination with the newly\n",
+    "# created Geodataframe to see our scene footprint\n",
+    "s1_project.plot_inventory(latest_df, transparency=0.5)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "z0J2MNlq5378"
+   },
+   "source": [
+    "### 7* Download scene\n",
+    "\n",
+    "We use the build-in download method from the *Sentinel1* class. Note that you can pass any Geodataframe generated by OST, and filtered by you (e.g. sort out rows that you do not need). In our case we are only interested in the latest scene, so we pass the newly generated *latest_df* Geodataframe object.\n",
+    "\n",
+    "**NOTE** that you should use ESA's scihub server in this case, since it is the place where the images arrive first. Other data mirrors might have slight delays, so that the scene found by the inventory might not be available."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "EVCw8pHg5378"
+   },
+   "outputs": [],
+   "source": [
+    "s1_project.download(latest_df)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "1TI2VeaD538A"
+   },
+   "source": [
+    "### 8* - Display some metadata of the latest scene\n",
+    "\n",
+    "After use of the *Sentinel1* class for finding and downloading the latest scene, we hand the scene identifier over to the *Sentinel1Scene* class for further processing as already demonstrated in OST Tutorial 1."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "kqqZdzKC538A"
+   },
+   "outputs": [],
+   "source": [
+    "# create a S1Scene class instance based on the scene identifier coming from our \"latest scene dataframe\"\n",
+    "latest_scene = Sentinel1Scene(latest_df[\"identifier\"].values[0])\n",
+    "\n",
+    "# print summarising infos\n",
+    "latest_scene.info()\n",
+    "\n",
+    "# print file location\n",
+    "file_location = latest_scene.get_path(s1_project.download_dir)\n",
+    "\n",
+    "print(\" File is located at: \")\n",
+    "print(\" \" + str(file_location))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "MkKvHRSl538D"
+   },
+   "source": [
+    "### 9* - Produce a subsetted ARD product\n",
+    "\n",
+    "The creation of the ARD product follows the same logic as presented in OST Tutorial 1. However, for this case we introduce the subset argument to eh *create_ard* function.\n",
+    "Subsetting is adviced if only a small portion of the whole image is of interest. It will speed up processing and uses less storage.\n",
+    "\n",
+    "In our case we use some helper functions within the OST package to create a squared buffer area fo 10000 meter around our point of interest defined as AOI."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "W3Wyb_ny538E"
+   },
+   "outputs": [],
+   "source": [
+    "# 10 km buffer around AOI Point\n",
+    "from shapely.wkt import loads\n",
+    "from ost.helpers import vector as vec\n",
+    "\n",
+    "# turn WKT into shapely geometry object\n",
+    "shp_aoi = loads(s1_project.aoi)\n",
+    "\n",
+    "# use OST helper's function to create a quadrant buffer for subset\n",
+    "subset_area = vec.geodesic_point_buffer(shp_aoi.y, shp_aoi.x, 10000, envelope=True)\n",
+    "\n",
+    "print(\"-----------------------------------------------------------------------------\")\n",
+    "latest_scene.create_ard(\n",
+    "    # we see our download path can be automatically generated by providing the Project's download directory\n",
+    "    infile=latest_scene.get_path(download_dir=s1_project.download_dir),\n",
+    "    # let's simply take our processing folder\n",
+    "    out_dir=s1_project.processing_dir,\n",
+    "    # define the subset\n",
+    "    subset=subset_area,\n",
+    "    # in case already processed, we will re-process\n",
+    "    overwrite=True,\n",
+    ")\n",
+    "\n",
+    "print(\"-----------------------------------------------------------------------------\")\n",
+    "print(\" The path to our newly created ARD product can be obtained the following way:\")\n",
+    "latest_scene.ard_dimap"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "colab_type": "text",
+    "id": "m4N-5dBu538G"
+   },
+   "source": [
+    "### 10* - Create a RGB color composite\n",
+    "\n",
+    "As already demonstrated in OST Tutorial 1, we create an RGB GeoTiff, and visualize it."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "colab": {},
+    "colab_type": "code",
+    "id": "icq5Nf34538H"
+   },
+   "outputs": [],
+   "source": [
+    "latest_scene.create_rgb(\n",
+    "    outfile=s1_project.processing_dir / f\"{latest_scene.start_date}.tif\"\n",
+    ")\n",
+    "latest_scene.visualise_rgb(shrink_factor=1)"
+   ]
+  }
+ ],
+ "metadata": {
+  "colab": {
+   "collapsed_sections": [],
+   "name": "3 - Latest Sentinel-1 scene.ipynb",
+   "provenance": []
+  },
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.5"
+  },
+  "toc": {
+   "base_numbering": 1,
+   "nav_menu": {},
+   "number_sections": false,
+   "sideBar": true,
+   "skip_h1_title": true,
+   "title_cell": "Table of Contents",
+   "title_sidebar": "Contents",
+   "toc_cell": false,
+   "toc_position": {},
+   "toc_section_display": true,
+   "toc_window_display": true
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/tests/conftest.py b/tests/conftest.py
index 3c5b0da1..417cfd24 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,174 +1,151 @@
-import pytest
-import shutil
-from pathlib import Path
-from shapely.geometry import box
-
-from ost.Project import Sentinel1Batch
-from ost.s1.s1scene import Sentinel1Scene
-from ost.helpers.settings import HERBERT_USER
-
-SCRIPT_DIR = Path(__file__).resolve().parent
-TESTDATA_DIR = SCRIPT_DIR.joinpath('testdata')
-TEMP_SLC_DIR = TESTDATA_DIR.joinpath('tmp_slc')
-TEMP_GRD_DIR = TESTDATA_DIR.joinpath('tmp_grd')
-CACHE_DIR = TESTDATA_DIR.joinpath('cache')
-
-
-@pytest.fixture(scope='session')
-def some_bounds_grd():
-    return [9.404296875, 54.84375, 9.4921875, 54.931640625]
-
-
-@pytest.fixture(scope='session')
-def some_bounds_slc():
-    return [8.02001953125, 46.34033203125, 8.0419921875, 46.3623046875]
-
-
-@pytest.fixture
-def s1_id():
-    return (
-        'S1A_IW_GRDH_1SDV_20141003T040550_20141003T040619_002660_002F64_EC04'
-    )
-
-
-@pytest.fixture(scope='session')
-def s1_grd_notnr():
-    return CACHE_DIR.joinpath(
-        'S1B_IW_GRDH_1SDV_20180813T054020_20180813T054045_012240_0168D6_B775'
-        '.zip'
-    )
-
-
-@pytest.fixture(scope='session')
-def s1_grd_notnr_ost_product(s1_grd_notnr):
-    scene_id = s1_grd_notnr.stem
-    return scene_id, Sentinel1Scene(scene_id)
-
-
-@pytest.fixture(scope='session')
-def s1_slc_master():
-    return CACHE_DIR.joinpath(
-        'S1A_IW_SLC__1SDV_20190101T171515_20190101T171542_'
-        '025287_02CC09_0A0B.zip'
-    )
-
-
-@pytest.fixture
-def s1_slc_slave():
-    return CACHE_DIR.joinpath(
-        'S1A_IW_SLC__1SDV_20190113T171514_20190113T171541_025462_02D252_C063'
-        '.zip'
-    )
-
-
-@pytest.fixture(scope='session')
-def s1_slc_ost_master(s1_slc_master):
-    scene_id = s1_slc_master.stem
-    return scene_id, Sentinel1Scene(scene_id)
-
-
-@pytest.fixture
-def s1_slc_ost_slave(s1_slc_slave):
-    scene_id = s1_slc_slave.stem
-    return scene_id, Sentinel1Scene(scene_id)
-
-
-@pytest.fixture(scope='session')
-def slc_project_class(some_bounds_slc, s1_slc_master, s1_slc_ost_master):
-    start = '2019-01-01'
-    end = '2019-01-02'
-    scene_id, product = s1_slc_ost_master
-    TEMP_SLC_DIR.mkdir(parents=True, exist_ok=True)
-    aoi = box(
-        some_bounds_slc[0], some_bounds_slc[1], some_bounds_slc[2],
-        some_bounds_slc[3]
-    ).wkt
-
-    try:
-        s1_batch = Sentinel1Batch(
-            project_dir=TEMP_SLC_DIR,
-            aoi=aoi,
-            start=start,
-            end=end,
-            product_type='SLC',
-            ard_type='OST-RTC',
-        )
-
-        download_path = s1_batch.download_dir.joinpath(
-            'SAR',
-            product.product_type,
-            product.year,
-            product.month,
-            product.day
-        )
-
-        product.download_path(download_dir=download_path)
-        download_path.mkdir(parents=True, exist_ok=True)
-        shutil.copy(s1_slc_master, download_path)
-        shutil.move(
-            download_path.joinpath(f'{scene_id}.zip'),
-            download_path.joinpath(f'{scene_id}.downloaded')
-        )
-        shutil.copy(s1_slc_master, download_path)
-        product.get_path(download_dir=s1_batch.download_dir)
-
-        s1_batch.scihub_uname = HERBERT_USER['uname']
-        s1_batch.scihub_pword = HERBERT_USER['pword']
-        s1_batch.search()
-
-        s1_batch.refine_inventory()
-        s1_batch.create_burst_inventory()
-
-        yield s1_batch
-    finally:
-        shutil.rmtree(TEMP_SLC_DIR)
-
-
-@pytest.fixture(scope='session')
-def grd_project_class(some_bounds_grd, s1_grd_notnr, s1_grd_notnr_ost_product):
-
-    start = '2018-08-13'
-    end = '2018-08-13'
-
-    scene_id, product = s1_grd_notnr_ost_product
-    TEMP_GRD_DIR.mkdir(parents=True, exist_ok=True)
-
-    aoi = box(
-        some_bounds_grd[0], some_bounds_grd[1], some_bounds_grd[2],
-        some_bounds_grd[3]
-    ).wkt
-
-    try:
-        s1_batch = Sentinel1Batch(
-            project_dir=TEMP_GRD_DIR,
-            aoi=aoi,
-            start=start,
-            end=end,
-            product_type='GRD',
-            ard_type='OST-RTC'
-        )
-        download_path = s1_batch.download_dir.joinpath(
-            'SAR',
-            product.product_type,
-            product.year,
-            product.month,
-            product.day
-        )
-        product.download_path(download_dir=download_path)
-        download_path.mkdir(parents=True, exist_ok=True)
-        shutil.copy(s1_grd_notnr, download_path)
-        shutil.move(
-            download_path.joinpath(f'{scene_id}.zip'),
-            download_path.joinpath(f'{scene_id}.downloaded')
-        )
-        shutil.copy(s1_grd_notnr, download_path)
-        product.get_path(download_dir=s1_batch.download_dir)
-
-        s1_batch.scihub_uname = HERBERT_USER['uname']
-        s1_batch.scihub_pword = HERBERT_USER['pword']
-        s1_batch.search()
-
-        s1_batch.refine_inventory()
-        yield s1_batch
-    finally:
-        shutil.rmtree(TEMP_GRD_DIR)
+# import pytest
+# import shutil
+# from pathlib import Path
+# from shapely.geometry import box
+
+# from ost.Project import Sentinel1Batch
+# from ost.s1.s1scene import Sentinel1Scene
+# from ost.helpers.settings import HERBERT_USER
+
+# SCRIPT_DIR = Path(__file__).resolve().parent
+# TESTDATA_DIR = SCRIPT_DIR/"testdata"
+# TEMP_SLC_DIR = TESTDATA_DIR/"tmp_slc"
+# TEMP_GRD_DIR = TESTDATA_DIR/"tmp_grd"
+# CACHE_DIR = TESTDATA_DIR/"cache"
+
+
+# @pytest.fixture(scope="session")
+# def some_bounds_grd():
+#     return [9.404296875, 54.84375, 9.4921875, 54.931640625]
+
+
+# @pytest.fixture(scope="session")
+# def some_bounds_slc():
+#     return [8.02001953125, 46.34033203125, 8.0419921875, 46.3623046875]
+
+
+# @pytest.fixture
+# def s1_id():
+#     return "S1A_IW_GRDH_1SDV_20141003T040550_20141003T040619_002660_002F64_EC04"
+
+
+# @pytest.fixture(scope="session")
+# def s1_grd_notnr():
+#     return CACHE_DIR/"S1B_IW_GRDH_1SDV_20180813T054020_20180813T054045_012240_0168D6_B775.zip"
+
+
+# @pytest.fixture(scope="session")
+# def s1_grd_notnr_ost_product(s1_grd_notnr):
+#     scene_id = s1_grd_notnr.stem
+#     return scene_id, Sentinel1Scene(scene_id)
+
+
+# @pytest.fixture(scope="session")
+# def s1_slc_master():
+#     return CACHE_DIR/"S1A_IW_SLC__1SDV_20190101T171515_20190101T171542_025287_02CC09_0A0B.zip"
+
+
+# @pytest.fixture
+# def s1_slc_slave():
+#     return CACHE_DIR/"S1A_IW_SLC__1SDV_20190113T171514_20190113T171541_025462_02D252_C063.zip"
+
+
+# @pytest.fixture(scope="session")
+# def s1_slc_ost_master(s1_slc_master):
+#     scene_id = s1_slc_master.stem
+#     return scene_id, Sentinel1Scene(scene_id)
+
+
+# @pytest.fixture
+# def s1_slc_ost_slave(s1_slc_slave):
+#     scene_id = s1_slc_slave.stem
+#     return scene_id, Sentinel1Scene(scene_id)
+
+
+# @pytest.fixture(scope="session")
+# def slc_project_class(some_bounds_slc, s1_slc_master, s1_slc_ost_master):
+#     start = "2019-01-01"
+#     end = "2019-01-02"
+#     scene_id, product = s1_slc_ost_master
+#     TEMP_SLC_DIR.mkdir(parents=True, exist_ok=True)
+#     aoi = box(
+#         some_bounds_slc[0], some_bounds_slc[1], some_bounds_slc[2], some_bounds_slc[3]
+#     ).wkt
+
+#     try:
+#         s1_batch = Sentinel1Batch(
+#             project_dir=TEMP_SLC_DIR,
+#             aoi=aoi,
+#             start=start,
+#             end=end,
+#             product_type="SLC",
+#             ard_type="OST-RTC",
+#         )
+
+#         download_path = s1_batch.download_dir/"SAR"/product.product_type/product.year/product.month/product.day
+#         )
+
+#         product.download_path(download_dir=download_path)
+#         download_path.mkdir(parents=True, exist_ok=True)
+#         shutil.copy(s1_slc_master, download_path)
+#         shutil.move(
+#             download_path/f"{scene_id}.zip",
+#             download_path/f"{scene_id}.downloaded",
+#         )
+#         shutil.copy(s1_slc_master, download_path)
+#         product.get_path(download_dir=s1_batch.download_dir)
+
+#         s1_batch.scihub_uname = HERBERT_USER["uname"]
+#         s1_batch.scihub_pword = HERBERT_USER["pword"]
+#         s1_batch.search()
+
+#         s1_batch.refine_inventory()
+#         s1_batch.create_burst_inventory()
+
+#         yield s1_batch
+#     finally:
+#         shutil.rmtree(TEMP_SLC_DIR)
+
+
+# @pytest.fixture(scope="session")
+# def grd_project_class(some_bounds_grd, s1_grd_notnr, s1_grd_notnr_ost_product):
+
+#     start = "2018-08-13"
+#     end = "2018-08-13"
+
+#     scene_id, product = s1_grd_notnr_ost_product
+#     TEMP_GRD_DIR.mkdir(parents=True, exist_ok=True)
+
+#     aoi = box(
+#         some_bounds_grd[0], some_bounds_grd[1], some_bounds_grd[2], some_bounds_grd[3]
+#     ).wkt
+
+#     try:
+#         s1_batch = Sentinel1Batch(
+#             project_dir=TEMP_GRD_DIR,
+#             aoi=aoi,
+#             start=start,
+#             end=end,
+#             product_type="GRD",
+#             ard_type="OST-RTC",
+#         )
+#         download_path = s1_batch.download_dir/"SAR"/product.product_type/product.year/product.month/product.day
+#         )
+#         product.download_path(download_dir=download_path)
+#         download_path.mkdir(parents=True, exist_ok=True)
+#         shutil.copy(s1_grd_notnr, download_path)
+#         shutil.move(
+#             download_path/f"{scene_id}.zip",
+#             download_path/f"{scene_id}.downloaded",
+#         )
+#         shutil.copy(s1_grd_notnr, download_path)
+#         product.get_path(download_dir=s1_batch.download_dir)
+
+#         s1_batch.scihub_uname = HERBERT_USER["uname"]
+#         s1_batch.scihub_pword = HERBERT_USER["pword"]
+#         s1_batch.search()
+
+#         s1_batch.refine_inventory()
+#         yield s1_batch
+#     finally:
+#         shutil.rmtree(TEMP_GRD_DIR)
diff --git a/tests/test_batch.py b/tests/test_batch.py
index d3171dca..e3c934f5 100644
--- a/tests/test_batch.py
+++ b/tests/test_batch.py
@@ -1,50 +1,63 @@
+import pytest
+
 from ost.helpers.settings import config_check
 
 
+@pytest.mark.skip(reason="not running in pip build")
 def test_update_ard_param(slc_project_class):
 
-    slc_project_class.update_ard_parameters(ard_type='OST-GTC')
-    assert slc_project_class.config_dict['processing']['single_ARD']['ard_type'] == 'OST-GTC'
+    slc_project_class.update_ard_parameters(ard_type="OST-GTC")
+    assert (
+        slc_project_class.config_dict["processing"]["single_ARD"]["ard_type"]
+        == "OST-GTC"
+    )
 
 
 # Test GRDs to ARD kind of batch
+@pytest.mark.skip(reason="not running in pip build")
 def test_grds_to_ards(grd_project_class):
-    for ard_type in config_check['ard_type']['choices']:
+    for ard_type in config_check["ard_type"]["choices"]:
         grd_project_class.update_ard_parameters(ard_type)
         grd_project_class.grds_to_ards(
-            inventory_df=grd_project_class.refined_inventory_dict['DESCENDING_VVVH'],
+            inventory_df=grd_project_class.refined_inventory_dict["DESCENDING_VVVH"],
             timeseries=False,
             timescan=False,
             mosaic=False,
-            overwrite=True
+            overwrite=True,
         )
 
+
 # TODO: Runs endlessly on travis for whatever reason
 # # Test burst batch for all slc ARD types,
 # split into more so that TRAVIS, wont get crazy
 # # [OST-GTC, OST-RTC, OST-minimal]
 
-# def test_burst_batch_ost_gtc(slc_project_class):
-#     slc_project_class.ard_parameters["single_ARD"]["type"] = 'OST-GTC'
-#     slc_project_class.update_ard_parameters()
-#     slc_project_class.ard_parameters['single_ARD']['resolution'] = 50
-#     slc_project_class.bursts_to_ard(
-#         timeseries=False,
-#         timescan=False,
-#         mosaic=False,
-#         overwrite=True,
-#         cut_to_aoi=False,
-#         ncores=2
-#     )
-# def test_burst_batch_ost_rtc(slc_project_class):
-#     slc_project_class.ard_parameters["single_ARD"]["type"] = 'OST-RTC'
-#     slc_project_class.update_ard_parameters()
-#     slc_project_class.ard_parameters['single_ARD']['resolution'] = 50
-#     slc_project_class.bursts_to_ard(
-#         timeseries=False,
-#         timescan=False,
-#         mosaic=False,
-#         overwrite=True,
-#         cut_to_aoi=False,
-#         ncores=2
-#     )
+
+@pytest.mark.skip(reason="not running in pip build")
+def test_burst_batch_ost_gtc(slc_project_class):
+    slc_project_class.ard_parameters["single_ARD"]["type"] = "OST-GTC"
+    slc_project_class.update_ard_parameters()
+    slc_project_class.ard_parameters["single_ARD"]["resolution"] = 50
+    slc_project_class.bursts_to_ard(
+        timeseries=False,
+        timescan=False,
+        mosaic=False,
+        overwrite=True,
+        cut_to_aoi=False,
+        ncores=2,
+    )
+
+
+@pytest.mark.skip(reason="not running in pip build")
+def test_burst_batch_ost_rtc(slc_project_class):
+    slc_project_class.ard_parameters["single_ARD"]["type"] = "OST-RTC"
+    slc_project_class.update_ard_parameters()
+    slc_project_class.ard_parameters["single_ARD"]["resolution"] = 50
+    slc_project_class.bursts_to_ard(
+        timeseries=False,
+        timescan=False,
+        mosaic=False,
+        overwrite=True,
+        cut_to_aoi=False,
+        ncores=2,
+    )
diff --git a/tests/test_common_wrappers.py b/tests/test_common_wrappers.py
index 30800b41..5fa5f9f5 100644
--- a/tests/test_common_wrappers.py
+++ b/tests/test_common_wrappers.py
@@ -1,5 +1,6 @@
 import json
 import logging
+import pytest
 
 from multiprocessing import cpu_count
 
@@ -10,109 +11,99 @@
 logger = logging.getLogger(__name__)
 
 # load standard config parameters
-config_file = OST_ROOT.joinpath('graphs', 'ard_json', 'grd.ost_gtc.json')
-with open(config_file, 'r') as file:
+config_file = OST_ROOT / "graphs" / "ard_json" / "grd.ost_gtc.json"
+with open(config_file, "r") as file:
     CONFIG_DICT = json.load(file)
-    CONFIG_DICT['snap_cpu_parallelism'] = cpu_count()
-    CONFIG_DICT['max_workers'] = 1
-    CONFIG_DICT['executor_type'] = 'billiard'
+    CONFIG_DICT["snap_cpu_parallelism"] = cpu_count()
+    CONFIG_DICT["max_workers"] = 1
+    CONFIG_DICT["executor_type"] = "billiard"
 
-def test_grd_import_subset(
-        s1_grd_notnr, s1_grd_notnr_ost_product, grd_project_class
-):
+
+@pytest.mark.skip(reason="not running in pip build")
+def test_grd_import_subset(s1_grd_notnr, s1_grd_notnr_ost_product, grd_project_class):
 
     # set subset
-    CONFIG_DICT['aoi'] = grd_project_class.aoi
-    CONFIG_DICT['subset'] = True
+    CONFIG_DICT["aoi"] = grd_project_class.aoi
+    CONFIG_DICT["subset"] = True
 
     scene_id, product = s1_grd_notnr_ost_product
     return_code = gw.grd_frame_import(
         infile=s1_grd_notnr,
-        outfile=grd_project_class.processing_dir.joinpath(
-            f'{scene_id}_import'
-        ),
+        outfile=grd_project_class.processing_dir / f"{scene_id}_import",
         logfile=logger,
-        config_dict=CONFIG_DICT
+        config_dict=CONFIG_DICT,
     )
     assert return_code == str(
-        grd_project_class.processing_dir.joinpath(f'{scene_id}_import.dim')
+        grd_project_class.processing_dir / f"{scene_id}_import.dim"
     )
 
 
+@pytest.mark.skip(reason="not running in pip build")
 def test_grd_remove_border(s1_grd_notnr_ost_product, grd_project_class):
 
     scene_id, product = s1_grd_notnr_ost_product
-    for polarisation in ['VV', 'VH', 'HH', 'HV']:
+    for polarisation in ["VV", "VH", "HH", "HV"]:
         infile = list(
-            grd_project_class.processing_dir.joinpath(
-                f'{scene_id}_imported*data'
-            ).glob(f'Intensity_{polarisation}.img')
+            (grd_project_class.processing_dir / f"{scene_id}_imported*data").glob(
+                f"Intensity_{polarisation}.img"
+            )
         )
 
         if len(infile) == 1:
             # run grd Border Remove
-            logger.debug(f'Remove border noise for {polarisation} band.')
+            logger.debug(f"Remove border noise for {polarisation} band.")
             gw.grd_remove_border(infile[0])
 
 
+@pytest.mark.skip(reason="not running in pip build")
 def test_grd_calibration(s1_grd_notnr_ost_product, grd_project_class):
 
     scene_id, product = s1_grd_notnr_ost_product
-    product_types = ['GTC-gamma0', 'GTC-sigma0', 'RTC-gamma0']
+    product_types = ["GTC-gamma0", "GTC-sigma0", "RTC-gamma0"]
 
     for product_type in product_types:
 
         # set product type
-        CONFIG_DICT['processing']['single_ARD']['product_type'] = product_type
+        CONFIG_DICT["processing"]["single_ARD"]["product_type"] = product_type
 
         # run command
         return_code = gw.calibration(
-            infile=grd_project_class.processing_dir.joinpath(
-                f'{scene_id}_import.dim'
-            ),
-            outfile=grd_project_class.processing_dir.joinpath(
-                f'{scene_id}_BS'
-            ),
+            infile=grd_project_class.processing_dir / f"{scene_id}_import.dim",
+            outfile=grd_project_class.processing_dir / f"{scene_id}_BS",
             logfile=logger,
-            config_dict=CONFIG_DICT
+            config_dict=CONFIG_DICT,
         )
         assert return_code == str(
-            grd_project_class.processing_dir.joinpath(f'{scene_id}_BS.dim')
+            grd_project_class.processing_dir / f"{scene_id}_BS.dim"
         )
 
 
+@pytest.mark.skip(reason="not running in pip build")
 def test_grd_speckle_filter(s1_grd_notnr_ost_product, grd_project_class):
 
-    CONFIG_DICT['processing']['single_ARD']['remove_speckle'] = True
+    CONFIG_DICT["processing"]["single_ARD"]["remove_speckle"] = True
     scene_id, product = s1_grd_notnr_ost_product
     return_code = cw.speckle_filter(
-        infile=grd_project_class.processing_dir.joinpath(
-            f'{scene_id}_BS.dim'
-        ),
-        outfile=grd_project_class.processing_dir.joinpath(
-            f'{scene_id}_BS_Spk'
-        ),
+        infile=grd_project_class.processing_dir / f"{scene_id}_BS.dim",
+        outfile=grd_project_class.processing_dir / f"{scene_id}_BS_Spk",
         logfile=logger,
-        config_dict=CONFIG_DICT
+        config_dict=CONFIG_DICT,
     )
     assert return_code == str(
-        grd_project_class.processing_dir.joinpath(f'{scene_id}_BS_Spk.dim')
+        grd_project_class.processing_dir / f"{scene_id}_BS_Spk.dim"
     )
 
 
+@pytest.mark.skip(reason="not running in pip build")
 def test_grd_tc(s1_grd_notnr_ost_product, grd_project_class):
 
     scene_id, product = s1_grd_notnr_ost_product
     return_code = cw.terrain_correction(
-        infile=grd_project_class.processing_dir.joinpath(
-            f'{scene_id}_BS_Spk.dim'
-        ),
-        outfile=grd_project_class.processing_dir.joinpath(
-            f'{scene_id}_BS_Spk_TC'
-        ),
+        infile=grd_project_class.processing_dir / f"{scene_id}_BS_Spk.dim",
+        outfile=grd_project_class.processing_dir / f"{scene_id}_BS_Spk_TC",
         logfile=logger,
-        config_dict=CONFIG_DICT
+        config_dict=CONFIG_DICT,
     )
     assert return_code == str(
-        grd_project_class.processing_dir.joinpath(f'{scene_id}_BS_Spk_TC.dim')
+        grd_project_class.processing_dir / f"{scene_id}_BS_Spk_TC.dim"
     )
diff --git a/tests/test_gpt.py b/tests/test_gpt.py
index 5fb46f13..82eacd87 100644
--- a/tests/test_gpt.py
+++ b/tests/test_gpt.py
@@ -1,6 +1,9 @@
+import pytest
+
 from ost.helpers.helpers import run_command
 from ost.helpers.settings import GPT_FILE
 
 
+@pytest.mark.skip(reason="not running in pip build")
 def test_gpt():
     assert run_command(GPT_FILE, logfile=None) == 0
diff --git a/tests/test_s1_download.py b/tests/test_s1_download.py
index 6114f686..2a3229a0 100644
--- a/tests/test_s1_download.py
+++ b/tests/test_s1_download.py
@@ -1,91 +1,78 @@
-import os
 import pytest
+import os
+
 import pandas as pd
 from tempfile import TemporaryDirectory
 
-from ost.helpers.helpers import check_zipfile
 from ost.helpers.asf import check_connection as check_connection_asf
-from ost.helpers.scihub import check_connection as check_connection_scihub, \
-    connect
-
-from ost.s1.download import download_sentinel1
-
+from ost.helpers.scihub import check_connection as check_connection_scihub, connect
 from ost.helpers.settings import HERBERT_USER
+from ost.s1.download import download_sentinel1
+from ost.helpers.helpers import check_zipfile
 
 
+@pytest.mark.skip(reason="not running in pip build")
 def test_asf_connection():
-    herbert_uname = HERBERT_USER['uname']
-    herbert_password = HERBERT_USER['asf_pword']
-    response_code = check_connection_asf(
-        uname=herbert_uname,
-        pword=herbert_password
-    )
+    herbert_uname = HERBERT_USER["uname"]
+    herbert_password = HERBERT_USER["asf_pword"]
+    response_code = check_connection_asf(uname=herbert_uname, pword=herbert_password)
     control_code = 200
     assert response_code == control_code
 
 
+@pytest.mark.skip(reason="not running in pip build")
 def test_esa_scihub_connection(s1_grd_notnr_ost_product):
-    herbert_uname = HERBERT_USER['uname']
-    herbert_password = HERBERT_USER['pword']
-    response_code = check_connection_scihub(
-        uname=herbert_uname,
-        pword=herbert_password
-    )
+    herbert_uname = HERBERT_USER["uname"]
+    herbert_password = HERBERT_USER["pword"]
+    response_code = check_connection_scihub(uname=herbert_uname, pword=herbert_password)
     control_code = 200
     assert response_code == control_code
     opener = connect(
-        base_url='https://apihub.copernicus.eu/apihub/',
+        base_url="https://apihub.copernicus.eu/apihub/",
         uname=herbert_uname,
-        pword=herbert_password
+        pword=herbert_password,
     )
-    control_uuid = '1b43fb7d-bd2c-41cd-86a1-3442b1fbd5bb'
+    control_uuid = "1b43fb7d-bd2c-41cd-86a1-3442b1fbd5bb"
     uuid = s1_grd_notnr_ost_product[1].scihub_uuid(opener)
     assert uuid == control_uuid
 
 
-# @pytest.mark.skipif("TRAVIS" in os.environ and os.environ["TRAVIS"] == "true",
-#                     reason="Skipping this test on Travis CI."
-#                     )
-# def test_esa_scihub_download(s1_grd_notnr_ost_product, mirror=1):
-#     herbert_uname = HERBERT_USER['uname']
-#     herbert_password = HERBERT_USER['pword']
-#     df = pd.DataFrame({'identifier': [s1_grd_notnr_ost_product[1].scene_id]})
-#     with TemporaryDirectory(dir=os.getcwd()) as temp:
-#         download_sentinel1(
-#             inventory_df=df,
-#             download_dir=temp,
-#             mirror=mirror,
-#             concurrent=1,
-#             uname=herbert_uname,
-#             pword=herbert_password
-#         )
-#
-#         product_path = s1_grd_notnr_ost_product[1].get_path(
-#             download_dir=temp
-#         )
-#         return_code = check_zipfile(product_path)
-#         assert return_code is None
-#
-#
-# @pytest.mark.skipif("TRAVIS" in os.environ and os.environ["TRAVIS"] == "true",
-#                     reason="Skipping this test on Travis CI."
-#                     )
-# def test_asf_download(s1_grd_notnr_ost_product, mirror=2):
-#     herbert_uname = HERBERT_USER['uname']
-#     herbert_password = HERBERT_USER['asf_pword']
-#     df = pd.DataFrame({'identifier': [s1_grd_notnr_ost_product[1].scene_id]})
-#     with TemporaryDirectory(dir=os.getcwd()) as temp:
-#         download_sentinel1(
-#             inventory_df=df,
-#             download_dir=temp,
-#             mirror=mirror,
-#             concurrent=1,
-#             uname=herbert_uname,
-#             pword=herbert_password
-#         )
-#         from ost.helpers.helpers import check_zipfile
-#         product_path = s1_grd_notnr_ost_product[1].get_path(
-#             download_dir=temp
-#         )
-#         return_code = check_zipfile(product_path)
-#         assert return_code is None
+@pytest.mark.skip(reason="not running in pip build")
+def test_esa_scihub_download(s1_grd_notnr_ost_product, mirror=1):
+    herbert_uname = HERBERT_USER["uname"]
+    herbert_password = HERBERT_USER["pword"]
+    df = pd.DataFrame({"identifier": [s1_grd_notnr_ost_product[1].scene_id]})
+    with TemporaryDirectory(dir=os.getcwd()) as temp:
+        download_sentinel1(
+            inventory_df=df,
+            download_dir=temp,
+            mirror=mirror,
+            concurrent=1,
+            uname=herbert_uname,
+            pword=herbert_password,
+        )
+
+        product_path = s1_grd_notnr_ost_product[1].get_path(download_dir=temp)
+        return_code = check_zipfile(product_path)
+        assert return_code is None
+
+
+@pytest.mark.skip(reason="not running in pip build")
+def test_asf_download(s1_grd_notnr_ost_product, mirror=2):
+    herbert_uname = HERBERT_USER["uname"]
+    herbert_password = HERBERT_USER["asf_pword"]
+    df = pd.DataFrame({"identifier": [s1_grd_notnr_ost_product[1].scene_id]})
+    with TemporaryDirectory(dir=os.getcwd()) as temp:
+        download_sentinel1(
+            inventory_df=df,
+            download_dir=temp,
+            mirror=mirror,
+            concurrent=1,
+            uname=herbert_uname,
+            pword=herbert_password,
+        )
+        from ost.helpers.helpers import check_zipfile
+
+        product_path = s1_grd_notnr_ost_product[1].get_path(download_dir=temp)
+        return_code = check_zipfile(product_path)
+        assert return_code is None
diff --git a/tests/test_s1scene.py b/tests/test_s1scene.py
index d180b7f6..9bfb9153 100644
--- a/tests/test_s1scene.py
+++ b/tests/test_s1scene.py
@@ -1,22 +1,26 @@
+import pytest
+
 from ost import Sentinel1Scene
 
 
+@pytest.mark.skip(reason="not running in pip build")
 def test_s1scene_metadata(s1_id):
     s1 = Sentinel1Scene(s1_id)
-    control_id = 'S1A_IW_GRDH_1SDV_20141003T040550_' \
-                 '20141003T040619_002660_002F64_EC04'
-    control_dict = {'Scene_Identifier':
-                        'S1A_IW_GRDH_1SDV_20141003T040550_20141003T040619_'
-                        '002660_002F64_EC04',
-                    'Satellite': 'Sentinel-1A',
-                    'Acquisition_Mode': 'Interferometric Wide Swath',
-                    'Processing_Level': '1',
-                    'Product_Type': 'Ground Range Detected (GRD)',
-                    'Acquisition_Date': '20141003',
-                    'Start_Time': '040550',
-                    'Stop_Time': '040619',
-                    'Absolute_Orbit': '002660',
-                    'Relative_Orbit': '138'
-                    }
+    control_id = (
+        "S1A_IW_GRDH_1SDV_20141003T040550_" "20141003T040619_002660_002F64_EC04"
+    )
+    control_dict = {
+        "Scene_Identifier": "S1A_IW_GRDH_1SDV_20141003T040550_20141003T040619_"
+        "002660_002F64_EC04",
+        "Satellite": "Sentinel-1A",
+        "Acquisition_Mode": "Interferometric Wide Swath",
+        "Processing_Level": "1",
+        "Product_Type": "Ground Range Detected (GRD)",
+        "Acquisition_Date": "20141003",
+        "Start_Time": "040550",
+        "Stop_Time": "040619",
+        "Absolute_Orbit": "002660",
+        "Relative_Orbit": "138",
+    }
     assert control_dict == s1.info_dict()
     assert s1.scene_id == control_id
diff --git a/tests/test_search.py b/tests/test_search.py
index 52c7578b..9180fe6c 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -3,53 +3,51 @@
 import urllib.parse
 import geopandas as gpd
 from tempfile import TemporaryDirectory
+import pytest
 
 from ost.s1.search import scihub_catalogue
-from ost.helpers.scihub import create_aoi_str, create_toi_str, \
-    create_s1_product_specs
+from ost.helpers.scihub import create_aoi_str, create_toi_str, create_s1_product_specs
 from ost.helpers.settings import HERBERT_USER
 
 
+@pytest.mark.skip(reason="not running in pip build")
 def test_default_scihub_catalogue():
 
-    aoi = 'POLYGON ((16.875 45, 16.875 50.625, 11.25 50.625,' \
-          ' 11.25 45, 16.875 45))'
+    aoi = "POLYGON ((16.875 45, 16.875 50.625, 11.25 50.625," " 11.25 45, 16.875 45))"
 
     with TemporaryDirectory(dir=os.getcwd()) as temp:
         args_dict = {}
         args_dict.update(
             aoi=aoi,
-            beammode='IW',
-            begindate='2020-01-01',
-            enddate='2020-01-04',
-            output=os.path.join(temp, 'test_cat.gpkg'),
-            password=HERBERT_USER['pword'],
-            polarisation='VV,VH',
-            producttype='GRD',
-            username=HERBERT_USER['uname']
+            beammode="IW",
+            begindate="2020-01-01",
+            enddate="2020-01-04",
+            output=os.path.join(temp, "test_cat.gpkg"),
+            password=HERBERT_USER["pword"],
+            polarisation="VV,VH",
+            producttype="GRD",
+            username=HERBERT_USER["uname"],
         )
-        aoi = create_aoi_str(args_dict['aoi'])
-        toi = create_toi_str(args_dict['begindate'], args_dict['enddate'])
+        aoi = create_aoi_str(args_dict["aoi"])
+        toi = create_toi_str(args_dict["begindate"], args_dict["enddate"])
         product_specs = create_s1_product_specs(
-            args_dict['producttype'],
-            args_dict['polarisation'],
-            args_dict['beammode']
+            args_dict["producttype"], args_dict["polarisation"], args_dict["beammode"]
         )
 
         # construct the final query
         query = urllib.parse.quote(
-            f'Sentinel-1 AND {product_specs} AND {aoi} AND {toi}'
+            f"Sentinel-1 AND {product_specs} AND {aoi} AND {toi}"
         )
 
         scihub_catalogue(
             query,
-            output=args_dict['output'],
+            output=args_dict["output"],
             append=False,
-            uname=args_dict['username'],
-            pword=args_dict['password']
+            uname=args_dict["username"],
+            pword=args_dict["password"],
         )
         control_fields = 21
         control_products = 34
-        shp = gpd.read_file(args_dict['output'])
+        shp = gpd.read_file(args_dict["output"])
         assert len(shp.columns) == control_fields
         assert len(shp.id) == control_products
diff --git a/tests/test_slc_wrappers.py b/tests/test_slc_wrappers.py
index 9351cd9e..81f9e7fc 100644
--- a/tests/test_slc_wrappers.py
+++ b/tests/test_slc_wrappers.py
@@ -10,67 +10,59 @@
 logger = logging.getLogger(__name__)
 
 # load standard config parameters
-config_file = OST_ROOT.joinpath('graphs', 'ard_json', 'slc.ost_gtc.json')
-with open(config_file, 'r') as file:
+config_file = OST_ROOT / "graphs" / "ard_json" / "slc.ost_gtc.json"
+with open(config_file, "r") as file:
     CONFIG_DICT = json.load(file)
-    CONFIG_DICT['subset'] = False
-    CONFIG_DICT['snap_cpu_parallelism'] = cpu_count()
-    CONFIG_DICT['max_workers'] = 1
-    CONFIG_DICT['executor_type'] = 'billiard'
+    CONFIG_DICT["subset"] = False
+    CONFIG_DICT["snap_cpu_parallelism"] = cpu_count()
+    CONFIG_DICT["max_workers"] = 1
+    CONFIG_DICT["executor_type"] = "billiard"
 
 
+@pytest.mark.skip(reason="not running in pip build")
 def test_burst_import(s1_slc_master, s1_slc_ost_master, slc_project_class):
 
     scene_id, master = s1_slc_ost_master
     for idx, burst in slc_project_class.burst_inventory.iterrows():
-        if idx > 2 or burst.SwathID != 'IW1':
+        if idx > 2 or burst.SwathID != "IW1":
             continue
         return_code = sw.burst_import(
             infile=s1_slc_master,
-            outfile=slc_project_class.processing_dir.joinpath(
-                f'{scene_id}_{burst.bid}_import'
-            ),
+            outfile=slc_project_class.processing_dir / f"{scene_id}_{burst.bid}_import",
             logfile=logger,
             swath=burst.SwathID,
             burst=burst.BurstNr,
-            config_dict=CONFIG_DICT
+            config_dict=CONFIG_DICT,
         )
         assert return_code == str(
-            slc_project_class.processing_dir.joinpath(
-                f'{scene_id}_{burst.bid}_import.dim'
-            )
+            slc_project_class.processing_dir / f"{scene_id}_{burst.bid}_import.dim"
         )
 
 
-#@pytest.mark.skip(reason="Some GPT Error, but does not happen in production!")
+@pytest.mark.skip(reason="not running in pip build")
 def test_burst_calibration(s1_slc_ost_master, slc_project_class):
 
     scene_id, master = s1_slc_ost_master
     for idx, burst in slc_project_class.burst_inventory.iterrows():
-        if idx > 2 or burst.SwathID != 'IW1':
+        if idx > 2 or burst.SwathID != "IW1":
             continue
         return_code = sw.calibration(
-            infile=slc_project_class.processing_dir.joinpath(
-                f'{scene_id}_{burst.bid}_import.dim'
-            ),
-            outfile=slc_project_class.processing_dir.joinpath(
-                f'{scene_id}_BS'
-            ),
+            infile=slc_project_class.processing_dir
+            / f"{scene_id}_{burst.bid}_import.dim",
+            outfile=slc_project_class.processing_dir / f"{scene_id}_BS",
             logfile=logger,
-            config_dict=CONFIG_DICT
+            config_dict=CONFIG_DICT,
         )
         assert return_code == str(
-            slc_project_class.processing_dir.joinpath(
-                f'{scene_id}_BS.dim'
-            )
+            slc_project_class.processing_dir / f"{scene_id}_BS.dim"
         )
 
 
-@pytest.mark.skip(reason="Takes too long skip for now!")
+@pytest.mark.skip(reason="not running in pip build")
 def test_burst_ha_alpha(
-        s1_slc_master,
-        s1_slc_ost_master,
-        slc_project_class,
+    s1_slc_master,
+    s1_slc_ost_master,
+    slc_project_class,
 ):
     scene_id, master = s1_slc_ost_master
     for idx, burst in slc_project_class.burst_inventory.iterrows():
@@ -78,14 +70,10 @@ def test_burst_ha_alpha(
             continue
         return_code = sw.ha_alpha(
             infile=s1_slc_master,
-            outfile=slc_project_class.processing_dir.joinpath(
-                f'{scene_id}_ha_alpha'
-            ),
+            outfile=slc_project_class.processing_dir / f"{scene_id}_ha_alpha",
             logfile=logger,
-            config_dict=CONFIG_DICT
+            config_dict=CONFIG_DICT,
         )
         assert return_code == str(
-            slc_project_class.processing_dir.joinpath(
-                f'{scene_id}_ha_alpha.dim'
-            )
+            slc_project_class.processing_dir / f"{scene_id}_ha_alpha.dim"
         )
diff --git a/tests/test_vector.py b/tests/test_vector.py
index cfd2d951..ac9ff2b1 100644
--- a/tests/test_vector.py
+++ b/tests/test_vector.py
@@ -1,35 +1,28 @@
-import os
-import sys
-
-
-import shapely 
-
-sys.path.append(os.getcwd())
-from ost.helpers import vector 
+# from ost.helpers import vector
 
 # test aoi_to_wkt
-aoi = vector.aoi_to_wkt('IRL')
-assert (type(aoi) == str and aoi.startswith('POLYGON'))
+# aoi = vector.aoi_to_wkt("IRL")
+# assert type(aoi) == str and aoi.startswith("POLYGON")
 
-aoi = vector.aoi_to_wkt('POLYGON((1 2,1 4,3 4,3 2,1 2))')
-assert (type(aoi) == str and aoi.startswith('POLYGON'))
+# aoi = vector.aoi_to_wkt("POLYGON((1 2,1 4,3 4,3 2,1 2))")
+# assert type(aoi) == str and aoi.startswith("POLYGON")
 
-aoi = vector.aoi_to_wkt('tests/testdata/test_polygon.geojson')
-assert (type(aoi) == str and aoi.startswith('POLYGON'))
+# aoi = vector.aoi_to_wkt("tests/testdata/test_polygon.geojson")
+# assert type(aoi) == str and aoi.startswith("POLYGON")
 
 # test latlon_to_wkt
-lat, lon = '-67', '-61'
-aoi = vector.latlon_to_wkt(lat, lon)
-assert (type(aoi) == str and aoi.startswith('POINT'))
+# lat, lon = "-67", "-61"
+# aoi = vector.latlon_to_wkt(lat, lon)
+# assert type(aoi) == str and aoi.startswith("POINT")
 
-aoi = vector.latlon_to_wkt(lat, lon, buffer_degree=1)
-assert (type(aoi) == str and aoi.startswith('POLYGON'))
+# aoi = vector.latlon_to_wkt(lat, lon, buffer_degree=1)
+# assert type(aoi) == str and aoi.startswith("POLYGON")
 
-aoi = vector.latlon_to_wkt(lat, lon, buffer_degree=1, envelope=False)
-assert (type(aoi) == str and aoi.startswith('POLYGON'))
+# aoi = vector.latlon_to_wkt(lat, lon, buffer_degree=1, envelope=False)
+# assert type(aoi) == str and aoi.startswith("POLYGON")
 
-aoi = vector.latlon_to_wkt(lat, lon, buffer_degree=1, envelope=True)
-assert (type(aoi) == str and aoi.startswith('POLYGON'))
+# aoi = vector.latlon_to_wkt(lat, lon, buffer_degree=1, envelope=True)
+# assert type(aoi) == str and aoi.startswith("POLYGON")
 
-aoi = vector.latlon_to_wkt(lat, lon, buffer_meter=1000, envelope=True)
-assert (type(aoi) == str and aoi.startswith('POLYGON'))
\ No newline at end of file
+# aoi = vector.latlon_to_wkt(lat, lon, buffer_meter=1000, envelope=True)
+# assert type(aoi) == str and aoi.startswith("POLYGON")
diff --git a/tests/testdata/cache/S1A_IW_SLC__1SDV_20190101T171515_20190101T171542_025287_02CC09_0A0B.zip b/tests/testdata/cache/S1A_IW_SLC__1SDV_20190101T171515_20190101T171542_025287_02CC09_0A0B.zip
deleted file mode 100644
index 47050ebd..00000000
Binary files a/tests/testdata/cache/S1A_IW_SLC__1SDV_20190101T171515_20190101T171542_025287_02CC09_0A0B.zip and /dev/null differ
diff --git a/tests/testdata/cache/S1A_IW_SLC__1SDV_20190113T171514_20190113T171541_025462_02D252_C063.zip b/tests/testdata/cache/S1A_IW_SLC__1SDV_20190113T171514_20190113T171541_025462_02D252_C063.zip
deleted file mode 100644
index e6d4c2a4..00000000
Binary files a/tests/testdata/cache/S1A_IW_SLC__1SDV_20190113T171514_20190113T171541_025462_02D252_C063.zip and /dev/null differ
diff --git a/tests/testdata/cache/S1B_IW_GRDH_1SDV_20180813T054020_20180813T054045_012240_0168D6_B775.zip b/tests/testdata/cache/S1B_IW_GRDH_1SDV_20180813T054020_20180813T054045_012240_0168D6_B775.zip
deleted file mode 100644
index cffcfb6a..00000000
Binary files a/tests/testdata/cache/S1B_IW_GRDH_1SDV_20180813T054020_20180813T054045_012240_0168D6_B775.zip and /dev/null differ
diff --git a/tests/testdata/test_polygon.geojson b/tests/testdata/test_polygon.geojson
deleted file mode 100644
index 51541627..00000000
--- a/tests/testdata/test_polygon.geojson
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-"type": "FeatureCollection",
-"name": "test_polygon",
-"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
-"features": [
-{ "type": "Feature", "properties": { }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -62.642260990940358, -9.144642335372852 ], [ -62.644538612626853, -9.656964044353968 ], [ -61.984028323546326, -9.643491691704158 ], [ -62.025025513903039, -9.155885526454442 ], [ -62.642260990940358, -9.144642335372852 ] ] ] } }
-]
-}