dataset.py 24.9 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
# -*- coding: utf-8 -*-

import collections
import copy
import warnings
import logging

import numpy as np

from geoarray import GeoArray, NoDataMask, CloudMask
11
from py_tools_ds.geo.projection import WKT2EPSG, get_UTMzone
12
13
14
from py_tools_ds.geo.coord_calc import calc_FullDataset_corner_positions
from py_tools_ds.geo.coord_trafo import pixelToLatLon, pixelToMapYX, imXY2mapXY
from py_tools_ds.geo.map_info import geotransform2mapinfo, mapinfo2geotransform
15
from py_tools_ds.numeric.array import get_array_tilebounds
16
17

from ..misc.logging import GMS_logger as DatasetLogger
18
from ..model.metadata import METADATA, get_LayerBandsAssignment
19
from ..misc import path_generator as PG
20
from ..io import input_reader as INP_R
21
22
23
from ..misc import helper_functions as HLP_F
from ..misc import definition_dicts as DEF_D

24
__author__ = 'Daniel Scheffler'
25
26
27
28
29
30


class Dataset(object):
    def __init__(self):

        # protected attributes
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
        self._logger = None
        self._loggers_disabled = False
        self._log = ''
        self._MetaObj = None
        self._LayerBandsAssignment = None
        self._coreg_needed = None
        self._resamp_needed = None
        self._arr = None
        self._mask_nodata = None
        self._mask_clouds = None
        self._mask_clouds_confidence = None
        self._masks = None
        self._dem = None
        self._pathGen = None
        self._ac_options = {}
        self._ac_errors = None
47
        self._spat_homo_errors = None
48
        self._spec_homo_errors = None
49
        self._accuracy_layers = None
50
51

        # defaults
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
        self.proc_level = 'init'
        self.image_type = ''
        self.satellite = ''
        self.sensor = ''
        self.subsystem = ''
        self.sensormode = ''
        self.acq_datetime = None  # also includes time, set by from_disk() and within L1A_P
        self.entity_ID = ''
        self.scene_ID = -9999
        self.filename = ''
        self.dataset_ID = -9999
        self.outInterleave = 'bsq'
        self.VAA_mean = None  # set by self.calc_mean_VAA()
        self.corner_lonlat = None
        self.trueDataCornerPos = []  # set by self.calc_corner_positions()
        self.trueDataCornerLonLat = []  # set by self.calc_corner_positions()
        self.fullSceneCornerPos = []  # set by self.calc_corner_positions()
        self.fullSceneCornerLonLat = []  # set by self.calc_corner_positions()
        # rows,cols,bands of the full scene (not of the subset as possibly represented by self.arr.shape)
        self.shape_fullArr = [None, None, None]
        self.arr_shape = 'cube'
        self.arr_desc = ''  # description of data units for self.arr
        self.arr_pos = None  # <tuple> in the form ((row_start,row_end),(col_start,col_end))
        self.tile_pos = None  # <list>, filled by self.get_tilepos()
        self.GeoTransProj_ok = True  # set by self.validate_GeoTransProj_GeoAlign()
        self.GeoAlign_ok = True  # set by self.validate_GeoTransProj_GeoAlign()
        self.masks_meta = {}  # set by self.build_L1A_masks()
79
80
81
        # self.CLD_obj               = CLD_P.GmsCloudClassifier(classifier=self.path_cloud_class_obj)

        # set pathes
82
83
84
85
86
87
88
89
        self.path_archive = ''
        self.path_procdata = ''
        self.ExtractedFolder = ''
        self.baseN = ''
        self.path_logfile = ''
        self.path_archive_valid = False
        self.path_InFilePreprocessor = None
        self.path_MetaPreprocessor = None
90
91
92
93
94

    def __getstate__(self):
        """Defines how the attributes of GMS object are pickled."""

        self.close_loggers()
