Commit 1aff6e33 authored by Daniel Scheffler's avatar Daniel Scheffler
Browse files

Revised some docstrings and fixed a lot of docstyle issues.



Signed-off-by: Daniel Scheffler's avatarDaniel Scheffler <danschef@gfz-potsdam.de>
parent 594b712d
Pipeline #23798 passed with stages
in 15 minutes
......@@ -56,13 +56,14 @@ __author__ = 'Daniel Scheffler'
class GeoArray(object):
"""A class providing a fast Python interface for geodata - either on disk or in memory.
GeoArray can be instanced with a file path or with a numpy array and the corresponding geoinformation. Instances
can always be indexed and sliced like normal numpy arrays, no matter if it has been instanced from file or from an
in-memory array. GeoArray provides a wide range of geo-related attributes belonging to the dataset as well as
some functions for quickly visualizing the data as a map, a simple image or an interactive image.
"""
This class creates a fast Python interface for geodata - either on disk or in memory. It can be instanced
with a file path or with a numpy array and the corresponding geoinformation. Instances can always be indexed
like normal numpy arrays, no matter if GeoArray has been instanced from file or from an in-memory array.
GeoArray provides a wide range of geo-related attributes belonging to the dataset as well as some functions for
quickly visualizing the data as a map, a simple image or an interactive image.
"""
def __init__(self, path_or_array, geotransform=None, projection=None, bandnames=None, nodata=None, progress=True,
q=False):
# type: (Union[str, np.ndarray, GeoArray], tuple, str, list, float, bool, bool) -> None
......@@ -78,7 +79,6 @@ class GeoArray(object):
:param progress: show progress bars (default: True)
:param q: quiet mode (default: False)
"""
# TODO implement compatibility to GDAL VRTs
if not (isinstance(path_or_array, (str, np.ndarray, GeoArray)) or
issubclass(getattr(path_or_array, '__class__'), GeoArray)):
raise ValueError("%s parameter 'arg' takes only string, np.ndarray or GeoArray(and subclass) instances. "
......@@ -241,7 +241,6 @@ class GeoArray(object):
@property
def columns(self):
"""Get the number of columns of the associated image array."""
return self.shape[1]
cols = alias_property('columns')
......@@ -264,7 +263,7 @@ class GeoArray(object):
@property
def geotransform(self):
"""Get the GDAL GeoTransform of the associated image, e.g., (283500.0, 5.0, 0.0, 4464500.0, 0.0, -5.0)"""
"""Get the GDAL GeoTransform of the associated image, e.g., (283500.0, 5.0, 0.0, 4464500.0, 0.0, -5.0)."""
if self._geotransform:
return self._geotransform
elif not self.is_inmem:
......@@ -298,21 +297,21 @@ class GeoArray(object):
@property
def xygrid_specs(self):
"""
Get the specifications for the X/Y coordinate grid, e.g. [[15,30], [0,30]] for a coordinate with its origin
at X/Y[15,0] and a GSD of X/Y[15,30].
"""
"""Get the specifications for the X/Y coordinate grid.
This returns for example [[15,30], [0,30]] for a coordinate
with its origin at X/Y[15,0] and a GSD of X/Y[15,30].
"""
def get_grid(gt, xgsd, ygsd): return [[gt[0], gt[0] + xgsd], [gt[3], gt[3] - ygsd]]
return get_grid(self.geotransform, self.xgsd, self.ygsd)
@property
def projection(self):
"""
Get the projection of the associated image. Setting the projection is only allowed if GeoArray has been
instanced from memory or the associated file on disk has no projection.
"""
"""Get the projection of the associated image.
Setting the projection is only allowed if GeoArray has been instanced from memory or the associated file on
disk has no projection.
"""
if self._projection:
return self._projection
elif not self.is_inmem:
......@@ -347,19 +346,17 @@ class GeoArray(object):
@property
def is_map_geo(self):
# type: () -> bool
"""
Return 'True' if the GeoArray instance has a valid geoinformation with map instead of image coordinates.
"""
"""Return 'True' if the image has a valid geoinformation with map instead of image coordinates."""
return all([self.gt, list(self.gt) != [0, 1, 0, 0, 0, -1], self.prj])
@property
def nodata(self):
"""
Get the nodata value of the GeoArray. If GeoArray has been instanced with a file path the file is checked
for an existing nodata value. Otherwise (if no value is exlicitly given during object instanciation) the nodata
value is tried to be automatically detected.
"""
"""Get the nodata value of the GeoArray instance.
If GeoArray has been instanced with a file path the metadata of the file on disk is checked for an existing
nodata value. Otherwise (if no value is exlicitly given during object instanciation) an automatic detection
based on 3x3 windows at each image corner is run that analyzes the mean and standard deviation of these windows.
"""
if self._nodata is not None:
return self._nodata
else:
......@@ -387,10 +384,7 @@ class GeoArray(object):
@property
def mask_nodata(self):
"""
Get the nodata mask of the associated image array. It is calculated using all image bands.
"""
"""Get the nodata mask of the associated image array. It is generated based on all image bands."""
if self._mask_nodata is not None:
return self._mask_nodata
else:
......@@ -399,11 +393,10 @@ class GeoArray(object):
@mask_nodata.setter
def mask_nodata(self, mask):
"""Set bad data mask.
"""Set the bad data mask.
:param mask: Can be a file path, a numpy array or an instance o GeoArray.
"""
if mask is not None:
from .masks import NoDataMask
geoArr_mask = NoDataMask(mask, progress=self.progress, q=self.q)
......@@ -432,11 +425,10 @@ class GeoArray(object):
@property
def mask_baddata(self):
"""
Returns the bad data mask for the associated image array if it has been explicitly previously. It can be set
by passing a file path, a numpy array or an instance of GeoArray to the setter of this property.
"""
"""Return the bad data mask.
Note: The mask must be explicitly set to a file path or a numpy array before.
"""
return self._mask_baddata
@mask_baddata.setter
......@@ -445,7 +437,6 @@ class GeoArray(object):
:param mask: Can be a file path, a numpy array or an instance o GeoArray.
"""
if mask is not None:
from .masks import BadDataMask
geoArr_mask = BadDataMask(mask, progress=self.progress, q=self.q)
......@@ -474,11 +465,8 @@ class GeoArray(object):
@property
def footprint_poly(self):
"""Get the footprint polygon of the associated image array (shapely.geometry.Polygon)."""
# FIXME should return polygon in image coordinates if no projection is available
"""
Get the footprint polygon of the associated image array (returns an instance of shapely.geometry.Polygon.
"""
if self._footprint_poly is None:
assert self.mask_nodata is not None, 'A nodata mask is needed for calculating the footprint polygon. '
if False not in self.mask_nodata[:]:
......@@ -519,15 +507,14 @@ class GeoArray(object):
@property
def metadata(self):
"""
Returns a DataFrame containing all available metadata (read from file if available).
"""Return a DataFrame containing all available metadata (read from file if available).
Use 'metadata[band_index].to_dict()' to get a metadata dictionary for a specific band.
Use 'metadata.loc[row_name].to_dict()' to get all metadata values of the same key for all bands as dictionary.
Use 'metadata.loc[row_name, band_index] = value' to set a new value.
:return: pandas.DataFrame
"""
if self._metadata is not None:
return self._metadata
else:
......@@ -624,13 +611,11 @@ class GeoArray(object):
return self.from_path(self.arg, getitem_params)
def __setitem__(self, idx, array2set):
"""Overwrites the pixel values of GeoArray.arr with the given array.
"""Overwrite the pixel values of GeoArray.arr with the given array.
:param idx: <int, list, slice> the index position to overwrite
:param array2set: <np.ndarray> array to be set. Must be compatible to the given index position.
:return:
"""
if self.is_inmem:
self.arr[idx] = array2set
else:
......@@ -653,24 +638,23 @@ class GeoArray(object):
raise AttributeError("%s object has no attribute '%s'." % (self.__class__.__name__, attr))
def __getstate__(self):
"""Defines how the attributes of GMS object are pickled."""
"""Define how the attributes of the GeoArray instance are pickled (e.g., by multiprocessing.Pool)."""
# clean array cache in order to avoid cache pickling
self.flush_cache()
return self.__dict__
def __setstate__(self, state):
"""Defines how the attributes of GMS object are unpickled.
"""Define how the attributes of the GeoArray instance are unpickled (e.g., by multiprocessing.Pool).
NOTE: This method has been implemented because otherwise pickled and unpickled instances show recursion errors
within __getattr__ when requesting any attribute.
"""
self.__dict__ = state
def calc_mask_nodata(self, fromBand=None, overwrite=False, flag='all'):
# type: (int, bool, str) -> np.ndarray
"""Calculates a no data mask with values False (=nodata) and True (=data).
"""Calculate a no data mask with values False (=nodata) and True (=data).
:param fromBand: index of the band to be used (if None, all bands are used)
:param overwrite: whether to overwrite existing nodata mask that has already been calculated
......@@ -740,7 +724,8 @@ class GeoArray(object):
return mask
def find_noDataVal(self, bandIdx=0, sz=3):
"""Tries to derive no data value from homogenious corner pixels within 3x3 windows (by default).
"""Try to derive no data value from homogenious corner pixels within 3x3 windows (by default).
:param bandIdx:
:param sz: window size in which corner pixels are analysed
"""
......@@ -775,12 +760,11 @@ class GeoArray(object):
return nodata
def set_gdalDataset_meta(self):
"""Retrieves GDAL metadata from file. This function is only executed once to avoid overwriting of user defined
attributes, that are defined after object instanciation.
"""Retrieve GDAL metadata from file.
:return:
This is only executed once to avoid overwriting of user defined attributes,
that are defined after object instanciation.
"""
if not self._gdalDataset_meta_already_set:
assert self.filePath
ds = gdal.Open(self.filePath)
......@@ -824,6 +808,7 @@ class GeoArray(object):
def from_path(self, path, getitem_params=None):
# type: (str, list) -> np.ndarray
"""Read a GDAL compatible raster image from disk, with respect to the given image position.
NOTE: If the requested array position is already in cache, it is returned from there.
:param path: <str> the file path of the image to read
......@@ -996,7 +981,6 @@ class GeoArray(object):
:param creationOptions: <list> GDAL creation options,
e.g., ["QUALITY=80", "REVERSIBLE=YES", "WRITE_METADATA=YES"]
"""
if not self.q:
print('Writing GeoArray of size %s to %s.' % (self.shape, out_path))
assert self.ndim in [2, 3], 'Only 2D- or 3D arrays are supported.'
......@@ -1137,7 +1121,6 @@ class GeoArray(object):
def dump(self, out_path):
# type: (str) -> None
"""Serialize the whole object instance to disk using dill."""
import dill
with open(out_path, 'wb') as outF:
dill.dump(self, outF)
......@@ -1227,7 +1210,7 @@ class GeoArray(object):
def show(self, xlim=None, ylim=None, band=None, boundsMap=None, boundsMapPrj=None, figsize=None,
interpolation='none', vmin=None, vmax=None, pmin=2, pmax=98, cmap=None, nodataVal=None,
res_factor=None, interactive=False, ax=None, ignore_rotation=False):
"""Plots the desired array position into a figure.
"""Plot the desired array position into a figure.
:param xlim: [start_column, end_column]
:param ylim: [start_row, end_row]
......@@ -1327,7 +1310,7 @@ class GeoArray(object):
def show_map(self, xlim=None, ylim=None, band=0, boundsMap=None, boundsMapPrj=None, out_epsg=None, figsize=None,
interpolation='none', vmin=None, vmax=None, pmin=2, pmax=98, cmap=None, nodataVal=None,
res_factor=None, return_map=False):
"""
"""Show a cartopy map of the associated image data (requires geocoding and projection information).
:param xlim:
:param ylim:
......@@ -1410,10 +1393,7 @@ class GeoArray(object):
plt.show()
def show_footprint(self):
"""This method is intended to be called from Jupyter Notebook and shows a web map containing the calculated
footprint of GeoArray.
"""
"""Show a web map containing the computed footprint of the GeoArray instance in a Jupyter notebook."""
if not find_loader('folium') or not find_loader('geojson'):
raise ImportError(
"This method requires the libraries 'folium' and 'geojson'. They can be installed with "
......@@ -1431,7 +1411,6 @@ class GeoArray(object):
def show_histogram(self, band=1, bins=200, normed=False, exclude_nodata=True, vmin=None, vmax=None, figsize=None):
# type: (int, int, bool, bool, float, float, tuple) -> None
"""Show a histogram of a given band.
:param band: the band to be used to plot the histogram
......@@ -1442,7 +1421,6 @@ class GeoArray(object):
:param vmax: maximum value for the x-axis of the histogram
:param figsize: figure size (tuple)
"""
from matplotlib import pyplot as plt
if self.nodata is not None and exclude_nodata:
......@@ -1504,6 +1482,7 @@ class GeoArray(object):
class GeneratorLen(object):
"""Generator class adding __len__."""
def __init__(self, gen, length):
self.gen = gen
self.length = length
......@@ -1526,7 +1505,7 @@ class GeoArray(object):
def get_mapPos(self, mapBounds, mapBounds_prj, band2get=None, out_prj=None, arr_gt=None, arr_prj=None, fillVal=None,
rspAlg='near', progress=None, v=False): # TODO implement slice for indexing bands
# type: (tuple, str, int, str, tuple, str, int, str, bool, bool) -> (np.ndarray, tuple, str)
"""Returns the array data of GeoArray at a given geographic position.
"""Return the array data of GeoArray at a given geographic position.
NOTE: The given mapBounds are snapped to the pixel grid of GeoArray. If the given mapBounds include areas
outside of the extent of GeoArray, these areas are filled with the fill value of GeoArray.
......@@ -1674,7 +1653,6 @@ class GeoArray(object):
:param CPUs: <int> number of CPUs to use (default: None -> use all available CPUs)
:return:
"""
assert (tgt_prj and tgt_xygrid) or prototype, "Provide either 'prototype' or 'tgt_prj' and 'tgt_xygrid'!"
tgt_prj = tgt_prj or prototype.prj
tgt_xygrid = tgt_xygrid or prototype.xygrid_specs
......@@ -1721,7 +1699,7 @@ class GeoArray(object):
self.footprint_poly = self.footprint_poly.intersection(self.box.mapPoly)
def read_pointData(self, mapXY_points, mapXY_points_prj=None, band=None, offside_val=np.nan):
"""Returns the array values for the given set of X/Y coordinates.
"""Return the array values for the given set of X/Y coordinates.
NOTE: If GeoArray has been instanced with a file path, the function will read the dataset into memory.
......@@ -1737,7 +1715,6 @@ class GeoArray(object):
- np.ndarray with shape [Nx1] in case only one band is requested
- np.ndarray with shape [Nx1xbands] in case all bands are requested
"""
mapXY = mapXY_points if isinstance(mapXY_points, np.ndarray) else np.array(mapXY_points).reshape(1, 2)
prj = mapXY_points_prj if mapXY_points_prj else self.prj
......@@ -1783,15 +1760,15 @@ class GeoArray(object):
return pointdata
def to_mem(self):
"""Reads the whole dataset into memory and sets self.arr to the read data."""
"""Read the whole dataset into memory and sets self.arr to the read data."""
self.arr = self[:]
return self
def to_disk(self):
"""Sets self.arr back to None if GeoArray has been instanced with a file path
and the whole dataset has been read."""
"""Set self.arr back to None for in-memory instances, to release memory.
Note: This requires that the GeoArray was instanced with a file path.
"""
if self.filePath and os.path.isfile(self.filePath):
self._arr = None
else:
......@@ -1807,12 +1784,10 @@ class GeoArray(object):
def cache_array_subset(self, arr_pos):
# type: (list) -> None
"""Sets the array cache of the GeoArray instance to the given array in order to speed up calculations
afterwards.
"""Set the array cache of the GeoArray instance to the given array to speed up calculations afterwards.
:param arr_pos: a list of array indices as passed to __getitem__
"""
if not self.is_inmem:
# noinspection PyStatementEffect
self[arr_pos] # runs __getitem__ and sets self._arr_cache
......@@ -1821,7 +1796,6 @@ class GeoArray(object):
def flush_cache(self):
"""Clear the array cache of the GeoArray instance."""
self._arr_cache = None
......@@ -1835,11 +1809,10 @@ def get_GeoArray_from_GDAL_ds(ds):
class MultiGeoArray(object): # pragma: no cover
def __init__(self, GeoArray_list):
"""
"""Get an instance of MultiGeoArray.
:param GeoArray_list: a list of GeoArray instances having a geographic overlap
"""
self._arrs = None
self.arrs = GeoArray_list
......
......@@ -43,6 +43,7 @@ if TYPE_CHECKING:
def _clip_array_at_mapPos(arr, mapBounds, arr_gt, band2clip=None, fillVal=0):
# type: (T_ndA_gA, tuple, tuple, int, int) -> (np.ndarray, tuple)
"""
NOTE: asserts that mapBounds have the same projection like the coordinates in arr_gt
:param arr:
......@@ -52,7 +53,6 @@ def _clip_array_at_mapPos(arr, mapBounds, arr_gt, band2clip=None, fillVal=0):
:param fillVal:
:return:
"""
# assertions
assert isinstance(arr_gt, (tuple, list))
assert isinstance(band2clip, int) or band2clip is None
......@@ -139,7 +139,6 @@ def get_array_at_mapPosOLD(arr, arr_gt, arr_prj, mapBounds, mapBounds_prj, band2
:param fillVal:
:return:
"""
# [print(i,'\n') for i in [arr, arr_gt, arr_prj, mapBounds, mapBounds_prj]]
# check if requested bounds have the same projection like the array
samePrj = prj_equal(arr_prj, mapBounds_prj)
......@@ -217,7 +216,6 @@ def get_array_at_mapPos(arr, arr_gt, arr_prj, out_prj, mapBounds, mapBounds_prj=
:param progress:
:return:
"""
# check if reprojection is needed
mapBounds_prj = mapBounds_prj if mapBounds_prj else out_prj
samePrj = prj_equal(arr_prj, out_prj)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment