test_geoarray.py 35.8 KB
Newer Older
1 2
#!/usr/bin/env python
# -*- coding: utf-8 -*-
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

# geoarray, A fast Python interface for image geodata - either on disk or in memory.
#
# Copyright (C) 2019  Daniel Scheffler (GFZ Potsdam, daniel.scheffler@gfz-potsdam.de)
#
# This software was developed within the context of the GeoMultiSens project funded
# by the German Federal Ministry of Education and Research
# (project grant code: 01 IS 14 010 A-C).
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.

25 26
"""
test_geoarray
27
-------------
28

29 30 31 32
Tests for the functions of the "GeoArray"-class in the "geoarray" module.
For this purpose the TIFF-file "L8_2bands_extract10x11.tif" and the array of
the TIFF-image "L8_2bands_extract10x11_array.txt" in the directory "../tests/data"
is used. The outputs of the "GeoArray"-class functions are tested against the well
33 34
known properties of the test-image.

35
The tests are divided into a total of two test cases. The order of execution is as
36
follows:
37 38
test case 1 (path) - test case 2 (path) - test case 1 (array) - test case 2 (array).

39 40
Note that the tests in the test case "Test_GeoarrayAppliedOnPathArray" and
"Test_GeoarrayFunctions" follow - with a few exceptions - the same order as in the
41
"GeoArray"-class (but they are executed in alphanumeric order inside the test case).
42
Functions that depend on each other are tested together..
43
"""
44
from __future__ import print_function
45
from collections import OrderedDict
46
import dill
47
import numpy as np
48 49
import os
from os import path
50
import osgeo.osr
51
from shapely.geometry import Polygon
52
import time
53 54
import unittest
from unittest import TestLoader
55
import matplotlib
Daniel Scheffler's avatar
Bugfix  
Daniel Scheffler committed
56
from typing import Iterable
57
from pkgutil import find_loader
58
import tempfile
59

60
# Imports regarding the created python module.
61
from py_tools_ds.geo.vector import geometry
62
from py_tools_ds.compatibility.python.os import makedirs
63
matplotlib.use('Template')  # disables matplotlib figure popups
64
from geoarray import GeoArray, masks, __file__  # noqa E402 module level import not at top of file
Daniel Scheffler's avatar
Daniel Scheffler committed
65
from geoarray.metadata import GDAL_Metadata  # noqa E402 module level import not at top of file
66

67
__author__ = 'Jessica Palka'
68

69
# Path of the tests-directory in the geoarray-package.
70
tests_path = os.path.abspath(path.join(__file__, "..", ".."))
71

72

73
############################################
74
# Test case: Test_GeoarrayAppliedOnPathArray
75
############################################
76 77 78

class Test_GeoarrayAppliedOnPathArray(unittest.TestCase):
    """
79
    The class "Test_GeoarrayAppliedOnPathArray" tests the basic functions of the
80 81
    "GeoArray"-class from which the other functions depend.
    Note that the function set_gdalDataset_meta is tested indirectly by a
82
    couple of tests in the test case (a notation is applied).
83 84

    Since the "GeoArray"-class can be instanced with a file path or with a numpy
85 86
    array and the corresponding geoinformation, the tests in this test case will
    be executed two times in a row (the test case is parametrized). The order is
87
    as follows: In the first/second round the tests will be executed using the
88 89 90 91 92
    "GeoArray"-instance created with a file path/numpy array.
    """

    # Expected results concerning the used TIFF-image.
    expected_bandnames = OrderedDict([('B1', 0), ('B2', 1)])
93 94
    (R_exp, C_exp, B_exp) = expected_shape = (10, 11, 2)
    expected_result = (3, R_exp, C_exp, B_exp)  # dimensions, rows, columns, bands
95
    expected_dtype = np.dtype('float32')
96
    given_geotransform = [365985.0, 30.0, 0.0, 5916615.0, 0.0, -30.0]
97 98 99 100 101
    expected_resolution = (30, 30)
    expected_grid = [[365985.0, 366015.0], [5916615.0, 5916585.0]]
    given_pszProj4_string = '+proj=utm +zone=33 +datum=WGS84 +units=m +no_defs'
    expected_epsg = 32633
    given_nodata = -9999.0
102
    given_bandnames = ['B1', 'B2']
103 104 105 106 107 108 109 110 111

    # Variable for the parametrization of the test case.
    k = 0

    @classmethod
    def setUpClass(cls):
        # First line of the test case output to ease the distinction between the test cases using different
        # instances of the "GeoArray"-class.
        print(' ')
Daniel Scheffler's avatar
Daniel Scheffler committed
112
        print("Geoarray instanced with {i}, TEST CASE 1 ('basic functions'):".format(i=('PATH', 'NUMPY ARRAY')[cls.k]))
113 114 115 116

        # Creating the instances of the "GeoArray"-class.
        if cls.k == 0:
            # Creating the "GeoArray"-instance with a FILE PATH.
