Repository: dlr-eoc/ukis-pysat

Issue:
Consider array band count when writing Image
Currently [write_to_file() ](https://github.com/dlr-eoc/ukis-pysat/blob/9da685ab9d4644ddb0b7a7fc0c864fd96f642d98/ukis_pysat/raster.py#L360) takes width and height from the Image array but its band number (count) from the dataset. 

In case one initializes an Image() from an existing dataset and an array it may happen that the wrong band number is used internally to write to file. Take for example the case where someone computes a simple NVDI from a multi-band Image() and wants to write the result (a single-band image) back to file. 

```python
img = Image(path=multiband_file.tif)
ndvi_arr = (img.arr[3] - img.arr[2]) / (img.arr[3] + img.arr[2])
ndvi = Image(dataset=img.dataset, array=ndvi_arr)
ndvi.write_to_file(ndvi_file.tif)
```
This would result in an error upon writing the array to file, because img band number != ndvi band number.

The following could be a possible solution to account for this:
```python
    def write_to_file(self, path_to_file, dtype, driver="GTiff", compress=None):
        """
        Write a dataset to file.
        :param path_to_file: str, path to new file
        :param dtype: datatype, like np.uint16, 'float32' or 'min' to use the minimum type to represent values
        :param driver: str, optional (default: 'GTiff')
        :param compress: compression, e.g. 'lzw' (default: None)
        """
        if dtype == 'min':
            dtype = get_minimum_dtype(self.__arr)

        profile = self.dataset.meta
        profile.update(
            {
                "driver": driver,
                "height": self.__arr.shape[-2],
                "width": self.__arr.shape[-1],
                "count": self.__arr.shape[0],
                "dtype": dtype,
                "transform": self.transform,
                "crs": self.crs,
            }
        )

        if compress:
            profile.update({"compress": compress})

        with rasterio.open(path_to_file, "w", **profile) as dst:
            dst.write(self.__arr.astype(dtype), indexes=range(1, self.__arr.shape[0] + 1)
```


Discussion:
codecov-commenter: # [Codecov](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/47?src=pr&el=h1) Report
> Merging [#47](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/47?src=pr&el=desc) into [master](https://codecov.io/gh/dlr-eoc/ukis-pysat/commit/657e294ee8b68baa0cb4059007d9cd977b556be9&el=desc) will **decrease** coverage by `17.27%`.
> The diff coverage is `98.26%`.

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

```diff
@@             Coverage Diff             @@
##           master      #47       +/-   ##
===========================================
- Coverage   93.48%   76.21%   -17.28%     
===========================================
  Files           8        8               
  Lines         675      723       +48     
===========================================
- Hits          631      551       -80     
- Misses         44      172      +128     
```

| Flag | Coverage Δ | |
|---|---|---|
| #unittests | `76.21% <98.26%> (-17.28%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/47?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [tests/test\_data.py](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/47/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9kYXRhLnB5) | `56.14% <80.00%> (-42.11%)` | :arrow_down: |
| [ukis\_pysat/raster.py](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/47/diff?src=pr&el=tree#diff-dWtpc19weXNhdC9yYXN0ZXIucHk=) | `93.03% <97.67%> (-0.72%)` | :arrow_down: |
| [tests/test\_raster.py](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/47/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9yYXN0ZXIucHk=) | `99.21% <100.00%> (+0.28%)` | :arrow_up: |
| [ukis\_pysat/data.py](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/47/diff?src=pr&el=tree#diff-dWtpc19weXNhdC9kYXRhLnB5) | `40.18% <0.00%> (-47.67%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/47?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/47?src=pr&el=footer). Last update [8ebed56...92fa92a](https://codecov.io/gh/dlr-eoc/ukis-pysat/pull/47?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

fwfichtner: We're skipping tests related to EarthExplorer API because it's currently offline

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index e04d08e..8334b7f 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -13,7 +13,7 @@ Changed
 ~~~~~~~
 something was changed
 
-[master]  (2020-06-16)
+[master]  (2020-**-**)
 ----------------------
 
 Added
@@ -24,6 +24,10 @@ Fixed
 *****
 - ``file.get_ts_from_sentinel_filename()``: Return datetime.datetime objects instead of timestamp strings #42
 
+Changed
+*******
+- ``raster.Image()``: update of init signature to be less confusing #41
+
 [0.4.0]  (2020-06-05)
 ----------------------
 
diff --git a/ukis_pysat/raster.py b/ukis_pysat/raster.py
index 3eeb1bd..f8ce71a 100644
--- a/ukis_pysat/raster.py
+++ b/ukis_pysat/raster.py
@@ -1,8 +1,10 @@
-#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
 
 import math
 from itertools import product
 
+from ukis_pysat.members import Platform
+
 try:
     import numpy as np
     import rasterio
@@ -22,41 +24,46 @@ except ImportError as e:
     )
     raise ImportError(str(e) + "\n\n" + msg)
 
-from ukis_pysat.members import Platform
-
 
 class Image:
-    def __init__(self, path=None, dataset=None, arr=None, dimorder="first"):
+
+    da_arr = None
+
+    def __init__(self, data, dimorder="first", crs=None, transform=None):
         """
-        :param path: str, path to raster (default: None)
-        :param dataset: rasterio.io.DatasetReader (default: None)
-        :param arr: np.ndarray (default: None)
-        :param dimorder: dimensions order of arr. return channels or bands as 'first' or 'last' dimension. should match
-            the dimensions order of the input array when initializing an image from an array (default: 'first)
+        :param data: rasterio.io.DatasetReader or path to raster or np.ndarray of shape (bands, rows, columns)
+        :param dimorder: Order of channels or bands 'first' or 'last' (default: 'first')
+        :param crs: Coordinate reference system used when creating form array. If 'data' is np.ndarray this is required (default: None)
+        :param transform: Affine transformation mapping the pixel space to geographic space. If 'data' is np.ndarray this is required (default:None)
         """
-        if path:
-            if isinstance(path, str):
-                self.dataset = rasterio.open(path)
-                self.__arr = self.dataset.read()
-            else:
-                raise TypeError(f"path must be of type str")
+        if dimorder in ("first", "last"):
+            self.dimorder = dimorder
         else:
-            if isinstance(dataset, rasterio.io.DatasetReader) and isinstance(arr, np.ndarray):
-                self.dataset = dataset
-                if dimorder == "first":
-                    self.__arr = arr
-                elif dimorder == "last":
-                    self.__arr = reshape_as_raster(arr)
-                else:
-                    raise AttributeError("dimorder for bands or channels must be either 'first' or 'last'.")
+            raise TypeError("dimorder for bands or channels must be either 'first' or 'last'.")
+        try:
+            self.dataset = rasterio.open(data)
+            self.crs = self.dataset.crs
+            self.transform = self.dataset.transform
+            self.__arr = self.dataset.read()
+        except (TypeError, AttributeError):
+            if isinstance(data, rasterio.io.DatasetReader):
+                self.dataset = data
+                self.crs = self.dataset.crs
+                self.transform = self.dataset.transform
+                self.__arr = self.dataset.read()
+            elif isinstance(data, np.ndarray):
+                if crs is None:
+                    raise TypeError("if dataset is of type np.ndarray crs must not be None")
+                if transform is None:
+                    raise TypeError("if dataset is of type np.ndarray transform must not be None")
+                meta = {"dtype": data.dtype, "count": data.shape[0], "crs": crs, "driver": "GTiff"}
+                self.__arr = data
+                self.transform = transform
+                self.dataset = self.__update_dataset(meta).open()
+                self.crs = self.dataset.crs
+                self.transform = self.dataset.transform
             else:
-                raise TypeError(
-                    f"dataset must be of type rasterio.io.DatasetReader and arr must be of type " f"numpy.ndarray"
-                )
-        self.dimorder = dimorder
-        self.transform = self.dataset.transform
-        self.crs = self.dataset.crs
-        self.da_arr = None
+                raise TypeError("dataset must be of type str, rasterio.io.DatasetReader or np.ndarray")
 
     @property
     def arr(self):
@@ -77,16 +84,18 @@ class Image:
         valid_data_window = windows.get_data_window(self.__arr, nodata=nodata)
         return windows.bounds(valid_data_window, windows.transform(valid_data_window, self.transform))
 
-    def mask_image(self, bbox, crop=True, pad=False, **kwargs):
+    def mask_image(self, bbox, crop=True, pad=False, mode="constant", constant_values=0):
         """Mask the area outside of the input shapes with no data.
 
         :param bbox: bounding box of type tuple or Shapely Polygon
         :param crop: bool, see rasterio.mask. Optional, (default: True)
         :param pad: pads image, should only be used when bbox.bounds extent img.bounds, optional (default: False)
+        :param mode: str, how to pad, see rasterio.pad. Optional (default: 'constant') 
+        :param constant_values: nodata value, padding should be filled with, optional (default: 0)
         """
         # TODO https://github.com/mapbox/rasterio/issues/995
         if pad:
-            self.dataset = self._pad_to_bbox(bbox, **kwargs).open()
+            self.dataset = self._pad_to_bbox(bbox, mode, constant_values).open()
 
         if isinstance(bbox, polygon.Polygon):
             self.__arr, self.transform = rasterio.mask.mask(self.dataset, [bbox], crop=crop)
@@ -96,7 +105,7 @@ class Image:
             raise TypeError(f"bbox must be of type tuple or Shapely Polygon")
 
         # update for further processing
-        self.dataset = self.__update_dataset().open()
+        self.dataset = self.__update_dataset(self.dataset.meta).open()
 
     def _pad_to_bbox(self, bbox, mode="constant", constant_values=0):
         """Buffers array with biggest difference to bbox and adjusts affine transform matrix. Can be used to fill
@@ -132,20 +141,21 @@ class Image:
 
         self.__arr = destination
 
-        return self.__update_dataset()
+        return self.__update_dataset(self.dataset.meta)
 
-    def __update_dataset(self):
+    def __update_dataset(self, meta):
         """Update dataset without writing to file after it theoretically changed.
 
+        :param meta: The basic metadata of the dataset as returned from the meta property of rasterio Datasets
         :return: closed dataset in memory
         """
-        mem_profile = self.dataset.meta
-        mem_profile.update(
+
+        meta.update(
             {"height": self.__arr.shape[-2], "width": self.__arr.shape[-1], "transform": self.transform,}
         )
 
         memfile = MemoryFile()
-        ds = memfile.open(**mem_profile)
+        ds = memfile.open(**meta)
         ds.write(self.__arr)
 
         return memfile
@@ -193,7 +203,7 @@ class Image:
         self.__arr = destination
         self.transform = transform
         self.crs = dst_crs
-        self.dataset = self.__update_dataset().open()
+        self.dataset = self.__update_dataset(self.dataset.meta).open()
 
     def dn2toa(self, platform, mtl_file=None, wavelengths=None):
         """This method converts digital numbers to top of atmosphere reflectance, like described here:
@@ -373,7 +383,7 @@ class Image:
         :param nodata: nodata value, e.g. 255 (default: None, means nodata value of dataset will be used)
         :param compress: compression, e.g. 'lzw' (default: None)
         """
-        if dtype == 'min':
+        if dtype == "min":
             dtype = get_minimum_dtype(self.__arr)
 
         profile = self.dataset.meta
@@ -400,7 +410,3 @@ class Image:
     def close(self):
         """closes Image"""
         self.dataset.close()
-
-
-if __name__ == "__main__":
-    pass


Test patch:
diff --git a/tests/test_data.py b/tests/test_data.py
index 4a5fca2..37bdf3b 100644
--- a/tests/test_data.py
+++ b/tests/test_data.py
@@ -2,8 +2,8 @@ import os
 import traceback
 import unittest
 
-from ukis_pysat.members import Datahub, Platform
 from ukis_pysat.data import Source
+from ukis_pysat.members import Datahub, Platform
 
 # os.environ["EARTHEXPLORER_USER"] = "Tim"
 # os.environ["EARTHEXPLORER_PW"] = "TheEnchanter"
@@ -121,6 +121,7 @@ class DownloadTest(unittest.TestCase):
         with self.assertRaises(TypeError, msg=f"aoi must be of type string or tuple"):
             src.prep_aoi(1)
 
+    @unittest.skip("until API is reachable again")
     # @unittest.skip("uncomment when you set ENVs with credentials")
     def test_query_metadata(self):
         for i in range(len(queries)):
@@ -144,9 +145,12 @@ class DownloadTest(unittest.TestCase):
     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,)
+            src.download_image(
+                platform=Datahub.File, product_uuid="1", target_dir=target_dir,
+            )
         # TODO download tests
 
+    @unittest.skip("until API is reachable again")
     # @unittest.skip("uncomment when you set ENVs with credentials")
     def test_download_quicklook(self):
         for i in range(len(queries)):
@@ -171,7 +175,9 @@ class DownloadTest(unittest.TestCase):
 
         src = Source(datahub=Datahub.File, datadir=target_dir)
         with self.assertRaises(NotImplementedError, msg=f"download_quicklook not supported for Datahub.File."):
-            src.download_quicklook(platform=Datahub.File, product_uuid="1", target_dir=target_dir,)
+            src.download_quicklook(
+                platform=Datahub.File, product_uuid="1", target_dir=target_dir,
+            )
 
 
 if __name__ == "__main__":
diff --git a/tests/test_raster.py b/tests/test_raster.py
index 7e7cc02..ea32ae0 100644
--- a/tests/test_raster.py
+++ b/tests/test_raster.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python3
 import os
 import unittest
 
@@ -7,32 +8,55 @@ from rasterio import windows
 from rasterio.coords import BoundingBox
 from rasterio.transform import from_bounds
 from shapely.geometry import box
-
 from ukis_pysat.members import Platform
 from ukis_pysat.raster import Image
 
-img = Image(path=os.path.join(os.path.dirname(__file__), "testfiles", "dummy.tif"))
+TEST_FILE = os.path.join(os.path.dirname(__file__), "testfiles", "dummy.tif")
 
 
 class DataTest(unittest.TestCase):
+    def setUp(self):
+        self.img = Image(TEST_FILE)
+
+    def tearDown(self):
+        self.img.close()
+
     def test_init(self):
-        with self.assertRaises(TypeError, msg=f"path must be of type str"):
-            Image(path=1)
+        img = Image(self.img.dataset)
+        self.assertTrue(np.array_equal(self.img.arr, img.arr))
+        img.close()
 
-        with self.assertRaises(
-            TypeError, msg=f"dataset must be of type rasterio.io.DatasetReader and arr must be of type numpy.ndarray"
-        ):
-            Image(dataset=1, arr=img.arr)
+    def test_init_fail_invalid_path(self):
+        with self.assertRaises(TypeError):
+            Image(1)
 
-        with self.assertRaises(
-            TypeError, msg=f"dataset must be of type rasterio.io.DatasetReader and arr must be of type numpy.ndarray"
-        ):
-            Image(arr=img.arr)
+    def test_init_fail_invalid_dataset(self):
+        with self.assertRaises(TypeError,):
+            Image(1)
 
-        self.assertTrue(np.array_equal(img.arr, Image(dataset=img.dataset, arr=img.arr).arr))
+    def test_init_with_arry_fail_missing_crs_and_transform(self):
+        with self.assertRaises(TypeError):
+            Image(data=self.img.arr)
+
+    def test_init_with_arry_fail_missing_transform(self):
+        with self.assertRaises(TypeError):
+            Image(data=self.img.arr, crs=self.img.crs)
+
+    def test_init_with_array(self):
+        img = Image(self.img.arr, crs=self.img.crs, transform=self.img.transform)
+        self.assertTrue(np.array_equal(self.img.arr, img.arr))
+        img.close()
+
+    def test_init_with_arry_fail_missing_crs(self):
+        with self.assertRaises(TypeError):
+            Image(data=self.img.arr, crs=self.img.transform)
+
+    def test_dimorder_error(self):
+        with self.assertRaises(TypeError):
+            img_first = Image(TEST_FILE, dimorder="middle")
 
     def test_arr(self):
-        img_first = Image(path=os.path.join(os.path.dirname(__file__), "testfiles", "dummy.tif"), dimorder="first")
+        img_first = Image(TEST_FILE, dimorder="first")
         img_first.mask_image(box(11.9027457562112939, 51.4664152338322580, 11.9477435281016131, 51.5009522690838750,))
         self.assertEqual(img_first.arr.shape, (1, 385, 502))
         self.assertEqual(
@@ -48,8 +72,9 @@ class DataTest(unittest.TestCase):
                 )
             ),
         )
+        img_first.close()
 
-        img_last = Image(path=os.path.join(os.path.dirname(__file__), "testfiles", "dummy.tif"), dimorder="last")
+        img_last = Image(TEST_FILE, dimorder="last")
         img_last.mask_image(box(11.9027457562112939, 51.4664152338322580, 11.9477435281016131, 51.5009522690838750,))
         self.assertEqual(img_last.arr.shape, (385, 502, 1))
         self.assertEqual(
@@ -65,64 +90,62 @@ class DataTest(unittest.TestCase):
                 )
             ),
         )
+        img_last.close()
 
-        img = Image(path=os.path.join(os.path.dirname(__file__), "testfiles", "dummy.tif"), dimorder="first")
-        img_first = Image(dataset=img.dataset, arr=np.ones((1, 385, 502)), dimorder="first")
+        img_first = Image(np.ones((1, 385, 502)), dimorder="first", crs=self.img.crs, transform=self.img.transform)
         self.assertEqual(img_first.arr.shape, (1, 385, 502))
+        img_first.close()
 
-        img_last = Image(dataset=img.dataset, arr=np.ones((385, 502, 1)), dimorder="last")
+        img_last = Image(np.ones((1, 385, 502)), dimorder="last", crs=self.img.crs, transform=self.img.transform)
         self.assertEqual(img_last.arr.shape, (385, 502, 1))
-
-        with self.assertRaises(AttributeError, msg="dimorder for bands or channels must be either 'first' or 'last'."):
-            img_first = Image(path=os.path.join(os.path.dirname(__file__), "testfiles", "dummy.tif"), dimorder="middle")
-            img_first.arr
+        img_last.close()
 
     def test_get_valid_data_bbox(self):
         self.assertEqual(
-            img.get_valid_data_bbox(), (11.896863892, 51.515176657, 11.896863892, 51.515176657),
+            self.img.get_valid_data_bbox(), (11.896863892, 51.515176657, 11.896863892, 51.515176657),
         )
         self.assertEqual(
-            img.get_valid_data_bbox(nodata=1), (11.896863892, 51.446545369, 11.9578595, 51.515176657),
+            self.img.get_valid_data_bbox(nodata=1), (11.896863892, 51.446545369, 11.9578595, 51.515176657),
         )
 
     def test_mask_image(self):
         with self.assertRaises(TypeError, msg="bbox must be of type tuple or Shapely Polygon"):
-            img.mask_image([1, 2, 3])
+            self.img.mask_image([1, 2, 3])
 
-        img.mask_image(box(11.9027457562112939, 51.4664152338322580, 11.9477435281016131, 51.5009522690838750,))
+        self.img.mask_image(box(11.9027457562112939, 51.4664152338322580, 11.9477435281016131, 51.5009522690838750,))
         self.assertEqual(
-            img.dataset.bounds,
+            self.img.dataset.bounds,
             BoundingBox(
                 left=11.902702941366716, bottom=51.46639813686387, right=11.947798368783504, top=51.50098327545026,
             ),
         )
 
-        img.mask_image((11.9027457562112939, 51.4664152338322580, 11.9477435281016131, 51.5009522690838750,))
+        self.img.mask_image((11.9027457562112939, 51.4664152338322580, 11.9477435281016131, 51.5009522690838750,))
         self.assertEqual(
-            img.dataset.bounds,
+            self.img.dataset.bounds,
             BoundingBox(
                 left=11.902702941366716, bottom=51.46639813686387, right=11.947798368783504, top=51.50098327545026,
             ),
         )
 
-        img.mask_image(
+        self.img.mask_image(
             box(11.8919236802142620, 51.4664152338322580, 11.9477435281016131, 51.5009522690838750,), pad=True,
         )
         self.assertEqual(
-            img.dataset.bounds,
+            self.img.dataset.bounds,
             BoundingBox(
                 left=11.891923157920472, bottom=51.46639813686387, right=11.947798368783504, top=51.50098327545026
             ),
         )
 
     def test_warp(self):
-        self.assertEqual(img.crs, "EPSG:4326")
+        self.assertEqual(self.img.crs, "EPSG:4326")
 
-        img.warp("EPSG:3857")
-        self.assertEqual(img.crs, "EPSG:3857")
+        self.img.warp("EPSG:3857")
+        self.assertEqual(self.img.crs, "EPSG:3857")
 
-        img.warp("EPSG:4326", resolution=1.0)
-        self.assertEqual(1.0, img.transform.to_gdal()[1])
+        self.img.warp("EPSG:4326", resolution=1.0)
+        self.assertEqual(1.0, self.img.transform.to_gdal()[1])
 
     def test_dn2toa(self):
         target_dir = os.path.join(os.path.dirname(__file__), "testfiles", "satellite_data")
@@ -160,33 +183,33 @@ class DataTest(unittest.TestCase):
         ]
 
         for i in range(len(tests)):
-            img_dn = Image(path=tests[i]["dn_file"])
-            img_toa = Image(path=tests[i]["toa_file"])
+            img_dn = Image(tests[i]["dn_file"])
+            img_toa = Image(tests[i]["toa_file"])
             img_dn.dn2toa(
                 platform=tests[i]["platform"], mtl_file=tests[i]["mtl_file"], wavelengths=tests[i]["wavelengths"]
             )
             self.assertTrue(np.array_equal(img_dn.arr, img_toa.arr))
 
         with self.assertRaises(AttributeError, msg=f"'mtl_file' has to be set if platform is {Platform.Landsat8}."):
-            img.dn2toa(platform=Platform.Landsat8)
+            self.img.dn2toa(platform=Platform.Landsat8)
 
         with self.assertRaises(
             AttributeError,
             msg=f"Cannot convert dn2toa. Platform {Platform.Sentinel1} not "
             f"supported [Landsat-5, Landsat-7, Landsat-8, Sentinel-2]. ",
         ):
-            img.dn2toa(platform=Platform.Sentinel1)
+            self.img.dn2toa(platform=Platform.Sentinel1)
 
     def test__lookup_bands(self):
         self.assertEqual(
-            ["1", "2", "3"], img._lookup_bands(Platform.Landsat5, ["Blue", "Green", "Red"]),
+            ["1", "2", "3"], self.img._lookup_bands(Platform.Landsat5, ["Blue", "Green", "Red"]),
         )
         self.assertEqual(
-            ["8", "10", "11"], img._lookup_bands(Platform.Landsat8, ["PAN", "Tirs1", "Tirs2"]),
+            ["8", "10", "11"], self.img._lookup_bands(Platform.Landsat8, ["PAN", "Tirs1", "Tirs2"]),
         )
 
     def test_get_tiles(self):
-        for idx, each in enumerate(img.get_tiles(5, 5, 1)):
+        for idx, each in enumerate(self.img.get_tiles(5, 5, 1)):
             self.assertIsInstance(each, windows.Window)
             if idx == 2578:
                 self.assertEqual(each, windows.Window(col_off=79, row_off=649, width=7, height=7))
@@ -194,24 +217,24 @@ class DataTest(unittest.TestCase):
         self.assertEqual(idx, 20807)
 
     def test_get_subset(self):
-        for idx, each in enumerate(img.get_tiles(5, 5, 1)):
+        for idx, each in enumerate(self.img.get_tiles(5, 5, 1)):
             if idx == 2578:
-                array, bounds = img.get_subset(each)
+                array, bounds = self.img.get_subset(each)
                 self.assertTrue(np.array_equal(array, np.zeros(shape=(7, 7), dtype=array.dtype)))
                 self.assertEqual(bounds, (11.903960582768779, 51.45624717410995, 11.904589403469808, 51.45687599481152))
 
     def test_get_dask_array(self):
-        self.assertIsInstance(img.to_dask_array(chunk_size=(1, 10, 10)), dask.array.core.Array)
+        self.assertIsInstance(self.img.to_dask_array(chunk_size=(1, 10, 10)), dask.array.core.Array)
 
     def test_write_to_file(self):
-        img.write_to_file(r"result.tif", np.uint16)
+        self.img.write_to_file(r"result.tif", np.uint16)
         img2 = Image("result.tif")
-        self.assertTrue(np.array_equal(img2.arr, img.arr))
+        self.assertTrue(np.array_equal(img2.arr, self.img.arr))
 
         img2.close()
         os.remove(r"result.tif")
 
-        img.write_to_file(r"result.tif", "min", compress="lzw")
+        self.img.write_to_file(r"result.tif", "min", compress="lzw")
         img2 = Image("result.tif")
         self.assertEqual(img2.arr.dtype, "uint8")
         self.assertEqual(img2.dataset.profile["compress"], "lzw")
@@ -219,6 +242,14 @@ class DataTest(unittest.TestCase):
         img2.close()
         os.remove(r"result.tif")
 
+        img3 = Image(self.img.arr, crs=self.img.crs, transform=self.img.transform)
+        img3.write_to_file(r"result.tif", np.uint16)
+        img3.close()
+        img4 = Image("result.tif")
+        self.assertTrue(np.array_equal(img4.arr, self.img.arr))
+        img4.close()
+        os.remove(r"result.tif")
+
 
 if __name__ == "__main__":
     unittest.main()