Commit d5f8b455 authored by Daniel Scheffler's avatar Daniel Scheffler
Browse files

Fixed an unhelpful error message in case no coregistration point can be placed...


Fixed an unhelpful error message in case no coregistration point can be placed within an image area usable for coregistration due to the provided bad data mask. Fixed some wrong type hints. Added COREG_LOCAL.calculate_spatial_shifts() allowing to explicitly compute the shifts instead of implicitly running the getter properties. This improves API clarity and facilitates debugging. Added sphinx-autodoc-typehints to doc requirements.
Signed-off-by: Daniel Scheffler's avatarDaniel Scheffler <danschef@gfz-potsdam.de>
parent 35823252
Pipeline #14720 failed with stage
in 1 minute and 56 seconds
...@@ -1244,7 +1244,7 @@ class COREG(object): ...@@ -1244,7 +1244,7 @@ class COREG(object):
self._ssim_improved = has_improved self._ssim_improved = has_improved
def calculate_spatial_shifts(self): def calculate_spatial_shifts(self):
# type: (COREG) -> str # type: () -> str
"""Compute the global X/Y shift between reference and the target image within the matching window. """Compute the global X/Y shift between reference and the target image within the matching window.
:return: 'success' or 'fail' :return: 'success' or 'fail'
...@@ -1419,7 +1419,7 @@ class COREG(object): ...@@ -1419,7 +1419,7 @@ class COREG(object):
return inv_coreg_info return inv_coreg_info
def correct_shifts(self): def correct_shifts(self):
# type: (COREG) -> dict # type: () -> dict
"""Correct the already calculated X/Y shift of the target image. """Correct the already calculated X/Y shift of the target image.
:return: COREG.deshift_results (dictionary) :return: COREG.deshift_results (dictionary)
......
...@@ -72,8 +72,8 @@ class COREG_LOCAL(object): ...@@ -72,8 +72,8 @@ class COREG_LOCAL(object):
ignore_errors=True): ignore_errors=True):
"""Get an instance of COREG_LOCAL. """Get an instance of COREG_LOCAL.
:param im_ref(str, GeoArray): source path of reference image (any GDAL compatible image format is supported) :param im_ref(str | GeoArray): source path of reference image (any GDAL compatible image format is supported)
:param im_tgt(str, GeoArray): source path of image to be shifted (any GDAL compatible image format is :param im_tgt(str | GeoArray): source path of image to be shifted (any GDAL compatible image format is
supported) supported)
:param grid_res: tie point grid resolution in pixels of the target image (x-direction) :param grid_res: tie point grid resolution in pixels of the target image (x-direction)
:param max_points(int): maximum number of points used to find coregistration tie points :param max_points(int): maximum number of points used to find coregistration tie points
...@@ -151,14 +151,14 @@ class COREG_LOCAL(object): ...@@ -151,14 +151,14 @@ class COREG_LOCAL(object):
given) given)
:param binary_ws(bool): use binary X/Y dimensions for the matching window (default: True) :param binary_ws(bool): use binary X/Y dimensions for the matching window (default: True)
:param force_quadratic_win(bool): force a quadratic matching window (default: 1) :param force_quadratic_win(bool): force a quadratic matching window (default: 1)
:param mask_baddata_ref(str, BadDataMask): :param mask_baddata_ref(str | BadDataMask):
path to a 2D boolean mask file (or an instance of BadDataMask) for the path to a 2D boolean mask file (or an instance of BadDataMask) for the
reference image where all bad data pixels (e.g. clouds) are marked with reference image where all bad data pixels (e.g. clouds) are marked with
True and the remaining pixels with False. Must have the same geographic True and the remaining pixels with False. Must have the same geographic
extent and projection like 'im_ref'. The mask is used to check if the extent and projection like 'im_ref'. The mask is used to check if the
chosen matching window position is valid in the sense of useful data. chosen matching window position is valid in the sense of useful data.
Otherwise this window position is rejected. Otherwise this window position is rejected.
:param mask_baddata_tgt(str, BadDataMask): :param mask_baddata_tgt(str | BadDataMask):
path to a 2D boolean mask file (or an instance of BadDataMask) for the path to a 2D boolean mask file (or an instance of BadDataMask) for the
image to be shifted where all bad data pixels (e.g. clouds) are marked image to be shifted where all bad data pixels (e.g. clouds) are marked
with True and the remaining pixels with False. Must have the same with True and the remaining pixels with False. Must have the same
...@@ -317,25 +317,7 @@ class COREG_LOCAL(object): ...@@ -317,25 +317,7 @@ class COREG_LOCAL(object):
if self._tiepoint_grid: if self._tiepoint_grid:
return self._tiepoint_grid return self._tiepoint_grid
else: else:
self._tiepoint_grid = Tie_Point_Grid(self.COREG_obj, self.grid_res, self.calculate_spatial_shifts()
max_points=self.max_points,
outFillVal=self.outFillVal,
resamp_alg_calc=self.rspAlg_calc,
tieP_filter_level=self.tieP_filter_level,
outlDetect_settings=dict(
min_reliability=self.min_reliability,
rs_max_outlier=self.rs_max_outlier,
rs_tolerance=self.rs_tolerance),
dir_out=self.projectDir,
CPUs=self.CPUs,
progress=self.progress,
v=self.v,
q=self.q)
self._tiepoint_grid.get_CoRegPoints_table()
if self.v:
print('Visualizing CoReg points grid...')
self.view_CoRegPoints(figsize=(10, 10))
return self._tiepoint_grid return self._tiepoint_grid
@property @property
...@@ -350,10 +332,30 @@ class COREG_LOCAL(object): ...@@ -350,10 +332,30 @@ class COREG_LOCAL(object):
@property @property
def success(self): def success(self):
self._success = self.tiepoint_grid.GCPList != [] self._success = self.tiepoint_grid.GCPList != []
if not self._success and not self.q:
warnings.warn('No valid GCPs could by identified.')
return self._success return self._success
def calculate_spatial_shifts(self):
self._tiepoint_grid = \
Tie_Point_Grid(self.COREG_obj, self.grid_res,
max_points=self.max_points,
outFillVal=self.outFillVal,
resamp_alg_calc=self.rspAlg_calc,
tieP_filter_level=self.tieP_filter_level,
outlDetect_settings=dict(
min_reliability=self.min_reliability,
rs_max_outlier=self.rs_max_outlier,
rs_tolerance=self.rs_tolerance),
dir_out=self.projectDir,
CPUs=self.CPUs,
progress=self.progress,
v=self.v,
q=self.q)
self._tiepoint_grid.get_CoRegPoints_table()
if self.v:
print('Visualizing CoReg points grid...')
self.view_CoRegPoints(figsize=(10, 10))
def show_image_footprints(self): def show_image_footprints(self):
"""Show a web map containing the calculated footprints and overlap area of the input images. """Show a web map containing the calculated footprints and overlap area of the input images.
...@@ -541,7 +543,7 @@ class COREG_LOCAL(object): ...@@ -541,7 +543,7 @@ class COREG_LOCAL(object):
else: else:
plt.close(fig) plt.close(fig)
def view_CoRegPoints_folium(self, attribute2plot='ABS_SHIFT', cmap=None, exclude_fillVals=True): def view_CoRegPoints_folium(self, attribute2plot='ABS_SHIFT'):
warnings.warn(UserWarning('This function is still under construction and may not work as expected!')) warnings.warn(UserWarning('This function is still under construction and may not work as expected!'))
assert self.CoRegPoints_table is not None, 'Calculate tie point grid first!' assert self.CoRegPoints_table is not None, 'Calculate tie point grid first!'
...@@ -596,13 +598,18 @@ class COREG_LOCAL(object): ...@@ -596,13 +598,18 @@ class COREG_LOCAL(object):
if self._coreg_info: if self._coreg_info:
return self._coreg_info return self._coreg_info
else: else:
if not self._tiepoint_grid:
self.calculate_spatial_shifts()
TPG = self._tiepoint_grid
self._coreg_info = { self._coreg_info = {
'GCPList': self.tiepoint_grid.GCPList, 'GCPList': TPG.GCPList,
'mean_shifts_px': {'x': self.tiepoint_grid.mean_x_shift_px, 'mean_shifts_px': {'x': TPG.mean_x_shift_px if TPG.GCPList else None,
'y': self.tiepoint_grid.mean_y_shift_px}, 'y': TPG.mean_y_shift_px if TPG.GCPList else None},
'mean_shifts_map': {'x': self.tiepoint_grid.mean_x_shift_map, 'mean_shifts_map': {'x': TPG.mean_x_shift_map if TPG.GCPList else None,
'y': self.tiepoint_grid.mean_y_shift_map}, 'y': TPG.mean_y_shift_map if TPG.GCPList else None},
'updated map info means': self._get_updated_map_info_meanShifts(), 'updated map info means': self._get_updated_map_info_meanShifts() if TPG.GCPList else None,
'original map info': geotransform2mapinfo(self.imref.gt, self.imref.prj), 'original map info': geotransform2mapinfo(self.imref.gt, self.imref.prj),
'reference projection': self.imref.prj, 'reference projection': self.imref.prj,
'reference geotransform': self.imref.gt, 'reference geotransform': self.imref.gt,
...@@ -625,13 +632,14 @@ class COREG_LOCAL(object): ...@@ -625,13 +632,14 @@ class COREG_LOCAL(object):
the mean shift of the remaining points)(default: 5 tie points) the mean shift of the remaining points)(default: 5 tie points)
:return: :return:
""" """
coreg_info = self.coreg_info if not self._tiepoint_grid:
self.calculate_spatial_shifts()
if self.tiepoint_grid.GCPList: if self.tiepoint_grid.GCPList:
if max_GCP_count: if max_GCP_count:
coreg_info['GCPList'] = coreg_info['GCPList'][:max_GCP_count] self.coreg_info['GCPList'] = self.coreg_info['GCPList'][:max_GCP_count]
DS = DESHIFTER(self.im2shift, coreg_info, DS = DESHIFTER(self.im2shift, self.coreg_info,
path_out=self.path_out, path_out=self.path_out,
fmt_out=self.fmt_out, fmt_out=self.fmt_out,
out_crea_options=self.out_creaOpt, out_crea_options=self.out_creaOpt,
......
...@@ -52,7 +52,7 @@ class DESHIFTER(object): ...@@ -52,7 +52,7 @@ class DESHIFTER(object):
def __init__(self, im2shift, coreg_results, **kwargs): def __init__(self, im2shift, coreg_results, **kwargs):
"""Get an instance of DESHIFTER. """Get an instance of DESHIFTER.
:param (str, GeoArray) im2shift: :param (str | GeoArray) im2shift:
path of an image to be de-shifted or alternatively a GeoArray object path of an image to be de-shifted or alternatively a GeoArray object
:param (dict) coreg_results : :param (dict) coreg_results :
......
...@@ -229,10 +229,9 @@ class Tie_Point_Grid(object): ...@@ -229,10 +229,9 @@ class Tie_Point_Grid(object):
GDF = GDF[inliers].copy() GDF = GDF[inliers].copy()
# GDF = GDF[GDF['geometry'].within(self.COREG_obj.overlap_poly.simplify(tolerance=15))] # works but much slower # GDF = GDF[GDF['geometry'].within(self.COREG_obj.overlap_poly.simplify(tolerance=15))] # works but much slower
# FIXME track that
assert not GDF.empty, 'No coregistration point could be placed within the overlap area. Check your input data!' assert not GDF.empty, 'No coregistration point could be placed within the overlap area. Check your input data!'
# exclude all point where bad data mask is True (e.g. points on clouds etc.) # exclude all points where bad data mask is True (e.g. points on clouds etc.)
orig_len_GDF = len(GDF) # length of GDF after dropping all points outside the overlap polygon orig_len_GDF = len(GDF) # length of GDF after dropping all points outside the overlap polygon
mapXY = np.array(GDF.loc[:, ['X_UTM', 'Y_UTM']]) mapXY = np.array(GDF.loc[:, ['X_UTM', 'Y_UTM']])
GDF['REF_BADDATA'] = self.COREG_obj.ref.mask_baddata.read_pointData(mapXY) \ GDF['REF_BADDATA'] = self.COREG_obj.ref.mask_baddata.read_pointData(mapXY) \
...@@ -242,8 +241,12 @@ class Tie_Point_Grid(object): ...@@ -242,8 +241,12 @@ class Tie_Point_Grid(object):
GDF = GDF[(~GDF['REF_BADDATA']) & (~GDF['TGT_BADDATA'])] GDF = GDF[(~GDF['REF_BADDATA']) & (~GDF['TGT_BADDATA'])]
if self.COREG_obj.ref.mask_baddata is not None or self.COREG_obj.shift.mask_baddata is not None: if self.COREG_obj.ref.mask_baddata is not None or self.COREG_obj.shift.mask_baddata is not None:
if not self.q: if not self.q:
print('According to the provided bad data mask(s) %s points of initially %s have been excluded.' if not GDF.empty:
% (orig_len_GDF - len(GDF), orig_len_GDF)) print('With respect to the provided bad data mask(s) %s points of initially %s have been excluded.'
% (orig_len_GDF - len(GDF), orig_len_GDF))
else:
warnings.warn('With respect to the provided bad data mask(s) no coregistration point could be '
'placed within an image area usable for coregistration.')
return GDF return GDF
...@@ -418,6 +421,12 @@ class Tie_Point_Grid(object): ...@@ -418,6 +421,12 @@ class Tie_Point_Grid(object):
self.CoRegPoints_table = GDF self.CoRegPoints_table = GDF
if not self.q:
if GDF.empty:
warnings.warn('No valid GCPs could by identified.')
else:
print("%d valid tie points remain after filtering." % len(GDF[GDF.OUTLIER.__eq__(False)]))
return self.CoRegPoints_table return self.CoRegPoints_table
def calc_rmse(self, include_outliers=False): def calc_rmse(self, include_outliers=False):
...@@ -592,9 +601,6 @@ class Tie_Point_Grid(object): ...@@ -592,9 +601,6 @@ class Tie_Point_Grid(object):
GDF_row.X_IM, GDF_row.Y_IM), axis=1) GDF_row.X_IM, GDF_row.Y_IM), axis=1)
self.GCPList = GDF.GCP.tolist() self.GCPList = GDF.GCP.tolist()
if not self.q:
print('Found %s valid tie points.' % len(self.GCPList))
return self.GCPList return self.GCPList
def test_if_singleprocessing_equals_multiprocessing_result(self): def test_if_singleprocessing_equals_multiprocessing_result(self):
......
...@@ -2,7 +2,7 @@ Local image co-registration ...@@ -2,7 +2,7 @@ Local image co-registration
*************************** ***************************
This local co-registration module of AROSICS has been designed to detect and correct geometric shifts present locally This local co-registration module of AROSICS has been designed to detect and correct geometric shifts present locally
in your input image. The class :class:`~arosics.COREG_LOCAL` calculates a grid of spatial shifts with points spread in your input image. The class :class:`arosics.COREG_LOCAL` calculates a grid of spatial shifts with points spread
over the whole overlap area of the input images. Based on this grid a correction of local shifts can be performed. over the whole overlap area of the input images. Based on this grid a correction of local shifts can be performed.
......
...@@ -64,7 +64,7 @@ req_setup = [ ...@@ -64,7 +64,7 @@ req_setup = [
# ipython is needed for testing interactive plotting # ipython is needed for testing interactive plotting
req_test = ['coverage', 'nose', 'nose2', 'nose-htmloutput', 'rednose', 'ipython'] req_test = ['coverage', 'nose', 'nose2', 'nose-htmloutput', 'rednose', 'ipython']
req_doc = ['sphinx-argparse', 'sphinx_rtd_theme'] req_doc = ['sphinx-argparse', 'sphinx_rtd_theme', 'sphinx-autodoc-typehints']
req_lint = ['flake8', 'pycodestyle', 'pydocstyle'] req_lint = ['flake8', 'pycodestyle', 'pydocstyle']
......
...@@ -80,9 +80,8 @@ class CompleteWorkflow_INTER1_S2A_S2A(unittest.TestCase): ...@@ -80,9 +80,8 @@ class CompleteWorkflow_INTER1_S2A_S2A(unittest.TestCase):
# get instance of COREG_LOCAL object # get instance of COREG_LOCAL object
CRL = COREG_LOCAL(self.ref_path, self.tgt_path, **self.coreg_kwargs) CRL = COREG_LOCAL(self.ref_path, self.tgt_path, **self.coreg_kwargs)
# use the getter of the CoRegPoints_table to calculate tie point grid # calculate tie point grid
# noinspection PyStatementEffect CRL.calculate_spatial_shifts()
CRL.CoRegPoints_table
# test tie point grid visualization # test tie point grid visualization
with warnings.catch_warnings(): with warnings.catch_warnings():
...@@ -119,6 +118,8 @@ class CompleteWorkflow_INTER1_S2A_S2A(unittest.TestCase): ...@@ -119,6 +118,8 @@ class CompleteWorkflow_INTER1_S2A_S2A(unittest.TestCase):
# get instance of COREG_LOCAL object # get instance of COREG_LOCAL object
CRL = COREG_LOCAL(ref, tgt, **dict(CPUs=32, CRL = COREG_LOCAL(ref, tgt, **dict(CPUs=32,
**self.coreg_kwargs)) **self.coreg_kwargs))
CRL.calculate_spatial_shifts()
# use the getter of the CoRegPoints_table to calculate tie point grid # use the getter of the CoRegPoints_table to calculate tie point grid
# noinspection PyStatementEffect # noinspection PyStatementEffect
......
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