117
            cls.L8_2bands_extract10x11 = os.path.join(tests_path, "tests", "data", "L8_2bands_extract10x11.tif")
118 119
            cls.testtiff = GeoArray(cls.L8_2bands_extract10x11)

120
            # Serialize the "GeoArray"-class to the "../tests/data/output"-directory
121
            serialized_testtiff_path = os.path.join(tests_path, "tests", "data", "output", "testtiff_path.tmp")
122
            makedirs(os.path.dirname(serialized_testtiff_path), exist_ok=True)
123
            with open(serialized_testtiff_path, "wb") as f:
124 125
                dill.dump(cls.testtiff, f)

126 127
        if cls.k == 1:
            # Loading the TIFF-image array from the "data"-directory.
128
            array_path = os.path.join(tests_path, "tests", "data", "L8_2bands_extract10x11_array.txt")
129 130 131
            cls.L8_2bands_extract10x11 = np.loadtxt(array_path, 'float32').reshape(10, 11, 2)

            # Change the pszProj4-string in a WKT-string.
132
            srs = osgeo.osr.SpatialReference()
133 134 135 136
            srs.ImportFromProj4(cls.given_pszProj4_string)
            cls.given_projection = srs.ExportToWkt()

            # Creating the "GeoArray"-instance with a NUMPY ARRAY.
137
            # The notdata-value will be set in the test "test_NoDataValueOfTiff".
138
            cls.testtiff = GeoArray(cls.L8_2bands_extract10x11, geotransform=cls.given_geotransform,
139
                                    projection=cls.given_projection, bandnames=cls.given_bandnames)
140

141
            # Serialize the "GeoArray"-class to the "../tests/data/output"-directory
142
            serialized_testtiff_array = os.path.join(tests_path, "tests", "data", "output", "testtiff_array.tmp")
143
            makedirs(os.path.dirname(serialized_testtiff_array))
144
            with open(serialized_testtiff_array, "wb") as f:
145 146
                dill.dump(cls.testtiff, f)

147 148
        cls.TiffIsInstanceOfGeoarray(cls)
        cls.ArrOfTiffIsInMemory(cls)
149 150 151 152

    def TiffIsInstanceOfGeoarray(self):
        """
        Indirect test.
153
        Testing, whether the object "testtiff" is an instance of the class "GeoArray" or not. If an exception is
154
        raised for an error, all tests of the test case "Test_GeoarrayAppliedOnTiffPath" will be skipped.
155 156 157 158 159
        """

        try:
            assert isinstance(self.testtiff, GeoArray)
        except AssertionError:
160
            self.skipTest(Test_GeoarrayAppliedOnPathArray,
161 162 163 164 165 166
                          reason="The created object 'testtiff' is not an instance of class 'GeoArray'. "
                                 "All tests of the Test Case 'Test_GeoarrayAppliedOnTiffPath' will be skipped!")

    def ArrOfTiffIsInMemory(self):
        """
        Testing the functions: arr - is_inmem, indirect test with 2 stages.
167
        Stage 1: Checking, if the argument passed to the "GeoArray"-class is a file path or a numpy.ndarray.
168 169
        Stage 2: It is tested, if the arr- and is_inmem-function give the expected output.
        If, for any possibility that was tested, an exception is raised for an error, all tests of the test case
170 171 172 173 174
        "Test_GeoarrayAppliedOnTiffPath" will be skipped.
        """

        testtiff_basicfunctions = (self.testtiff.arr, self.testtiff.is_inmem)
        expected_conditions = ((None, False), (True, True))
175
        equal_arr_L8 = np.array_equal(testtiff_basicfunctions[0], self.L8_2bands_extract10x11)
176

177 178
        # FILE PATH
        if isinstance(self.L8_2bands_extract10x11, str) and os.path.isfile(self.L8_2bands_extract10x11):
179 180
            try:
                assert (testtiff_basicfunctions[0] == expected_conditions[0][0]) and \
181
                       (testtiff_basicfunctions[1] == expected_conditions[0][1])
182
            except AssertionError:
183 184 185 186
                self.skipTest(Test_GeoarrayAppliedOnPathArray,
                              reason="A path is passed to 'GeoArray'. But the output of the functions "
                                     "arr and is_inmem %s do not match as expected %s!"
                                     % (testtiff_basicfunctions, expected_conditions[0]))
187

188 189
        # NUMPY ARRAY
        elif isinstance(self.L8_2bands_extract10x11, np.ndarray):
190 191
            try:
                assert (isinstance(testtiff_basicfunctions[0], np.ndarray) == expected_conditions[1][0]) and \
192
                       equal_arr_L8 and \
193
                       (testtiff_basicfunctions[1] == expected_conditions[1][1])
194 195
            except (AssertionError, AttributeError):
                self.skipTest(Test_GeoarrayAppliedOnPathArray,
196
                              reason="A numpy.ndarray is passed to 'GeoArray'. But the output of the functions "
197
                                     "arr and is_inmem do not match the expected output "
198
                                     "(arr == given array? %s, is_inmem: %s/%s)!"
199
                                     % (equal_arr_L8,
200
                                        testtiff_basicfunctions[1], expected_conditions[1][1]))
201 202

        else:
203
            self.skipTest(Test_GeoarrayAppliedOnPathArray,
204 205 206
                          reason="The variable committed to the 'GeoArray'-class is neither a path nor a numpy.ndarray."
                                 "All tests of the Test Case 'Test_GeoarrayAppliedOnTiffPath' will be skipped!")

207
    def test_bandnames(self):
208
        """
209
        Testing the function: bandnames.
210
        Test, if the default (for k=0) and set (for k=1) band names respectively were correctly assigned.
211
        """
212
        self.assertEqual(self.testtiff.bandnames, self.expected_bandnames,
213 214
                         msg="The bandnames of the Tiff-file are different than ['B1', 'B2'] (format: OrderedDict)")

215
    def test_shape(self):
216
        """
217 218
        Testing the functions: shape - ndim - rows - columns - bands,
        indirect testing of the function: set_gdalDataset_meta(!),
219
        test with 2 stages.
220 221 222
        Stage 1: Comparing the shape of the testtiff-image with the output of the shape-function.
                    When identical, induction of stage 2...
        Stage 2: Comparing the output of the ndim-, rows-, columns- and bands-function with the expected results.
223
        If the the shape of the image is not as expected (Stage 1), the whole test will be skipped.
224 225 226 227 228
        """

        testtiff_shapefunctions = (self.testtiff.ndim, self.testtiff.rows, self.testtiff.columns, self.testtiff.bands)
        shape_property = ('DIMENSIONS', 'ROWS', 'COLUMNS', 'BANDS')

229
        if self.testtiff.shape == self.expected_shape:
230 231
            for i in range(0, 4, 1):
                with self.subTest(i=i):
232
                    self.assertEqual(testtiff_shapefunctions[i], self.expected_result[i],
233 234
                                     msg='The number of {i} is different from the expected result!'.format(
                                         i=shape_property[i]))
235 236

        else:
237 238
            self.skipTest("The shape of the array behind the 'Geoarray'-object is not as expected! "
                          "The test 'test_ShapeOfTiffArray' will be skipped.")
239

240
    def test_dtype(self):
241
        """
242
        Testing the function: dtype,
243
        indirect testing of the function: set_gdalDataset_meta(!).
244 245 246
        Test, if the data type of the .ndarray behind the "GeoArray"-class was correctly assigned.
        """

247
        self.assertEqual(self.testtiff.dtype, self.expected_dtype,
248 249
                         msg='The dtype of the corresponding array is not as expected!')

250
    def test_geotransform(self):
251
        """
252 253
        Testing the functions: geotransform - xgsd - ygsd - xygrid_specs,
        indirect testing of the function: set_gdalDataset_meta(!),
254
        test with 3 stages.
255
        Stage 1: Comparing the geotransform-tupel of the geotransform-function with the expected result.
256
                    When identical, induction of stage 2...
257
        Stage 2: Comparing the resolution from the geotransform-tupel with the expected resolution.
258
                    When identical, induction of stage 3...
259
        Stage 3: Comparing the x/y coordinate grid by the xygrid_specs-function with the expected result.
260 261 262 263 264
        If an exception is raised as an error in stage 1 or 2, the test will be skipped.
        """

        testtiff_resolutionfunctions = (self.testtiff.xgsd, self.testtiff.ygsd)

265 266 267
        if self.testtiff.geotransform == self.given_geotransform:
            if testtiff_resolutionfunctions == self.expected_resolution:
                self.assertEqual(self.testtiff.xygrid_specs, self.expected_grid,
268 269 270
                                 msg='The [[xOrigin, xGSD], [yOrigin, yGSD]]-grid is not as expected!')

            else:
271 272
                self.skipTest("The x/y-resolution %s of the grid of the tested Tiff-file is not as expected %s! "
                              "The function 'XYGRID_SPECS' will not be tested."
273
                              % (testtiff_resolutionfunctions, self.expected_resolution))
274
        else:
275 276
            self.skipTest("The geotransform-tuple of the array behind the 'GeoArray'-object is not as expected! "
                          "The test 'test_GeotransformTiff' will be skipped.")
277

278
    def test_projection(self):
279
        """
280 281
        Testing the functions: projection - epsg,
        indirect testing of the function: set_gdalDataset_meta(!),
282
        test with 2 stages.
283 284 285
        Stage 1: After translating the projection-string provided by the projection-function to a pszProj4-string,
                    it is compared to the expected pszProj4-string. When identical, induction of stage 2...
        Stage 2: Comparing the EPSG-code provided by the epsg-function with the expected EPSG-code.
286
        If the pszProj4-string is not as expected (Stage 1), the whole test will be skipped.
287 288 289 290
        """

        # Convert WKT-string of the projection to a pszProj4_string
        # Code adapted from source:
291 292 293
        # mgleahy, 21 November 2010, "SpatialNotes". [Online].
        # URL: http://spatialnotes.blogspot.de/2010/11/converting-wkt-projection-info-to-proj4.html
        # [Accessed 23 Mai 2017].
294
        srs = osgeo.osr.SpatialReference()
295 296 297
        srs.ImportFromWkt(self.testtiff.projection)
        testtiff_pszProj4_string = srs.ExportToProj4()

298 299
        if testtiff_pszProj4_string.strip(' /t/n/r') == self.given_pszProj4_string:
            self.assertEqual(self.testtiff.epsg, self.expected_epsg,
300
                             msg="The EPSG-code returned by the 'GeoArray' epsg-function (%s) is not "
301
                                 "equal to the expected code (%s)." % (self.testtiff.epsg, self.expected_epsg))
302 303

        else:
304 305
            self.skipTest("The projections of the 'GeoArray'-object is not as expected! "
                          "The test 'test_ProjectionTiff' will be skipped.")
306

307
    def test_nodata(self):
308
        """
309
        Testing the function: nodata,
310
        indirect testing of the functions: find_noDataVal(), set_gdalDataset_meta(!).
311
        Test, if the set/default nodata value of the GeoArray-instance was correctly assigned.
312
        """
313

314
        if self.k == 1:
315 316
            self.assertIsNone(self.testtiff.nodata,
                              msg="The nodata-value of the 'GeoArray'-object instanced with a numpy.array and without "
317 318
                                  "declaring a nodata-value is not automatically set to 'None'! "
                                  "The remaining assertion in the test 'test_NoDataValueOfTiff' will not be executed.")
319
            self.testtiff.nodata = self.given_nodata
320

321
        self.assertEqual(self.testtiff.nodata, self.given_nodata,
322
                         msg="The nodata-value of the tested Tiff-file (%s) is not as expected (%s)!"
323
                             % (self.testtiff.nodata, self.given_nodata))
324

325 326 327 328 329 330 331 332
    def test___getitem__(self):
        def validate(array, exp_shape):
            self.assertIsInstance(array, np.ndarray)
            self.assertEqual(array.shape, exp_shape)

        R, C, B = self.testtiff.shape  # (10, 11, 2)

        # test row/col subset
333 334
        validate(self.testtiff[:1, :3, :], (1, 3, B))  # only one row is requested, given as a slice
        validate(self.testtiff[0, :3, :], (3, B))  # only one row is requested, given as an int
335 336 337 338
        validate(self.testtiff[2:5, :3], (3, 3, B))  # third dimension is not given
        validate(self.testtiff[2:5, :3, :], (3, 3, B))

        # test band subset
339 340 341
        validate(self.testtiff[:, :, 0:1], (R, C, 1))  # band slice  # returns 3D array
        validate(self.testtiff[:, :, 0], (R, C))  # band indexing  # returns 2D array
        validate(self.testtiff[1], (R, C))  # only band is given  # returns 2D
342 343 344 345 346
        validate(self.testtiff['B1'], (R, C))  # only bandname is given

        # test wrong inputs
        self.assertRaises(ValueError, self.testtiff.__getitem__, 'B01')

347 348 349
        # test full array  # NOTE: This sets self.testtiff.arr!
        validate(self.testtiff[:], (R, C, B))

350 351
        # TODO: add further tests

352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
    def test___getitem__consistency(self):
        testarr = np.zeros((2, 2, 2))
        testarr[0, :, :] = [[11, 12], [13, 14]]
        testarr[1, :, :] = [[21, 22], [23, 24]]

        gA_inmem = GeoArray(testarr)
        inmem_res = gA_inmem[0, :, :]

        with tempfile.TemporaryDirectory() as tf:
            gA_inmem.save(os.path.join(tf, 'test.bsq'))

            gA_notinmem = GeoArray(os.path.join(tf, 'test.bsq'))
            notinmem_res = gA_notinmem[0, :, :]

        self.assertEqual(inmem_res.ndim, notinmem_res.ndim)
        self.assertEqual(inmem_res.shape, notinmem_res.shape)

Daniel Scheffler's avatar
Bugfix.  
Daniel Scheffler committed
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
    def test___getitem__consistency_3d_array_1_column(self):
        testarr = np.array([[1, 2], [3, 4]]).reshape(2, 1, 2)

        gA_inmem = GeoArray(testarr)
        inmem_res = gA_inmem[:]

        with tempfile.TemporaryDirectory() as tf:
            gA_inmem.save(os.path.join(tf, 'test.bsq'))

            gA_notinmem = GeoArray(os.path.join(tf, 'test.bsq'))
            notinmem_res = gA_notinmem[:]

        self.assertEqual(inmem_res.ndim, notinmem_res.ndim)
        self.assertEqual(inmem_res.shape, notinmem_res.shape)