95
        del self.pathGen  # path generator can only be used for the current processing level
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

        return self.__dict__

    def __setstate__(self, ObjDict):
        """Defines how the attributes of GMS object are unpickled."""

        self.__dict__ = ObjDict
        # TODO unpickle meta to MetaObj

    def __deepcopy__(self, memodict={}):
        """Returns a deepcopy of the object excluding loggers because loggers are not serializable."""

        cls = self.__class__
        result = cls.__new__(cls)
        self.close_loggers()
111
        del self.pathGen  # has a logger
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
        memodict[id(self)] = result

        for k, v in self.__dict__.items():
            setattr(result, k, copy.deepcopy(v, memodict))
        return result

    @property
    def logger(self):
        if self._loggers_disabled:
            return None
        if self._logger and self._logger.handlers[:]:
            return self._logger
        else:
            self._logger = DatasetLogger('log__' + self.baseN, fmt_suffix=self.scene_ID, path_logfile=self.path_logfile,
                                         log_level='INFO', append=True)
            return self._logger

    @logger.setter
    def logger(self, logger):
        assert isinstance(logger, logging.Logger) or logger in ['not set', None], \
132
            "GMS_obj.logger can not be set to %s." % logger
133
134

        # save prior logs
135
        # if logger is None and self._logger is not None:
136
137
138
        #    self.log += self.logger.captured_stream
        self._logger = logger

139
    @property  # FIXME does not work yet
140
141
    def log(self):
        """Returns a string of all logged messages until now."""
Daniel Scheffler's avatar
Daniel Scheffler committed
142

143
144
145
146
        return self._log

    @log.setter
    def log(self, string):
147
148
        assert isinstance(string, str), "'log' can only be set to a string. Got %s." % type(string)
        self._log = string
149
150
151
152
153

    @property
    def identifier(self):
        return collections.OrderedDict(zip(
            ['image_type', 'Satellite', 'Sensor', 'Subsystem', 'proc_level', 'dataset_ID', 'logger'],
154
155
            [self.image_type, self.satellite, self.sensor, self.subsystem, self.proc_level, self.dataset_ID,
             self.logger]))
156
157
158
159
160
161
162
163
164
165
166

    @property
    def MetaObj(self):
        if self._MetaObj is None:
            self._MetaObj = METADATA(self.identifier)

        return self._MetaObj

    @MetaObj.setter
    def MetaObj(self, MetaObj):
        assert isinstance(MetaObj, METADATA), "'MetaObj' can only be set to an instance of METADATA class. " \
167
                                              "Got %s." % type(MetaObj)
168
169
170
171
172
173
174
175
        self._MetaObj = MetaObj

    @MetaObj.deleter
    def MetaObj(self):
        self._MetaObj = None

    @property
    def LayerBandsAssignment(self):
176
177
        # FIXME merge that with self.MetaObj.LayerBandsAssignment
        # FIXME -> otherwise a change of LBA in MetaObj is not recognized here
178
179
        if self._LayerBandsAssignment:
            return self._LayerBandsAssignment
180
        elif self.image_type == 'RSD':
181
182
183
184
185
186
187
188
            self._LayerBandsAssignment = get_LayerBandsAssignment(self.identifier) \
                if self.sensormode != 'P' else get_LayerBandsAssignment(self.identifier, nBands=1)
            return self._LayerBandsAssignment
        else:
            return ''

    @LayerBandsAssignment.setter
    def LayerBandsAssignment(self, LBA_list):
189
        self._LayerBandsAssignment = LBA_list
190
191
192
193
        self.MetaObj.LayerBandsAssignment = LBA_list

    @property
    def arr(self):
194
        # type: () -> GeoArray
195
196
197
198
199
200
201
202
203
204
        # TODO this must return a subset if self.subset is not None
        return self._arr

    @arr.setter
    def arr(self, *geoArr_initArgs):
        # TODO this must be able to handle subset inputs in tiled processing
        self._arr = GeoArray(*geoArr_initArgs)

        # set nodata value and geoinfos
        # NOTE: MetaObj is NOT gettable before import_metadata has been executed!
