Repository: dlr-eoc/ukis-pysat

Issue:
Replace os.path with pathlib
We could consider using [pathlib](https://docs.python.org/3/library/pathlib.html) instead of os.path for more readability and to be more modern.

Discussion:
codecov-commenter: # [Codecov](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/79?src=pr&el=h1) Report
> Merging [#79](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/79?src=pr&el=desc) into [master](https://codecov.io/gh/dlr-eoc/ukis-pysat/commit/785947a01377d742e4cfa4a2139787ee8fafa1aa?el=desc) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/79/graphs/tree.svg?width=650&height=150&src=pr&token=6LYPGRGMXV)](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/79?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master      #79   +/-   ##
=======================================
  Coverage   93.23%   93.23%           
=======================================
  Files           8        8           
  Lines         754      754           
=======================================
  Hits          703      703           
  Misses         51       51           
```

| Flag | Coverage Δ | |
|---|---|---|
| #unittests | `93.23% <100.00%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/79?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [ukis\_pysat/file.py](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/79/diff?src=pr&el=tree#diff-dWtpc19weXNhdC9maWxlLnB5) | `94.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/79?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/79?src=pr&el=footer). Last update [785947a...2b5379a](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/79?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 981d100..49203cc 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -34,6 +34,7 @@ Changed
 - ``data.MetadataCollection()``: Improved plotting of MetadataCollection to_pandas method #56
 - ``data.MetadataCollection()``: Made filter method more flexible with list and fuzzy filter options #55
 - ``raster.Image()``: Split `_pad_to_bbox()` into `pad()` and `_get_pad_width()`, updated `mask()` #59
+- replaced os.path with Pathlib #78
 
 Removed
 *******
diff --git a/ukis_pysat/data.py b/ukis_pysat/data.py
index a21f7ff..244c2fa 100644
--- a/ukis_pysat/data.py
+++ b/ukis_pysat/data.py
@@ -5,6 +5,7 @@ import json
 import os
 import shutil
 from io import BytesIO
+from pathlib import Path
 from typing import List
 
 try:
@@ -101,13 +102,13 @@ class Source:
             # get all json files in datadir that match substr
             meta_files = sorted(
                 [
-                    os.path.join(dp, f)
+                    Path(dp).joinpath(f)
                     for dp, dn, filenames in os.walk(self.api)
                     for substr in self.api_substr
                     for f in filenames
                     if f.endswith(".json") and substr in f
                 ],
-                key=str.lower,
+                key=lambda path: str(path).lower(),
             )
 
             # filter json files by query parameters
@@ -118,7 +119,7 @@ class Source:
                     try:
                         self.construct_metadata(meta_src=m, platform=platform)
                     except (json.decoder.JSONDecodeError, LookupError, TypeError) as e:
-                        raise ValueError(f"{os.path.basename(meta_file)} not a valid metadata file. {e}.")
+                        raise ValueError(f"{meta_file.name} not a valid metadata file. {e}.")
                     m_platform = m["properties"]["platformname"]
                     m_date = sentinelsat.format_query_date(m["properties"]["acquisitiondate"])
                     m_geom = geometry.shape(m["geometry"])
@@ -236,8 +237,10 @@ class Source:
 
         :param platform: Image platform (<enum 'Platform'>).
         :param product_uuid: UUID of the satellite image product (String).
-        :param target_dir: Target directory that holds the downloaded images (String)
+        :param target_dir: Target directory that holds the downloaded images (String, Path)
         """
+        if isinstance(target_dir, str):
+            target_dir = Path(target_dir)
         if self.src == Datahub.File:
             raise Exception("download_image not supported for {self.src}.")
 
@@ -251,9 +254,9 @@ class Source:
 
             # compress download directory and remove original files
             shutil.make_archive(
-                os.path.join(target_dir, product_srcid), "zip", root_dir=os.path.join(target_dir, product_srcid),
+                target_dir.joinpath(product_srcid), "zip", root_dir=target_dir.joinpath(product_srcid),
             )
-            shutil.rmtree(os.path.join(target_dir, product_srcid))
+            shutil.rmtree(target_dir.joinpath(product_srcid))
 
         else:
             self.api.download(product_uuid, target_dir, checksum=True)
@@ -264,8 +267,10 @@ class Source:
 
         :param platform: Image platform (<enum 'Platform'>).
         :param product_uuid: UUID of the satellite image product (String).
-        :param target_dir: Target directory that holds the downloaded images (String)
+        :param target_dir: Target directory that holds the downloaded images (String, Path)
         """
+        if isinstance(target_dir, str):
+            target_dir = Path(target_dir)
         if self.src == Datahub.File:
             raise NotImplementedError(f"download_quicklook not supported for {self.src}.")
 
@@ -291,14 +296,14 @@ class Source:
         # use threshold of 50 to overcome noise in JPEG compression
         xs, ys, zs = np.where(quicklook >= 50)
         quicklook = quicklook[min(xs) : max(xs) + 1, min(ys) : max(ys) + 1, min(zs) : max(zs) + 1]
-        Image.fromarray(quicklook).save(os.path.join(target_dir, product_srcid + ".jpg"))
+        Image.fromarray(quicklook).save(target_dir.joinpath(product_srcid + ".jpg"))
 
         # geocode quicklook
         quicklook_size = (quicklook.shape[1], quicklook.shape[0])
         dist_x = geometry.Point(bounds[0], bounds[1]).distance(geometry.Point(bounds[2], bounds[1])) / quicklook_size[0]
         dist_y = geometry.Point(bounds[0], bounds[1]).distance(geometry.Point(bounds[0], bounds[3])) / quicklook_size[1]
         ul_x, ul_y = bounds[0], bounds[3]
-        with open(os.path.join(os.path.join(target_dir, product_srcid + ".jpgw")), "w") as out_file:
+        with open(target_dir.joinpath(product_srcid + ".jpgw"), "w") as out_file:
             out_file.write(str(dist_x) + "\n")
             out_file.write(str(0.0) + "\n")
             out_file.write(str(0.0) + "\n")
@@ -315,10 +320,10 @@ class Source:
         """
 
         # check if handed object is a string
-        # this could include both fiel paths and WKT strings
-        if isinstance(aoi, str):
+        # this could include both file paths and WKT strings
+        if isinstance(aoi, (str, Path)):
             # check if handed object is a file
-            if os.path.isfile(aoi):
+            if Path(aoi).is_file():
                 with fiona.open(aoi, "r") as aoi:
                     # make sure crs is in epsg:4326
                     project = pyproj.Transformer.from_proj(
@@ -328,18 +333,14 @@ class Source:
                         always_xy=True,
                     )
                     aoi = ops.transform(project.transform, geometry.shape(aoi[0]["geometry"]))
-
-            elif wkt.loads(aoi):
-                aoi = wkt.loads(aoi)
-
             else:
-                raise ValueError(f"aoi must be a filepath or a WKT string")
+                aoi = wkt.loads(aoi)
 
         elif isinstance(aoi, tuple):
             aoi = geometry.box(aoi[0], aoi[1], aoi[2], aoi[3])
 
         else:
-            raise TypeError(f"aoi must be of type string or tuple")
+            raise TypeError(f"aoi must be of type string, Path or tuple")
 
         return aoi
 
@@ -437,10 +438,12 @@ class Metadata:
     def save(self, target_dir):
         """Saves Metadata to GeoJSON file in target_dir with srcid as filename.
 
-        :param target_dir: Target directory that holds the downloaded metadata (String)
+        :param target_dir: Target directory that holds the downloaded metadata (String, Path)
         """
         g = self.to_geojson()
-        with open(os.path.join(target_dir, g["properties"]["srcid"] + ".json"), "w") as f:
+        if isinstance(target_dir, str):
+            target_dir = Path(target_dir)
+        with open(target_dir.joinpath(g["properties"]["srcid"] + ".json"), "w") as f:
             json.dump(g, f)
 
 
@@ -484,7 +487,7 @@ class MetadataCollection:
         except ImportError:
             raise ImportError("to_pandas requires optional dependency Pandas.")
 
-        pd.set_option('display.max_colwidth', -1)
+        pd.set_option("display.max_colwidth", -1)
         d = [item.to_dict() for item in self.items]
         return pd.DataFrame(d)
 
@@ -492,6 +495,7 @@ class MetadataCollection:
         """Filters MetadataCollection based on filter_dict.
 
         :param filter_dict: Key value pair to use as filter e.g. {"producttype": "S2MSI1C"} (Dictionary).
+        :param type: how to filter match, default 'exact', alternative 'fuzzy'.
         :returns: self
         """
         k = list(filter_dict.keys())[0]
@@ -517,7 +521,7 @@ class MetadataCollection:
     def save(self, target_dir):
         """Saves MetadataCollection to GeoJSON files in target_dir with srcid as filenames.
 
-        :param target_dir: Target directory (String)
+        :param target_dir: Target directory (String, Path)
         """
         for item in self.items:
             item.save(target_dir)
diff --git a/ukis_pysat/file.py b/ukis_pysat/file.py
index df1624c..26e5ad0 100644
--- a/ukis_pysat/file.py
+++ b/ukis_pysat/file.py
@@ -3,11 +3,11 @@
 import contextlib
 import os
 import re
-import shutil
 import tempfile
 import xml.etree.ElementTree as ET
 import zipfile
 from datetime import datetime, timezone
+from pathlib import Path
 
 
 def env_get(key):
@@ -25,32 +25,32 @@ def get_sentinel_scene_from_dir(indir):
     :param indir: path to zipped S1 scene or directory with S1 scene
     :yields: full_path (directory with scene, str), ident (filename of scene, str)
 
-    >>> with get_sentinel_scene_from_dir(os.path.join(r"..", "tests", "testfiles")) as (full_path, ident):
-    ...     print(ident)
+    >>> with get_sentinel_scene_from_dir(Path(__file__).parents[1] / "tests/testfiles") as (fp, name):
+    ...     print(name)
     S1M_hello_from_inside
     """
+    if isinstance(indir, str):
+        indir = Path(indir)
     pattern = re.compile("^S[1-3]._+")
 
-    for entry in os.listdir(indir):
-        full_path = os.path.join(indir, entry)
-
-        ident = os.path.splitext(os.path.basename(full_path))[0]
+    for full_path in indir.iterdir():
+        ident = full_path.stem
         if not pattern.match(ident):
             continue
 
-        if full_path.endswith(".zip"):
-            cwd = os.getcwd()
-            scene_zip = os.path.abspath(full_path)
+        if full_path.suffix == ".zip":
+            cwd = Path.cwd()
             with tempfile.TemporaryDirectory() as td:
+                td = Path(td)
                 os.chdir(td)
                 try:
-                    with zipfile.ZipFile(scene_zip) as z:
+                    with zipfile.ZipFile(full_path) as z:
                         z.extractall()
                         with get_sentinel_scene_from_dir(td) as res:
                             yield res
                 finally:
                     os.chdir(cwd)
-        elif os.path.isdir(full_path):
+        elif full_path.is_dir():
             yield full_path, ident
 
 
@@ -124,7 +124,7 @@ def get_footprint_from_manifest(xml_path):
     :param xml_path: path to manifest.safe
     :return: shapely polygon
 
-    >>> get_footprint_from_manifest(os.path.join(r"../tests/testfiles/", "manifest.safe")).wkt
+    >>> get_footprint_from_manifest(Path(__file__).parents[1] / "tests/testfiles/manifest.safe").wkt
     'POLYGON ((149.766922 -24.439564, 153.728622 -23.51771, 154.075058 -24.737713, 150.077042 -25.668921, 149.766922 -24.439564))'
     """
     try:
@@ -151,7 +151,7 @@ def get_origin_from_manifest(xml_path):
     :param xml_path: path to manifest.safe
     :return: country of origin
 
-    >>> get_origin_from_manifest(os.path.join(r"../tests/testfiles/", "manifest.safe"))
+    >>> get_origin_from_manifest(Path(__file__).parents[1] / "tests/testfiles/manifest.safe")
     'United Kingdom'
     """
     tree = ET.parse(xml_path)
@@ -168,7 +168,7 @@ def get_ipf_from_manifest(xml_path):
     :param xml_path: path to manifest.safe
     :return: ipf version (float)
 
-    >>> get_ipf_from_manifest(os.path.join(r"../tests/testfiles/", "manifest.safe"))
+    >>> get_ipf_from_manifest(Path(__file__).parents[1] / "tests/testfiles/manifest.safe")
     2.82
     """
     tree = ET.parse(xml_path)
@@ -186,12 +186,14 @@ def get_pixel_spacing(scenedir, polarization="HH"):
     :param polarization: str (default: 'HH')
     :return: tuple with pixel spacing in meters and degrees as floats
 
-    >>> get_pixel_spacing(r"../tests/testfiles/")
+    >>> get_pixel_spacing(Path(__file__).parents[1] / "tests/testfiles")
     (40.0, 0.0003593261136478086)
     """
-    for entry in os.listdir(os.path.join(scenedir, "annotation")):
-        if entry.endswith(".xml") and entry.split("-")[3] == polarization.lower():
-            tree = ET.parse(os.path.join(scenedir, "annotation", entry))
+    if isinstance(scenedir, str):
+        scenedir = Path(scenedir)
+    for path_to_file in scenedir.joinpath("annotation").iterdir():
+        if path_to_file.suffix == ".xml" and path_to_file.name.split("-")[3] == polarization.lower():
+            tree = ET.parse(path_to_file)
             root = tree.getroot()
             for elem in root.iter("imageInformation"):
                 for child in elem.iter():
@@ -209,8 +211,8 @@ def get_proj_string(footprint):
     :param footprint: shapely polygon
     :return: string with information about projection
 
-    >>> get_proj_string(get_footprint_from_manifest(r"../tests/testfiles/"))
-    +proj=utm +zone=56J, +ellps=WGS84 +datum=WGS84 +units=m +no_defs
+    >>> get_proj_string(get_footprint_from_manifest(Path(__file__).parents[1] / "tests/testfiles/manifest.safe"))
+    '+proj=utm +zone=56J, +ellps=WGS84 +datum=WGS84 +units=m +no_defs'
     """
     try:
         import utm
diff --git a/ukis_pysat/raster.py b/ukis_pysat/raster.py
index d23ca93..2676c25 100644
--- a/ukis_pysat/raster.py
+++ b/ukis_pysat/raster.py
@@ -2,6 +2,7 @@
 
 import math
 from itertools import product
+from pathlib import Path
 
 from ukis_pysat.members import Platform
 
@@ -45,7 +46,7 @@ class Image:
             self.dataset = data
             self.__arr = self.dataset.read()
 
-        elif isinstance(data, str):
+        elif isinstance(data, (str, Path)):
             self.dataset = rasterio.open(data)
             self.__arr = self.dataset.read()
 
@@ -57,7 +58,7 @@ class Image:
             if dimorder == "first":
                 self.__arr = data
             else:
-                self.__arr = reshape_as_raster(data)
+                self.__arr = reshape_as_raster(data)
             self.dataset = None
             self.__update_dataset(crs, transform)
         else:
@@ -194,12 +195,12 @@ class Image:
             "count": self.__arr.shape[0],
             "crs": crs,
             "transform": transform,
-        }
-
-        memfile = MemoryFile()
-        with memfile.open(**meta) as ds:
-            ds.write(self.__arr)
-        self.dataset = memfile.open()
+        }
+
+        memfile = MemoryFile()
+        with memfile.open(**meta) as ds:
+            ds.write(self.__arr)
+        self.dataset = memfile.open()
         memfile.close()
 
     def warp(self, dst_crs, resampling_method=0, num_threads=4, resolution=None, nodata=None, target_align=None):
@@ -211,14 +212,14 @@ class Image:
         :param num_threads: int, number of workers, optional (default: 4)
         :param resolution: tuple (x resolution, y resolution) or float, optional.
             Target resolution, in units of target coordinate reference system.
-        :param target_align: raster to which to align resolution, extent and gridspacing, optional (Image).
+        :param target_align: raster to which to align resolution, extent and gridspacing, optional (Image).
         :param nodata: nodata value of source, int or float, optional.
-        """
-        if target_align:
-            transform = target_align.dataset.transform
-            width = target_align.dataset.width
-            height = target_align.dataset.height
-
+        """
+        if target_align:
+            transform = target_align.dataset.transform
+            width = target_align.dataset.width
+            height = target_align.dataset.height
+
         else:
             if resolution:
                 transform, width, height = calculate_default_transform(
@@ -268,7 +269,7 @@ class Image:
                 raise AttributeError(f"'mtl_file' has to be set if platform is {platform}.")
             else:
                 # get rescale factors from mtl file
-                mtl = toa_utils._load_mtl(mtl_file)  # no obvious reason not to call this
+                mtl = toa_utils._load_mtl(str(mtl_file))  # no obvious reason not to call this
                 metadata = mtl["L1_METADATA_FILE"]
                 sun_elevation = metadata["IMAGE_ATTRIBUTES"]["SUN_ELEVATION"]
                 toa = []


Test patch:
diff --git a/tests/test_data.py b/tests/test_data.py
index ca98af1..ac498dd 100644
--- a/tests/test_data.py
+++ b/tests/test_data.py
@@ -1,6 +1,7 @@
 import os
 import traceback
 import unittest
+from pathlib import Path
 
 from ukis_pysat.data import Source
 from ukis_pysat.members import Datahub, Platform
@@ -10,11 +11,13 @@ from ukis_pysat.members import Datahub, Platform
 # os.environ["SCIHUB_USER"] = "Tim"
 # os.environ["SCIHUB_PW"] = "TheEnchanter"
 
-aoi_4326 = os.path.join(os.path.dirname(__file__), "testfiles", "aoi_4326.geojson")
-aoi_3857 = os.path.join(os.path.dirname(__file__), "testfiles", "aoi_3857.geojson")
+target_dir = Path(__file__).parents[0] / "testfiles"
+str_target = os.path.join(os.path.dirname(os.path.realpath(__file__)), "testfiles")
+aoi_4326 = target_dir / "aoi_4326.geojson"
+aoi_3857 = target_dir / "aoi_3857.geojson"
 aoi_wkt = "POLYGON((11.09 47.94, 11.06 48.01, 11.12 48.11, 11.18 48.11, 11.18 47.94, 11.09 47.94))"
 aoi_bbox = (11.90, 51.46, 11.94, 51.50)
-target_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "testfiles")
+
 
 queries = [
     {
@@ -133,20 +136,23 @@ class DownloadTest(unittest.TestCase):
                     cloud_cover=queries[i]["cloud_cover"],
                 )
                 meta.filter(filter_dict={"srcid": queries[i]["returns_srcid"]},)
-                meta.save(target_dir)
+                if i % 2 == 0:
+                    meta.save(target_dir)
+                else:
+                    meta.save(str_target)
             returns_srcid = meta.to_geojson()[0]["properties"]["srcid"]
             returns_uuid = meta.to_geojson()[0]["properties"]["srcuuid"]
             self.assertEqual(returns_srcid, (queries[i]["returns_srcid"]))
             self.assertEqual(returns_uuid, (queries[i]["returns_uuid"]))
-            self.assertTrue(os.path.isfile(os.path.join(target_dir, queries[i]["returns_srcid"]) + ".json"))
-            os.remove(os.path.join(target_dir, queries[i]["returns_srcid"]) + ".json")
+            self.assertTrue(target_dir.joinpath(queries[i]["returns_srcid"] + ".json").is_file())
+            target_dir.joinpath(queries[i]["returns_srcid"] + ".json").unlink()
 
     # @unittest.skip("uncomment when you set ENVs with credentials")
     def test_download_image(self):
         src = Source(datahub=Datahub.File, datadir=target_dir)
         with self.assertRaises(Exception, msg="download_image not supported for Datahub.File."):
             src.download_image(
-                platform=Datahub.File, product_uuid="1", target_dir=target_dir,
+                platform=Datahub.File, product_uuid="1", target_dir=str_target,
             )
         # TODO download tests
 
@@ -160,7 +166,7 @@ class DownloadTest(unittest.TestCase):
                         src.download_quicklook(
                             platform=queries[i]["platform_name"],
                             product_uuid=queries[i]["returns_uuid"],
-                            target_dir=target_dir,
+                            target_dir=str_target,
                         )
                 else:
                     src.download_quicklook(
@@ -168,10 +174,10 @@ class DownloadTest(unittest.TestCase):
                         product_uuid=queries[i]["returns_uuid"],
                         target_dir=target_dir,
                     )
-                    self.assertTrue(os.path.isfile(os.path.join(target_dir, queries[i]["returns_srcid"]) + ".jpg"))
-                    self.assertTrue(os.path.isfile(os.path.join(target_dir, queries[i]["returns_srcid"]) + ".jpgw"))
-                    os.remove(os.path.join(target_dir, queries[i]["returns_srcid"]) + ".jpg")
-                    os.remove(os.path.join(target_dir, queries[i]["returns_srcid"]) + ".jpgw")
+                    self.assertTrue(target_dir.joinpath(queries[i]["returns_srcid"] + ".jpg").is_file())
+                    self.assertTrue(target_dir.joinpath(queries[i]["returns_srcid"] + ".jpgw").is_file())
+                    target_dir.joinpath(queries[i]["returns_srcid"] + ".jpg").unlink()
+                    target_dir.joinpath(queries[i]["returns_srcid"] + ".jpgw").unlink()
 
         src = Source(datahub=Datahub.File, datadir=target_dir)
         with self.assertRaises(NotImplementedError, msg=f"download_quicklook not supported for Datahub.File."):
diff --git a/tests/test_file.py b/tests/test_file.py
index e971078..bcebb27 100644
--- a/tests/test_file.py
+++ b/tests/test_file.py
@@ -1,11 +1,12 @@
 import os
-import shutil
 import unittest
 from datetime import datetime, timezone
+from pathlib import Path
 
 import ukis_pysat.file as psf
 
-path_testfiles = os.path.join(os.path.dirname(__file__), "testfiles")
+path_testfiles = Path(__file__).parents[0] / "testfiles"
+str_path = os.path.join(os.path.dirname(__file__), "testfiles")
 
 
 class FileTest(unittest.TestCase):
@@ -17,7 +18,10 @@ class FileTest(unittest.TestCase):
         with psf.get_sentinel_scene_from_dir(path_testfiles) as (full_path, ident):
             self.assertEqual("S1M_hello_from_inside", ident)
 
-        with psf.get_sentinel_scene_from_dir(os.path.join(path_testfiles, "another_scene")) as (full_path, ident):
+        with psf.get_sentinel_scene_from_dir(str_path) as (full_path, ident):
+            self.assertEqual("S1M_hello_from_inside", ident)
+
+        with psf.get_sentinel_scene_from_dir(path_testfiles.joinpath("another_scene")) as (full_path, ident):
             self.assertEqual("S2__IN_FOLDER", ident)
 
     def test_get_polarization_from_s1_filename(self):
@@ -80,27 +84,29 @@ class FileTest(unittest.TestCase):
 
     def test_get_footprint_from_manifest(self):
         self.assertEqual(
-            psf.get_footprint_from_manifest(os.path.join(path_testfiles, "manifest.safe")).wkt,
+            psf.get_footprint_from_manifest(path_testfiles.joinpath("manifest.safe")).wkt,
             "POLYGON ((149.766922 -24.439564, 153.728622 -23.51771, 154.075058 -24.737713, 150.077042 "
             "-25.668921, 149.766922 -24.439564))",
         )
 
     def test_get_origin_from_manifest(self):
         self.assertEqual(
-            psf.get_origin_from_manifest(os.path.join(path_testfiles, "manifest.safe")), "United Kingdom",
+            psf.get_origin_from_manifest(path_testfiles.joinpath("manifest.safe")), "United Kingdom",
         )
 
     def test_get_ipf_from_manifest(self):
         self.assertEqual(
-            psf.get_ipf_from_manifest(os.path.join(path_testfiles, "manifest.safe")), 2.82,
+            psf.get_ipf_from_manifest(path_testfiles.joinpath("manifest.safe")), 2.82,
         )
 
     def test_get_pixel_spacing(self):
         self.assertEqual(psf.get_pixel_spacing(path_testfiles), (40.0, 0.0003593261136478086))
 
+        self.assertEqual(psf.get_pixel_spacing(str_path), (40.0, 0.0003593261136478086))
+
     def test_get_proj_string(self):
         self.assertEqual(
-            psf.get_proj_string(psf.get_footprint_from_manifest(os.path.join(path_testfiles, "manifest.safe"))),
+            psf.get_proj_string(psf.get_footprint_from_manifest(path_testfiles.joinpath("manifest.safe"))),
             r"+proj=utm +zone=56J, +ellps=WGS84 +datum=WGS84 +units=m +no_defs",
         )
 
diff --git a/tests/test_raster.py b/tests/test_raster.py
index 7ee6eeb..d996f6c 100644
--- a/tests/test_raster.py
+++ b/tests/test_raster.py
@@ -1,6 +1,7 @@
 #!/usr/bin/env python3
 import os
 import unittest
+from pathlib import Path
 
 import dask.array
 import numpy as np
@@ -11,7 +12,7 @@ from shapely.geometry import box
 from ukis_pysat.members import Platform
 from ukis_pysat.raster import Image
 
-TEST_FILE = os.path.join(os.path.dirname(__file__), "testfiles", "dummy.tif")
+TEST_FILE = Path(__file__).parents[0] / "testfiles" / "dummy.tif"
 
 
 class DataTest(unittest.TestCase):
@@ -184,35 +185,33 @@ class DataTest(unittest.TestCase):
         self.assertEqual(source_img.dataset.transform, target_img.dataset.transform)
 
     def test_dn2toa(self):
-        target_dir = os.path.join(os.path.dirname(__file__), "testfiles", "satellite_data")
+        target_dir = Path(__file__).parents[0] / "testfiles" / "satellite_data"
         tests = [
             {
                 "platform": Platform.Landsat8,
-                "dn_file": os.path.join(target_dir, "LC08_L1TP_193024_20200509_20200509_01_RT.tif"),
-                "toa_file": os.path.join(target_dir, "LC08_L1TP_193024_20200509_20200509_01_RT_toa.tif"),
-                "mtl_file": os.path.join(target_dir, "LC08_L1TP_193024_20200509_20200509_01_RT_MTL.txt"),
+                "dn_file": target_dir.joinpath("LC08_L1TP_193024_20200509_20200509_01_RT.tif"),
+                "toa_file": target_dir.joinpath("LC08_L1TP_193024_20200509_20200509_01_RT_toa.tif"),
+                "mtl_file": target_dir.joinpath("LC08_L1TP_193024_20200509_20200509_01_RT_MTL.txt"),
                 "wavelengths": ["Aerosol", "Blue", "Green", "Red", "NIR", "SWIR1", "SWIR2", "Cirrus", "TIRS1", "TIRS2"],
             },
             {
                 "platform": Platform.Landsat7,
-                "dn_file": os.path.join(target_dir, "LE07_L1TP_193024_20100420_20161215_01_T1.tif"),
-                "toa_file": os.path.join(target_dir, "LE07_L1TP_193024_20100420_20161215_01_T1_toa.tif"),
-                "mtl_file": os.path.join(target_dir, "LE07_L1TP_193024_20100420_20161215_01_T1_MTL.txt"),
+                "dn_file": target_dir.joinpath("LE07_L1TP_193024_20100420_20161215_01_T1.tif"),
+                "toa_file": target_dir.joinpath("LE07_L1TP_193024_20100420_20161215_01_T1_toa.tif"),
+                "mtl_file": target_dir.joinpath("LE07_L1TP_193024_20100420_20161215_01_T1_MTL.txt"),
                 "wavelengths": ["Blue", "Green", "Red", "NIR", "SWIR1", "TIRS1", "TIRS2", "SWIR2"],
             },
             {
                 "platform": Platform.Landsat5,
-                "dn_file": os.path.join(target_dir, "LT05_L1TP_193024_20050516_20161127_01_T1.tif"),
-                "toa_file": os.path.join(target_dir, "LT05_L1TP_193024_20050516_20161127_01_T1_toa.tif"),
-                "mtl_file": os.path.join(target_dir, "LT05_L1TP_193024_20050516_20161127_01_T1_MTL.txt"),
+                "dn_file": target_dir.joinpath("LT05_L1TP_193024_20050516_20161127_01_T1.tif"),
+                "toa_file": target_dir.joinpath("LT05_L1TP_193024_20050516_20161127_01_T1_toa.tif"),
+                "mtl_file": target_dir.joinpath("LT05_L1TP_193024_20050516_20161127_01_T1_MTL.txt"),
                 "wavelengths": ["Blue", "Green", "Red", "NIR", "SWIR1", "TIRS", "SWIR2"],
             },
             {
                 "platform": Platform.Sentinel2,
-                "dn_file": os.path.join(target_dir, "S2B_MSIL1C_20200406T101559_N0209_R065_T32UPC_20200406T130159.tif"),
-                "toa_file": os.path.join(
-                    target_dir, "S2B_MSIL1C_20200406T101559_N0209_R065_T32UPC_20200406T130159_toa.tif"
-                ),
+                "dn_file": target_dir.joinpath("S2B_MSIL1C_20200406T101559_N0209_R065_T32UPC_20200406T130159.tif"),
+                "toa_file": target_dir.joinpath("S2B_MSIL1C_20200406T101559_N0209_R065_T32UPC_20200406T130159_toa.tif"),
                 "mtl_file": None,
                 "wavelengths": None,
             },