384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
    def test___getitem__consistency_2d_array(self):
        testarr = np.zeros((2, 2))
        testarr[:, :] = [[11, 12], [13, 14]]

        gA_inmem = GeoArray(testarr)
        inmem_res = gA_inmem[0, 0]

        with tempfile.TemporaryDirectory() as tf:
            gA_inmem.save(os.path.join(tf, 'test.bsq'))

            gA_notinmem = GeoArray(os.path.join(tf, 'test.bsq'))
            notinmem_res = gA_notinmem[0, 0]

        self.assertEqual(inmem_res.ndim, notinmem_res.ndim)
        self.assertEqual(inmem_res.shape, notinmem_res.shape)

400 401 402 403 404
    def test_numpy_array(self):
        arr = np.array(self.testtiff)
        self.assertIsInstance(arr, np.ndarray)
        self.assertEqual(arr.shape, self.testtiff.shape)

405

406
###################################
407
# Test case: Test_GeoarrayFunctions
408
###################################
409 410 411

class Test_GeoarrayFunctions(unittest.TestCase):
    """
412 413 414 415 416 417 418 419 420 421 422 423
    The class "Test_GeoarrayFunctions" is the second test case of the
    "test_geoarray"-script and tests the functions of the "GeoArray"-class that are
    not yet tested in the first test case. Since the basic functions on which most
    functions of the "GeoArray"-class depend on were already tested in test case 1,
    the tests of test case 2 can be considered moderately independent from these
    functions. Note that if an error, failure or skip occurs in test case 1, test
    case 2 will not be executed. If test case 1 was successful, test case 2 will be
    executed twice - like test case 1 test case 2 is parametrized. The order of
    execution is as follows:
    After the first test case is executed using the "GeoArray"-instance created
    with a file path, the second test case is executed using the same instance.
    The second execution of the test cases uses the "GeoArray"-instance created
424 425
    with a numpy array.
    """
426
    # TODO: Complete the test case!
427 428 429 430 431 432 433 434 435 436
    # Variable for the parametrization of the test case (the same variable as in test case 1).
    k = 0

    @classmethod
    def setUpClass(cls):

        # Adaption of the source code from the setUpClass of test case 1 to ease the distinction between
        # the test cases using different instances of the "GeoArray"-class.
        # First line of the test case output.
        print(' ')
Daniel Scheffler's avatar
Daniel Scheffler committed
437
        print("Geoarray instanced with {i}, TEST CASE 2 ('functions'):".format(i=('PATH', 'NUMPY ARRAY')[cls.k]))
438 439 440

        # Opening the temporary serialized variables (see setUpClass of test case 1) to re-use in the new test case
        # without the need to inherit the variables from test case 1.
Daniel Scheffler's avatar
Daniel Scheffler committed
441 442
        assert cls.k in [0, 1]
        fN = "testtiff_path.tmp" if cls.k == 0 else "testtiff_array.tmp"
443

Daniel Scheffler's avatar
Daniel Scheffler committed
444 445
        with open(os.path.join(tests_path, "tests", "data", "output", fN), "rb") as f:
            cls.testtiff = dill.load(f)
446 447 448

    @classmethod
    def tearDownClass(cls):
449 450
        # Removing the "../tests/data/output"-directory with all files. If test case 2 is not executed, the files
        # will be removed at the end of this script in the "if __name__ == '__main__'"-code segment.
451
        out_dir = os.path.join(tests_path, "tests", "data", "output")
452

453 454 455
        for file in os.listdir(out_dir):
            os.remove(path.join(out_dir, file))
        os.rmdir(out_dir)
456

457
    def test_box(self):
458 459 460 461 462 463 464
        """
        Testing the function: box.
        Test, if the output of the box-function is an instance boxObj (class, defined in geometry.py, py_tools_ds).
        """

        self.assertIsInstance(self.testtiff.box, geometry.boxObj)

465
    def test_mask_nodata(self):
466 467 468 469 470 471 472 473
        # TODO: Consider the dependency of mask_nodata on the calc_mask_nodata-function.
        """
        Testing the function: mask_nodata.
        Test, if the output of the mask_nodata-function is an instance of "NoDataMask"(class, defined in masks.py).
        """

        self.assertIsInstance(self.testtiff.mask_nodata, masks.NoDataMask)

474
    def test_mask_baddata(self):
475 476
        """
        Testing the function: mask_baddata.
477 478
        Test,
        a) if the output of the mask_baddata-function is "None", when the baddata-mask is not set, and
479
        b) if the output of the mask_baddata-function is an instance of "BadDataMask"(class, defined in masks.py).
480
        """
481
        self.L8_BadDataMask10x11 = os.path.join(tests_path, "tests", "data", "L8_BadDataMask10x11.tif")
482
        bdm = masks.BadDataMask(self.L8_BadDataMask10x11)
483 484 485 486 487 488 489 490

        for i in range(0, 2, 1):
            with self.subTest(i=1):
                if i == 0:
                    self.assertIsNone(self.testtiff.mask_baddata)
                if i == 1:
                    self.testtiff.mask_baddata = bdm
                    self.assertIsInstance(self.testtiff.mask_baddata, masks.BadDataMask)
491

492
    def test_footprint_poly(self):