205
        if hasattr(self, 'MetaObj') and self.MetaObj:
206
            self._arr.nodata = self.MetaObj.spec_vals['fill']
207
208
            self._arr.gt = mapinfo2geotransform(self.MetaObj.map_info) if self.MetaObj.map_info else [0, 1, 0, 0, 0, -1]
            self._arr.prj = self.MetaObj.projection if self.MetaObj.projection else self._arr.projection
209
210
        else:
            self._arr.nodata = DEF_D.get_outFillZeroSaturated(self._arr.dtype)[0]
211
212
            if hasattr(self, 'meta_odict') and self.meta_odict:  # TODO don't expect mete_odict here in base class
                self._arr.gt = mapinfo2geotransform(self.meta_odict['map info'])
213
214
215
216
217
218
219
220
                self._arr.prj = self.meta_odict['coordinate system string']

        # set bandnames like this: [B01, .., B8A,]
        if self.LayerBandsAssignment:
            if len(self.LayerBandsAssignment) == self._arr.bands:
                self._arr.bandnames = self.LBA2bandnames(self.LayerBandsAssignment)
            else:
                warnings.warn("Cannot set 'bandnames' attribute of GMS_object.arr because LayerBandsAssignment has %s "
221
222
                              "bands and GMS_object.arr has %s bands."
                              % (len(self.LayerBandsAssignment), self._arr.bands))
223
224
225
226
227
228
229
230
231
232
233

    @arr.deleter
    def arr(self):
        self._arr = None

    @property
    def mask_nodata(self):
        if self._mask_nodata is not None:
            if not self._mask_nodata.is_inmem and self._mask_nodata.bands > 1:
                # NoDataMask object self._mask_nodata points to multi-band image file (bands mask_nodata/mask_clouds)
                # -> read processes of not needed bands need to be avoided
234
235
236
                self._mask_nodata = NoDataMask(self._mask_nodata[:, :, 0],
                                               geotransform=self._mask_nodata.gt,
                                               projection=self._mask_nodata.prj)
237
238
239
240
            return self._mask_nodata

        elif self._masks:
            # return nodata mask from self.masks
241
242
243
            self._mask_nodata = NoDataMask(self._masks[:, :, 0],  # TODO use band names
                                           geotransform=self._masks.gt,
                                           projection=self._masks.prj)
244
245
246
247
            return self._mask_nodata

        elif isinstance(self.arr, GeoArray):
            self.logger.info('Calculating nodata mask...')
248
            self._mask_nodata = self.arr.mask_nodata  # calculates mask nodata if not already present
249
250
251
252
253
254
255
            return self._mask_nodata
        else:
            return None

    @mask_nodata.setter
    def mask_nodata(self, *geoArr_initArgs):
        if geoArr_initArgs[0] is not None:
256
            self._mask_nodata = NoDataMask(*geoArr_initArgs)
257
            self._mask_nodata.nodata = False
258
259
            self._mask_nodata.gt = self.arr.gt
            self._mask_nodata.prj = self.arr.prj
260
261
262
263
264
265
266
267
268
269
270
        else:
            del self.mask_nodata

    @mask_nodata.deleter
    def mask_nodata(self):
        self._mask_nodata = None

    @property
    def mask_clouds(self):
        if self._mask_clouds is not None:
            if not self._mask_clouds.is_inmem and self._mask_clouds.bands > 1:
271
272
273
274
275
                # CloudMask object self._mask_clouds points to multi-band image file on disk
                # (bands mask_nodata/mask_clouds) -> read processes of not needed bands need to be avoided
                self._mask_clouds = CloudMask(self._mask_clouds[:, :, 1],
                                              geotransform=self._mask_clouds.gt,
                                              projection=self._mask_clouds.prj)  # TODO add legend
276
277
            return self._mask_clouds

278
        elif self._masks and self._masks.bands > 1:  # FIXME this will not be secure if there are more than 2 bands
