diff --git a/geoarray/baseclasses.py b/geoarray/baseclasses.py index beabf9676664b0eee25d5643cd49a6cf3da4d397..6344ee0291cc58c12f04562d03ab1dcf3d6be40f 100644 --- a/geoarray/baseclasses.py +++ b/geoarray/baseclasses.py @@ -149,7 +149,7 @@ class GeoArray(object): if self._bandnames and len(self._bandnames) == self.bands: return self._bandnames else: - self._bandnames = OrderedDict(('B%s' % band, i) for i, band in enumerate(range(1, self.bands + 1))) + del self.bandnames # runs deleter which sets it to default values return self._bandnames @bandnames.setter @@ -164,11 +164,23 @@ class GeoArray(object): assert len(list(set([type(b) for b in list_bandnames]))) == 1 and type(list_bandnames[0] == 'str'), \ "'bandnames must be a set of strings. Got other datetypes in there.'" bN_dict = OrderedDict((band, i) for i, band in enumerate(list_bandnames)) - assert len( - bN_dict) == self.bands, 'Bands must not have the same name. Received band list: %s' % list_bandnames + assert len(bN_dict) == self.bands, \ + 'Bands must not have the same name. Received band list: %s' % list_bandnames self._bandnames = bN_dict + # update bandnames in metadata + if self._metadata is not None: + self.metadata.loc['band_name', :] = list_bandnames + else: + del self.bandnames + + @bandnames.deleter + def bandnames(self): + self._bandnames = OrderedDict(('B%s' % band, i) for i, band in enumerate(range(1, self.bands + 1))) + if self._metadata is not None: + self.metadata.loc['band_name', :] = list(self._bandnames.keys()) + @property def is_inmem(self): """Check if associated image array is completely loaded into memory.""" @@ -1397,7 +1409,7 @@ class GeoArray(object): progress=progress) return sub_arr, sub_gt, sub_prj - def get_subset(self, xslice=None, yslice=None, zslice=None, return_GeoArray=True): + def get_subset(self, xslice=None, yslice=None, zslice=None, return_GeoArray=True, reset_bandnames=False): # type: (slice, slice, slice, bool) -> GeoArray """Returns a new instatnce of GeoArray representing a subset of the initial one wit respect to given array position. @@ -1411,13 +1423,26 @@ class GeoArray(object): sub_arr = self[yslice if yslice else slice(None), # row xslice if xslice else slice(None), # col zslice if zslice else slice(None)] # band - sub_ulXY = imXY2mapXY((xslice.start, yslice.start), self.gt) + + if sub_arr is None: + raise ValueError('Unable to return an array for the given slice parameters.') + + sub_ulXY = imXY2mapXY((xslice.start or 0, yslice.start or 0), self.gt) sub_gt = (sub_ulXY[0], self.gt[1], self.gt[2], sub_ulXY[1], self.gt[4], self.gt[5]) + + # apply zslice to bandnames and metadata + bNs_out = list(np.array(list(self.bandnames))[zslice]) + meta_out = None if self._metadata is None else self.meta[list(np.array(range(self.bands))[zslice])] + + # get output GeoArray and copy some properties from the input GeoArray sub_gA = GeoArray(sub_arr, sub_gt, self.prj, - bandnames=list(self.bandnames.keys()), nodata=self.nodata, progress=self.progress, q=self.q) - sub_gA.meta = self.meta + bandnames=bNs_out, nodata=self.nodata, progress=self.progress, q=self.q) + sub_gA.meta = meta_out sub_gA.filePath = self.filePath + if reset_bandnames: + del self.bandnames # also updates the respective row in self.meta + return sub_gA if return_GeoArray else (sub_arr, sub_gt, self.prj) # import copy # TODO implement that in order to include all previously set attribute values diff --git a/tests/test_geoarray.py b/tests/test_geoarray.py index f061ad2584206ea21d5abd7f998b3d90e00db392..8e1e28f5e966b3c111d71efb243407045e622170 100644 --- a/tests/test_geoarray.py +++ b/tests/test_geoarray.py @@ -35,6 +35,7 @@ import unittest from unittest import TestLoader import matplotlib from typing import Iterable +from importlib import util # Imports regarding the created python module. from py_tools_ds.geo.vector import geometry @@ -183,7 +184,7 @@ class Test_GeoarrayAppliedOnPathArray(unittest.TestCase): 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!") - def test_Bandnames(self): + def test_bandnames(self): """ Testing the function: bandnames. Test, if the default (for k=0) and set (for k=1) band names respectively were correctly assigned. @@ -191,7 +192,7 @@ class Test_GeoarrayAppliedOnPathArray(unittest.TestCase): self.assertEqual(self.testtiff.bandnames, self.expected_bandnames, msg="The bandnames of the Tiff-file are different than ['B1', 'B2'] (format: OrderedDict)") - def test_ShapeOfTiffArray(self): + def test_shape(self): """ Testing the functions: shape - ndim - rows - columns - bands, indirect testing of the function: set_gdalDataset_meta(!), @@ -216,7 +217,7 @@ class Test_GeoarrayAppliedOnPathArray(unittest.TestCase): self.skipTest("The shape of the array behind the 'Geoarray'-object is not as expected! " "The test 'test_ShapeOfTiffArray' will be skipped.") - def test_DtypeOfTiffArray(self): + def test_dtype(self): """ Testing the function: dtype, indirect testing of the function: set_gdalDataset_meta(!). @@ -226,7 +227,7 @@ class Test_GeoarrayAppliedOnPathArray(unittest.TestCase): self.assertEqual(self.testtiff.dtype, self.expected_dtype, msg='The dtype of the corresponding array is not as expected!') - def test_GeotransformTiff(self): + def test_geotransform(self): """ Testing the functions: geotransform - xgsd - ygsd - xygrid_specs, indirect testing of the function: set_gdalDataset_meta(!), @@ -254,7 +255,7 @@ class Test_GeoarrayAppliedOnPathArray(unittest.TestCase): self.skipTest("The geotransform-tuple of the array behind the 'GeoArray'-object is not as expected! " "The test 'test_GeotransformTiff' will be skipped.") - def test_ProjectionTiff(self): + def test_projection(self): """ Testing the functions: projection - epsg, indirect testing of the function: set_gdalDataset_meta(!), @@ -283,7 +284,7 @@ class Test_GeoarrayAppliedOnPathArray(unittest.TestCase): self.skipTest("The projections of the 'GeoArray'-object is not as expected! " "The test 'test_ProjectionTiff' will be skipped.") - def test_NoDataValueOfTiff(self): + def test_nodata(self): """ Testing the function: nodata, indirect testing of the functions: find_noDataVal(), set_gdalDataset_meta(!). @@ -301,6 +302,30 @@ class Test_GeoarrayAppliedOnPathArray(unittest.TestCase): msg="The nodata-value of the tested Tiff-file (%s) is not as expected (%s)!" % (self.testtiff.nodata, self.given_nodata)) + 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 full array + validate(self.testtiff[:], (R, C, B)) + + # test row/col subset + 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 + validate(self.testtiff[:, :, 0:1], (R, C, 1)) # band slice # FIXME returns 3D array + validate(self.testtiff[1], (R, C)) # only band is given # FIXME returns 2D + validate(self.testtiff['B1'], (R, C)) # only bandname is given + + # test wrong inputs + self.assertRaises(ValueError, self.testtiff.__getitem__, 'B01') + + # TODO: add further tests + ################################### # Test case: Test_GeoarrayFunctions @@ -357,14 +382,13 @@ class Test_GeoarrayFunctions(unittest.TestCase): def tearDownClass(cls): # 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. - data_dir = os.path.join(tests_path, "tests", "data", "output") - data_dir_list = os.listdir(data_dir) + out_dir = os.path.join(tests_path, "tests", "data", "output") - for files in data_dir_list: - os.remove(path.join(data_dir, files)) - os.rmdir(data_dir) + for file in os.listdir(out_dir): + os.remove(path.join(out_dir, file)) + os.rmdir(out_dir) - def test_BoxIsInstanceOfBoxObj(self): + def test_box(self): """ Testing the function: box. Test, if the output of the box-function is an instance boxObj (class, defined in geometry.py, py_tools_ds). @@ -372,7 +396,7 @@ class Test_GeoarrayFunctions(unittest.TestCase): self.assertIsInstance(self.testtiff.box, geometry.boxObj) - def test_MaskNodataIsInstanceOfNoDataMask(self): + def test_mask_nodata(self): # TODO: Consider the dependency of mask_nodata on the calc_mask_nodata-function. """ Testing the function: mask_nodata. @@ -381,7 +405,7 @@ class Test_GeoarrayFunctions(unittest.TestCase): self.assertIsInstance(self.testtiff.mask_nodata, masks.NoDataMask) - def test_MaskBaddataOffTiff(self): + def test_mask_baddata(self): """ Testing the function: mask_baddata. Test, @@ -399,7 +423,7 @@ class Test_GeoarrayFunctions(unittest.TestCase): self.testtiff.mask_baddata = bdm self.assertIsInstance(self.testtiff.mask_baddata, masks.BadDataMask) - def test_FootprintPolyIsInstanceOfShapely(self): + def test_footprint_poly(self): # TODO: Test the validation of the footprint_poly-function. # TODO: Consider the dependencies of the footprint_poly-function on mask_nodata, boxObj. """ @@ -409,7 +433,7 @@ class Test_GeoarrayFunctions(unittest.TestCase): self.assertIsInstance(self.testtiff.footprint_poly, Polygon) - def test_MetadataIsInstanceOfGeodataframe(self): + def test_metadata(self): # TODO: Create a metadata-file for the tested TIFF-Image. # TODO: Test, if the metadata-function gives an output """ @@ -426,7 +450,27 @@ class Test_GeoarrayFunctions(unittest.TestCase): for ((rS, rE), (cS, cE)), tile in tiles: self.assertTrue(np.array_equal(tile, self.testtiff[rS: rE + 1, cS: cE + 1])) - def test_SaveTiffToDisk(self): + def test_get_subset(self): + # test without reseting band names + 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]) + + # test with reseting band names + 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']) + + # 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) + 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) + + def test_save(self): """ Testing the function: save, test with 2 stages. @@ -467,8 +511,10 @@ class Test_GeoarrayFunctions(unittest.TestCase): # TODO: https://github.com/ketch/griddle/blob/master/tests/plot_tests.py self.testtiff.show() # self.testtiff.show(interactive=True) # only works if test is started with ipython. - # self.testtiff.show_map() - # self.testtiff.show_map_utm() # Function still under construction. + + if util.find_spec('mpl_toolkits.basemap'): + self.testtiff.show_map() + self.testtiff.show_map_utm() # Function still under construction. self.testtiff.show_histogram() # ----> TODO: Write tests for the remaining functions!