493 494 495 496 497 498 499
        # TODO: Test the validation of the footprint_poly-function.
        # TODO: Consider the dependencies of the footprint_poly-function on mask_nodata, boxObj.
        """
        Testing the function: footprint_poly.
        Test, if the output of the footprint_poly-function is an instance of shapely.geometry.
        """

500
        self.assertIsInstance(self.testtiff.footprint_poly, Polygon)
501

502
    def test_metadata(self):
503
        # TODO: Create a metadata-file for the tested TIFF-Image.
504
        # TODO: Test, if the metadata-function gives an output
505 506 507 508 509
        """
        Testing the function: metadata.
        Test, if the output of the metadata-function is an instance of GeoDataFrame.
        """

Daniel Scheffler's avatar
Daniel Scheffler committed
510
        self.assertIsInstance(self.testtiff.metadata, GDAL_Metadata)
511

512
    def test_tiles(self):
513
        test_gAs = [self.testtiff,  # 3D
Daniel Scheffler's avatar
Daniel Scheffler committed
514
                    GeoArray(self.testtiff[:, :, 0], geotransform=self.testtiff.gt, projection=self.testtiff.prj)]  # 2D
515

516 517 518 519 520 521
        for gA in test_gAs:
            tiles = gA.tiles(tilesize=(50, 50))
            self.assertIsInstance(tiles, Iterable)

            for ((rS, rE), (cS, cE)), tile in tiles:
                self.assertTrue(np.array_equal(tile, gA[rS: rE + 1, cS: cE + 1]))
522

523
    def test_get_subset_3D_geoarray(self):
524
        # test without resetting band names
525 526 527 528
        sub_gA = self.testtiff.get_subset(xslice=slice(2, 5), yslice=slice(None, 3), zslice=slice(1, 2))
        self.assertIsInstance(sub_gA, GeoArray)
        self.assertTrue(list(sub_gA.bandnames), list(self.testtiff.bandnames)[1])

529 530 531 532 533 534 535 536
        # test with providing only xslice
        sub_gA = self.testtiff.get_subset(xslice=slice(2, 5))
        self.assertIsInstance(sub_gA, GeoArray)

        # test with providing only yslice
        sub_gA = self.testtiff.get_subset(yslice=slice(None, 3))
        self.assertIsInstance(sub_gA, GeoArray)

537 538 539 540
        # test with zslice provided as list
        sub_gA = self.testtiff.get_subset(xslice=slice(2, 5), yslice=slice(None, 3), zslice=[0, 1])
        self.assertIsInstance(sub_gA, GeoArray)

541 542 543 544
        # test without providing zslice
        sub_gA = self.testtiff.get_subset(xslice=slice(2, 5), yslice=slice(None, 3))
        self.assertIsInstance(sub_gA, GeoArray)

545 546 547 548
        # test requesting only one column
        sub_gA = self.testtiff.get_subset(xslice=slice(0, 1), yslice=slice(None, 3))
        self.assertIsInstance(sub_gA, GeoArray)

549
        # test with resetting band names
550 551 552 553
        sub_gA = self.testtiff.get_subset(xslice=slice(2, 5), yslice=slice(None, 3), zslice=slice(1, 2),
                                          reset_bandnames=True)
        self.assertTrue(list(sub_gA.bandnames), ['B1'])

554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571
        # test arrays are equal
        sub_gA = self.testtiff.get_subset(xslice=slice(2, 5), yslice=slice(None, 3), zslice=slice(1, 2))
        sub_testtiff_arr = self.testtiff[:3, 2:5, 1:2]
        self.assertTrue(np.array_equal(sub_gA[:], sub_testtiff_arr))

        # test deepcopied arrays (modification of sub_gA.arr must not affect self.testtiff.arr)
        sub_gA[:2, :2] = 99
        self.assertTrue(np.array_equal(sub_gA[:2, :2], np.full((2, 2, 1), 99, self.testtiff.dtype)))
        self.assertNotEqual(np.mean(sub_testtiff_arr[:2, :2]), 99)
        self.assertNotEqual(np.std(sub_testtiff_arr[:2, :2]), 0)

        # test metadata
        self.assertEqual(sub_gA.meta.bands, 1)
        self.assertEqual(len(list(sub_gA.meta.band_meta.values())[0]), 1)
        self.assertEqual(len(list(sub_gA.bandnames.keys())), 1)
        self.assertNotEqual(sub_gA.gt, self.testtiff.gt)
        self.assertEqual(sub_gA.prj, self.testtiff.prj)

572 573 574
        # test not to return GeoArray
        out = self.testtiff.get_subset(xslice=slice(2, 5), yslice=slice(None, 3), zslice=slice(1, 2),
                                       return_GeoArray=False)
575

576 577 578 579 580 581
        self.assertIsInstance(out, tuple)
        self.assertTrue(len(out), 3)
        self.assertIsInstance(out[0], np.ndarray)
        self.assertIsInstance(out[1], tuple)
        self.assertIsInstance(out[2], str)