279
280
            # return cloud mask from self.masks
            self._mask_clouds = CloudMask(self._masks[:, :, 1],  # TODO use band names
281
282
                                          geotransform=self._masks.gt,
                                          projection=self._masks.prj)
283
284
            return self._mask_clouds
        else:
285
            return None  # TODO don't calculate cloud mask?
286
287
288

    @mask_clouds.setter
    def mask_clouds(self, *geoArr_initArgs):
289
290
        if geoArr_initArgs[0] is not None:  # FIXME shape validation?
            self._mask_clouds = CloudMask(*geoArr_initArgs)
291
            self._mask_clouds.nodata = 0
292
293
            self._mask_clouds.gt = self.arr.gt
            self._mask_clouds.prj = self.arr.prj
294
295
296
297
298
299
300
301
302
303
304
305
        else:
            del self.mask_clouds

    @mask_clouds.deleter
    def mask_clouds(self):
        self._mask_clouds = None

    @property
    def dem(self):
        """
        Returns an SRTM DEM in the exact dimension an pixel grid of self.arr as an instance of GeoArray.
        """
Daniel Scheffler's avatar
Daniel Scheffler committed
306

307
308
        if self._dem is None:
            self.logger.info('Generating DEM...')
309
310
            DC_args = (self.arr.box.boxMapXY, self.arr.prj, self.arr.xgsd, self.arr.ygsd)
            try:
311
                DC = INP_R.DEM_Creator(dem_sensor='SRTM', logger=self.logger)
312
313
314
                self._dem = DC.from_extent(*DC_args)
            except RuntimeError:
                self.logger.warning('SRTM DEM generation failed. Falling back to ASTER...')
315
                DC = INP_R.DEM_Creator(dem_sensor='ASTER', logger=self.logger)
316
317
                self._dem = DC.from_extent(*DC_args)

318
319
320
321
322
323
324
            self._dem.nodata = DEF_D.get_outFillZeroSaturated(self._dem.dtype)[0]
        return self._dem

    @dem.setter
    def dem(self, *geoArr_initArgs):
        if geoArr_initArgs[0] is not None:
            geoArr = GeoArray(*geoArr_initArgs)
325
            assert self._dem.shape[:2] == self.arr.shape[:2]
326

327
            self._dem = geoArr
328
            self._dem.nodata = DEF_D.get_outFillZeroSaturated(self._dem.dtype)[0]
329
330
            self._dem.gt = self.arr.gt
            self._dem.prj = self.arr.prj
331
332
333
334
335
336
337
338
        else:
            del self.dem

    @dem.deleter
    def dem(self):
        self._dem = None

    @property
339
    def pathGen(self):  # TODO keep that in the base class?
340
        # type: () -> PG.path_generator
341
342
343
        """
        Returns the path generator object for generating file pathes belonging to the GMS object.
        """
Daniel Scheffler's avatar
Daniel Scheffler committed
344

345
        if self._pathGen and self._pathGen.proc_level == self.proc_level:
346
347
348
349
350
351
352
353
354
            return self._pathGen
        else:
            self._pathGen = PG.path_generator(self.__dict__.copy(), MGRS_info=self.MGRS_info)

        return self._pathGen

    @pathGen.setter
    def pathGen(self, pathGen):
        assert isinstance(pathGen, PG.path_generator), 'GMS_object.pathGen can only be set to an instance of ' \
355
                                                       'path_generator. Got %s.' % type(pathGen)
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
        self._pathGen = pathGen

    @pathGen.deleter
    def pathGen(self):
        self._pathGen = None

    @property
    def subset(self):
        return [self.arr_shape, self.arr_pos]

    @staticmethod
    def LBA2bandnames(LayerBandsAssignment):
        """Convert LayerbandsAssignment from format ['1','2',...] to bandnames like this: [B01, .., B8A,]."""
        return ['B%s' % i if len(i) == 2 else 'B0%s' % i for i in LayerBandsAssignment]

    def from_disk(self, tuple_GMS_subset):
        """Fills an already instanced GMS object with data from disk. Excludes array attributes in Python mode.

        :param tuple_GMS_subset:    <tuple> e.g. ('/path/gms_file.gms', ['cube', None])
        """

        # TODO

        return copy.copy(self)