582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601
    def test_get_subset_2D_geoarray(self):
        gA_2D = GeoArray(self.testtiff[0])

        # test without resetting band names
        sub_gA = self.testtiff.get_subset(xslice=slice(2, 5), yslice=slice(None, 3))
        self.assertIsInstance(sub_gA, GeoArray)
        self.assertTrue(list(sub_gA.bandnames), list(self.testtiff.bandnames)[1])

        # test with providing only xslice
        sub_gA = gA_2D.get_subset(xslice=slice(2, 5))
        self.assertIsInstance(sub_gA, GeoArray)

        # test with providing only yslice
        sub_gA = gA_2D.get_subset(yslice=slice(None, 3))
        self.assertIsInstance(sub_gA, GeoArray)

        # test without providing zslice
        sub_gA = gA_2D.get_subset(xslice=slice(2, 5), yslice=slice(None, 3))
        self.assertIsInstance(sub_gA, GeoArray)

602 603 604 605
        # test requesting only one column
        sub_gA = self.testtiff.get_subset(xslice=slice(0, 1), yslice=slice(None, 3))
        self.assertIsInstance(sub_gA, GeoArray)

606 607 608 609
        # test with resetting band names
        sub_gA = gA_2D.get_subset(xslice=slice(2, 5), yslice=slice(None, 3), reset_bandnames=True)
        self.assertTrue(list(sub_gA.bandnames), ['B1'])

610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
        # test arrays are equal
        sub_gA = gA_2D.get_subset(xslice=slice(2, 5), yslice=slice(None, 3))
        sub_gA_2D = gA_2D[:3, 2:5]
        self.assertTrue(np.array_equal(sub_gA[:], sub_gA_2D))

        # test deepcopied arrays (modification of sub_gA.arr must not affect self.testtiff.arr)
        sub_gA[:2, :2] = 99
        self.assertTrue(np.array_equal(sub_gA[:2, :2], np.full((2, 2), 99, gA_2D.dtype)))
        self.assertNotEqual(np.mean(sub_gA_2D[:2, :2]), 99)
        self.assertNotEqual(np.std(sub_gA_2D[:2, :2]), 0)

        # test metadata
        self.assertEqual(sub_gA.meta.bands, 1)
        self.assertEqual(len(list(sub_gA.meta.band_meta.values())[0]), 1)
        self.assertEqual(len(list(sub_gA.bandnames.keys())), 1)
        self.assertNotEqual(sub_gA.gt, gA_2D.gt)
        self.assertEqual(sub_gA.prj, gA_2D.prj)

628 629 630 631 632 633
        # test not to return GeoArray
        out = gA_2D.get_subset(xslice=slice(2, 5), yslice=slice(None, 3), return_GeoArray=False)

        # test with provided zslice
        with self.assertRaises(ValueError):
            gA_2D.get_subset(xslice=slice(2, 5), yslice=slice(None, 3), zslice=slice(1, 2))
634 635
        with self.assertRaises(ValueError):
            gA_2D.get_subset(xslice=slice(2, 5), yslice=slice(None, 3), zslice=[1, 2])
636 637 638 639 640 641 642

        self.assertIsInstance(out, tuple)
        self.assertTrue(len(out), 3)
        self.assertIsInstance(out[0], np.ndarray)
        self.assertIsInstance(out[1], tuple)
        self.assertIsInstance(out[2], str)

643
    def test_save(self):
644 645 646
        """
        Testing the function: save,
        test with 2 stages.
647 648 649
        Stage 1: After saving the original TIFF-Image (output format "GTiff") to the "../tests/data/output"-directory
                    with the "save"-function and then instancing the copied TIFF-Image as "GeoArray"-object, it is
                    first tested, if the file exists in the aforementioned directory.
650
                    When the file exists, induction of stage 2...
651 652 653
        Stage 2: Testing, whether a) the object "testtiff_copy" is an instance of the "GeoArray"-class, and b) if the
                    numpy.array of the copied TIFF-Image is identical to the numpy.array of the original TIFF-Image.
        If the newly created file does not exist in the "../tests/data/output"-directory (Stage 1), the whole test
654
        will be skipped.
655 656 657
        """

        # Saving the "GeoArray"-instance "testtiff" with the "save"-function of the "GeoArray"-class to the
658
        # "../tests/data/output"-directory of this repository and instancing it again as a "GeoArray"-object.
659 660
        L8_2bands_extract10x11_copy = os.path.join(tests_path, "tests", "data", "output",
                                                   "L8_BadDataMask10x11_copy.tif")
661 662 663 664 665 666
        self.testtiff.save(L8_2bands_extract10x11_copy, fmt="GTiff")
        testtiff_copy = GeoArray(L8_2bands_extract10x11_copy)

        if path.exists(L8_2bands_extract10x11_copy):
            assert isinstance(testtiff_copy, GeoArray) and \
                   np.array_equal(self.testtiff[:], testtiff_copy[:]), \