381
    def get_tilepos(self, target_tileshape, target_tilesize):
382
        self.tile_pos = [[target_tileshape, tb]
383
                         for tb in get_array_tilebounds(array_shape=self.shape_fullArr, tile_shape=target_tilesize)]
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398

    @staticmethod
    def rescale_array(inArray, outScaleFactor, inScaleFactor=1):
        """Adjust the scaling factor of an array to match the given output scale factor."""

        assert isinstance(inArray, np.ndarray)
        return inArray.astype(np.int16) * (outScaleFactor / inScaleFactor)

    def calc_mask_nodata(self, fromBand=None, overwrite=False):
        """Calculates a no data mask with (values: 0=nodata; 1=data)

        :param fromBand:  <int> index of the band to be used (if None, all bands are used)
        :param overwrite: <bool> whether to overwrite existing nodata mask that has already been calculated
        :return:
        """
Daniel Scheffler's avatar
Daniel Scheffler committed
399

400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
        self.logger.info('Calculating nodata mask...')

        if self._mask_nodata is None or overwrite:
            self.arr.calc_mask_nodata(fromBand=fromBand, overwrite=overwrite)
            self.mask_nodata = self.arr.mask_nodata
            return self.mask_nodata

    def get_subset_obj(self, imBounds=None, mapBounds=None, mapBounds_prj=None, out_prj=None, logmsg=None, v=False):
        # type: (tuple) -> self
        """Returns a subset of the given GMS object, based on the given bounds coordinates.
        Array attributes are clipped and relevant metadata keys are updated according to new extent.

        :param imBounds:        <tuple> tuple of image coordinates in the form (xmin,xmax,ymin,ymax)
        :param mapBounds:       <tuple> tuple of map coordinates in the form (xmin,xmax,ymin,ymax)
        :param mapBounds_prj:   <str> a WKT string containing projection of the given map bounds
                                (can be different to projection of the GMS object; ignored if map bounds not given)
        :param out_prj:         <str> a WKT string containing output projection.
                                If not given, the projection of self.arr is used.
        :param logmsg:          <str> a message to be logged when this method is called
        :param v:               <bool> verbose mode (default: False)
        :return:                <GMS_object> the GMS object subset
        """

        if logmsg:
            self.logger.info(logmsg)

        # copy object
427
        sub_GMS_obj = HLP_F.parentObjDict[self.proc_level](*HLP_F.initArgsDict[self.proc_level])  # init
428
429
430
        sub_GMS_obj.__dict__.update(
            {k: getattr(self, k) for k in self.__dict__.keys()
             if not isinstance(getattr(self, k), (GeoArray, np.ndarray))}.copy())
431
432
433
434

        sub_GMS_obj = copy.deepcopy(sub_GMS_obj)

        # clip all array attributes using the given bounds
435
        # list_arraynames = [i for i in self.__dict__ if not callable(getattr(self, i)) and \
436
        #                   isinstance(getattr(self, i), np.ndarray)]
437
        list_arraynames = [i for i in ['arr', 'masks', 'ac_errors', 'mask_clouds_confidence', 'spec_homo_errors']
438
                           if hasattr(self, i) and getattr(self, i) is not None]  # FIXME hardcoded
439
440
441
442
443
444
        assert list_arraynames
        assert imBounds or mapBounds, "Either 'imBounds' or 'mapBounds' must be given. Got nothing."

        # calculate mapBounds if not already given
        if not mapBounds:
            rS, rE, cS, cE = imBounds
445
446
            (xmin, ymax), (xmax, ymin) = [imXY2mapXY((imX, imY), self.arr.gt) for imX, imY in
                                          [(cS, rS), (cE + 1, rE + 1)]]