667 668 669 670
                   "The copy of the original TIFF-Image, saved by the 'GeoArray' save-function, %s an instance of " \
                   "class 'GeoArray'. Its numpy.array %s identical to the numpy.array of the original TIFF-Image." \
                   % ("IS" if isinstance(testtiff_copy, GeoArray) else "IS NOT",
                      "IS" if np.array_equal(self.testtiff[:], testtiff_copy[:]) else "IS NOT")
671 672 673 674

        else:
            self.skipTest("The file '%s' does not exists in the directory '%s'. "
                          "The test 'test_SaveTiffToDisk' will be skipped."
675
                          % (path.basename(L8_2bands_extract10x11_copy), path.dirname(L8_2bands_extract10x11_copy)))
676 677

    def test_PlottingFunctions(self):
678 679
        # FIXME: This test-function is not yet complete! + TODO: Use other parameters of the plot functions!
        # TODO: Idea: Testing of plot-functions with "from matplotlib.testing.decorators import image_comparison" (?),
680 681
        # TODO: example of matplotlib.testing.decorators under:
        # TODO: https://github.com/ketch/griddle/blob/master/tests/plot_tests.py
682 683
        self.testtiff.show()
        # self.testtiff.show(interactive=True) # only works if test is started with ipython.
684

685
        if find_loader('mpl_toolkits.basemap'):
686 687
            self.testtiff.show_map()
            self.testtiff.show_map_utm()  # Function still under construction.
688 689
        self.testtiff.show_histogram()

690
        # ----> TODO: Write tests for the remaining functions!
691 692


693
# Parametrizing the test case to run twice.
694 695 696
# Note that 'time.sleep(0.5)' is called several times throughout the following code segment to prevent that the
# standard output of the tests is mixed up with the generated print-commandos.

697
# Source: renzop, 04 March 2016, "Python Testing how to run parameterised Testcases and pass a parameter to setupClass".
698 699
# [Online]. URL: https://stackoverflow.com/questions/35773976/python-testing-how-to-run-parameterised-testcases-and-
#                pass-a-parameter-to-setupc
700
# [Accessed 30 Mai 2017].
701
if __name__ == '__main__':
702
    for k in range(0, 2, 1):
703
        # Creating a test suite for the first test case
704 705
        suite = unittest.TestSuite()
        loader = TestLoader()
Daniel Scheffler's avatar
Daniel Scheffler committed
706

707 708 709 710 711
        test = Test_GeoarrayAppliedOnPathArray
        test.k = k

        tests = loader.loadTestsFromTestCase(test)
        suite.addTest(tests)
712
        time.sleep(0.5)
713 714 715
        testResult = unittest.TextTestRunner(verbosity=1).run(suite)
        # Source End

716
        # Extending the display of the test result of test case 1 since the verbosity of the test case output is 1.
717 718 719
        time.sleep(0.5)
        print("Summary:", testResult)
        print("Test successful (errors, failures)? ", testResult.wasSuccessful(),
720 721
              " (", len(testResult.errors), ",", len(testResult.failures), ")", sep="")
        if not testResult.skipped:
722 723 724
            print("Test(s) skipped? No")
        else:
            print("Test(s) skipped? Yes")
725 726 727
            for j in range(0, len(testResult.skipped), 1):
                print(j + 1, ".Skipping occurred in: ", testResult.skipped[j][0], sep="")
                print("Reason for ", j + 1, ".skip: ", testResult.skipped[j][1], sep="")
728 729 730

        # If-else loop: Creating and executing the second test suite (created with the tests of the test case 2), only
        # if the first test case was successful and no test was skipped. Otherwise, a note will be printed as output to
731 732
        # inform the user that test case 2 was skipped. Additionally, the "../tests/data/output"-directory with files
        # will be removed in the else-statement.
733 734
        if testResult.wasSuccessful() and testResult.skipped == []:
            other_suite = unittest.TestSuite()
Daniel Scheffler's avatar
Daniel Scheffler committed
735

736 737 738 739 740 741 742 743 744 745 746 747
            more_test = Test_GeoarrayFunctions
            more_test.k = k

            more_tests = loader.loadTestsFromTestCase(more_test)
            other_suite.addTest(more_tests)
            time.sleep(0.5)
            more_testResult = unittest.TextTestRunner(verbosity=2).run(other_suite)

            # TODO: Extent the output of test case 2 (similar to test case 1) and
            # TODO: Change the verbosity of the TextTestRunner to 1

        else:
748
            data_dir = os.path.join(tests_path, "tests", "data", "output")
749 750 751 752 753 754
            data_dir_list = os.listdir(data_dir)

            for files in data_dir_list:
                os.remove(path.join(data_dir, files))
            os.rmdir(data_dir)

755 756
            print("Test case 2: Since %s error/ %s failure/ %s skip occured in the first test case the second test "
                  "case 'Test_GeoarrayFunctions' will be skipped."
757
                  % (len(testResult.errors), len(testResult.failures), len(testResult.skipped)))
758 759

        time.sleep(0.5)