447
448
449
450
451
452
            mapBounds_prj = self.arr.projection
        else:
            xmin, xmax, ymin, ymax = mapBounds

        # avoid disk IO if requested area is within the input array # TODO

453
454
455
456
        ####################################################################
        # subset all array attributes and update directly related metadata #
        ####################################################################

457
458
        for arrname in list_arraynames:
            # get input data for array subsetting
459
460
            meta2update = sub_GMS_obj.meta_odict if arrname == 'arr' else sub_GMS_obj.masks_meta
            geoArr = getattr(self, arrname)
461
462
463

            # get subsetted and (possibly) reprojected array
            out_prj = out_prj if out_prj else geoArr.prj
464
465
466
467
            rspAlg = 'near' if arrname == 'masks' else 'cubic'
            subArr = GeoArray(*geoArr.get_mapPos((xmin, ymin, xmax, ymax), mapBounds_prj,
                                                 out_prj=out_prj, rspAlg=rspAlg), progress=False,
                              bandnames=list(geoArr.bandnames), nodata=geoArr.nodata)
468
469

            # show result
470
471
            if v:
                subArr.show(figsize=(10, 10))
472
473

            # update array-related attributes of sub_GMS_obj
474
475
            meta2update['map info'] = geotransform2mapinfo(subArr.gt, subArr.prj)
            meta2update['coordinate system string'] = subArr.prj
476
            meta2update['lines'], meta2update['samples'] = subArr.arr.shape[:2]
477
478
479
480
            meta2update['CS_UTM_ZONE'] = get_UTMzone(prj=subArr.prj)  # FIXME only works for UTM
            meta2update['CS_EPSG'] = WKT2EPSG(subArr.prj)
            delattr(sub_GMS_obj,
                    arrname)  # connected filePath must be disconnected because projection change will be rejected
481
482
483
            setattr(sub_GMS_obj, arrname, subArr)

        # copy subset mask data to mask_nodata and mask_clouds
484
485
486
        sub_GMS_obj.mask_nodata = sub_GMS_obj.masks[:, :, 0]  # ==> getters will automatically return it from self.masks
        # FIXME not dynamic:
        sub_GMS_obj.mask_clouds = sub_GMS_obj.masks[:, :, 1] if sub_GMS_obj.masks.bands > 1 else None
487

488
489
490
491
        ###################
        # update metadata #
        ###################

492
493
494
        # update arr_pos
        sub_GMS_obj.arr_shape = 'block'
        if imBounds is not None:
495
            rS, rE, cS, cE = imBounds
496
497
            sub_GMS_obj.arr_pos = ((rS, rE), (cS, cE))
        else:
498
            pass  # FIXME how to set arr_pos in that case?
499
500

        # calculate new attributes 'corner_utm' and 'corner_lonlat'
501
502
503
504
505
506
507
        rows, cols, bands = sub_GMS_obj.arr.shape
        ULxy, URxy, LLxy, LRxy = [[0, 0], [cols - 1, 0], [0, rows - 1], [cols - 1, rows - 1]]
        # FIXME asserts gt in UTM coordinates:
        utm_coord_YX = pixelToMapYX([ULxy, URxy, LLxy, LRxy], geotransform=subArr.gt, projection=subArr.prj)
        # ULyx,URyx,LLyx,LRyx:
        lonlat_coord = pixelToLatLon([ULxy, URxy, LLxy, LRxy], geotransform=subArr.gt, projection=subArr.prj)
        sub_GMS_obj.corner_utm = [[YX[1], YX[0]] for YX in utm_coord_YX]  # ULxy,URxy,LLxy,LRxy
508
509
510
511
        sub_GMS_obj.corner_lonlat = [[YX[1], YX[0]] for YX in lonlat_coord]  # ULxy,URxy,LLxy,LRxy

        # calculate 'bounds_LonLat' and 'bounds_utm'
        sub_GMS_obj.bounds_LonLat = HLP_F.corner_coord_to_minmax(sub_GMS_obj.corner_lonlat)
512
        sub_GMS_obj.bounds_utm = HLP_F.corner_coord_to_minmax(sub_GMS_obj.corner_utm)
513
514
515
516
517
518
519

        # calculate data_corners_imXY (mask_nodata is always an array here because get_mapPos always returns an array)
        corners_imYX = calc_FullDataset_corner_positions(
            sub_GMS_obj.mask_nodata, assert_four_corners=False, algorithm='shapely')
        sub_GMS_obj.trueDataCornerPos = [(YX[1], YX[0]) for YX in corners_imYX]

        # calculate trueDataCornerLonLat
520
521
        data_corners_LatLon = pixelToLatLon(sub_GMS_obj.trueDataCornerPos,
                                            geotransform=subArr.gt, projection=subArr.prj)
522
523
524
        sub_GMS_obj.trueDataCornerLonLat = [(YX[1], YX[0]) for YX in data_corners_LatLon]

        # calculate trueDataCornerUTM
525
526
527
        data_corners_utmYX = pixelToMapYX([ULxy, URxy, LLxy, LRxy],
                                          geotransform=subArr.gt,
                                          projection=subArr.prj)  # FIXME asserts gt in UTM coordinates
528
529
530
531
        sub_GMS_obj.trueDataCornerUTM = [(YX[1], YX[0]) for YX in data_corners_utmYX]

        return sub_GMS_obj

532
    def to_tiles(self, blocksize=(2048, 2048)):
533
534
535
        # type: (tuple) -> self
        """Returns a generator object where items represent tiles of the given block size for the GMS object.

536
537
        # NOTE:  it's better to call get_subset_obj (also takes care of tile map infos)

538
539
540
541
542
543
544
        :param blocksize:   target dimensions of the generated block tile (rows, columns)
        :return:            <list> of GMS_object tiles
        """

        assert type(blocksize) in [list, tuple] and len(blocksize) == 2, \
            "The argument 'blocksize_RowsCols' must represent a tuple of size 2."

545
        tilepos = get_array_tilebounds(array_shape=self.shape_fullArr, tile_shape=blocksize)
546
547

        for tp in tilepos:
548
549
            (xmin, xmax), (ymin, ymax) = tp  # e.g. [(0, 1999), (0, 999)] at a blocksize of 2000*1000 (rowsxcols)
            tileObj = self.get_subset_obj(imBounds=(xmin, xmax, ymin, ymax))  # xmax+1/ymax+1?
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
            yield tileObj

    def save(self, write_masks_as_ENVI_classification=True, is_tempfile=False, compression=False):
        # type: (object, bool, bool) -> None
        """Write Dataset object to disk. Supports full cubes AND 'block' tiles.

        :param self:                               <object> GMS object, e.g. L1A_P.L1A_object
        :param write_masks_as_ENVI_classification:  <bool> whether to write masks as ENVI classification file
        :param is_tempfile:                         <bool> whether output represents a temporary file
                                                    -> suppresses logging and database updating
                                                    - ATTENTION! This keyword asserts that the actual output file that
                                                     is written later contains the final version of the array. The array
                                                     is not overwritten or written once more later, but only renamed.
        :param compression:                         <bool> enable or disable compression
        """
Daniel Scheffler's avatar
Daniel Scheffler committed
565

566
567
568
569
        pass

    def close_loggers(self):
        if self._logger not in [None, 'not set']:
570
571
            self.logger.close()  # this runs misc.logging.GMS_logger.close()
            self.logger = None  # also adds current captured stream to self.log
572
573

        if hasattr(self, 'MetaObj') and self.MetaObj and hasattr(self.MetaObj, 'logger') and \
574
                self.MetaObj.logger not in [None, 'not set']:
575
            self.MetaObj.logger.close()
576
            self.MetaObj.logger = None