From d5f8b455d353c37068b70893c70e5c8e53447c5d Mon Sep 17 00:00:00 2001 From: Daniel Scheffler Date: Mon, 19 Oct 2020 17:47:07 +0200 Subject: [PATCH 1/2] 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 --- arosics/CoReg.py | 4 +- arosics/CoReg_local.py | 78 +++++++++++++++++++++----------------- arosics/DeShifter.py | 2 +- arosics/Tie_Point_Grid.py | 20 ++++++---- docs/usage/local_coreg.rst | 2 +- setup.py | 2 +- tests/test_COREG_LOCAL.py | 7 ++-- 7 files changed, 65 insertions(+), 50 deletions(-) diff --git a/arosics/CoReg.py b/arosics/CoReg.py index 9009190..199f379 100755 --- a/arosics/CoReg.py +++ b/arosics/CoReg.py @@ -1244,7 +1244,7 @@ def ssim_improved(self, has_improved): self._ssim_improved = has_improved 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. :return: 'success' or 'fail' @@ -1419,7 +1419,7 @@ def _get_inverted_coreg_info(self): return inv_coreg_info def correct_shifts(self): - # type: (COREG) -> dict + # type: () -> dict """Correct the already calculated X/Y shift of the target image. :return: COREG.deshift_results (dictionary) diff --git a/arosics/CoReg_local.py b/arosics/CoReg_local.py index b90c771..98f61d4 100644 --- a/arosics/CoReg_local.py +++ b/arosics/CoReg_local.py @@ -72,8 +72,8 @@ def __init__(self, im_ref, im_tgt, grid_res, max_points=None, window_size=(256, ignore_errors=True): """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_tgt(str, GeoArray): source path of image to be shifted (any GDAL compatible image format is + :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 supported) :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 @@ -151,14 +151,14 @@ def __init__(self, im_ref, im_tgt, grid_res, max_points=None, window_size=(256, given) :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 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 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 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. 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 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 @@ -317,25 +317,7 @@ def tiepoint_grid(self): if self._tiepoint_grid: return self._tiepoint_grid else: - 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)) + self.calculate_spatial_shifts() return self._tiepoint_grid @property @@ -350,10 +332,30 @@ def CoRegPoints_table(self): @property def success(self): 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 + 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): """Show a web map containing the calculated footprints and overlap area of the input images. @@ -541,7 +543,7 @@ def view_CoRegPoints(self, shapes2plot='points', attribute2plot='ABS_SHIFT', cma else: 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!')) assert self.CoRegPoints_table is not None, 'Calculate tie point grid first!' @@ -596,13 +598,18 @@ def coreg_info(self): if self._coreg_info: return self._coreg_info else: + if not self._tiepoint_grid: + self.calculate_spatial_shifts() + + TPG = self._tiepoint_grid + self._coreg_info = { - 'GCPList': self.tiepoint_grid.GCPList, - 'mean_shifts_px': {'x': self.tiepoint_grid.mean_x_shift_px, - 'y': self.tiepoint_grid.mean_y_shift_px}, - 'mean_shifts_map': {'x': self.tiepoint_grid.mean_x_shift_map, - 'y': self.tiepoint_grid.mean_y_shift_map}, - 'updated map info means': self._get_updated_map_info_meanShifts(), + 'GCPList': TPG.GCPList, + 'mean_shifts_px': {'x': TPG.mean_x_shift_px if TPG.GCPList else None, + 'y': TPG.mean_y_shift_px if TPG.GCPList else None}, + 'mean_shifts_map': {'x': TPG.mean_x_shift_map if TPG.GCPList else None, + 'y': TPG.mean_y_shift_map if TPG.GCPList else None}, + '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), 'reference projection': self.imref.prj, 'reference geotransform': self.imref.gt, @@ -625,13 +632,14 @@ def correct_shifts(self, max_GCP_count=None, cliptoextent=False, min_points_loca the mean shift of the remaining points)(default: 5 tie points) :return: """ - coreg_info = self.coreg_info + if not self._tiepoint_grid: + self.calculate_spatial_shifts() if self.tiepoint_grid.GCPList: 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, fmt_out=self.fmt_out, out_crea_options=self.out_creaOpt, diff --git a/arosics/DeShifter.py b/arosics/DeShifter.py index f2b077d..b064b71 100644 --- a/arosics/DeShifter.py +++ b/arosics/DeShifter.py @@ -52,7 +52,7 @@ class DESHIFTER(object): def __init__(self, im2shift, coreg_results, **kwargs): """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 :param (dict) coreg_results : diff --git a/arosics/Tie_Point_Grid.py b/arosics/Tie_Point_Grid.py index 78113a5..087c985 100755 --- a/arosics/Tie_Point_Grid.py +++ b/arosics/Tie_Point_Grid.py @@ -229,10 +229,9 @@ def _exclude_bad_XYpos(self, GDF): GDF = GDF[inliers].copy() # 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!' - # 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 mapXY = np.array(GDF.loc[:, ['X_UTM', 'Y_UTM']]) GDF['REF_BADDATA'] = self.COREG_obj.ref.mask_baddata.read_pointData(mapXY) \ @@ -242,8 +241,12 @@ def _exclude_bad_XYpos(self, GDF): 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 not self.q: - print('According to the provided bad data mask(s) %s points of initially %s have been excluded.' - % (orig_len_GDF - len(GDF), orig_len_GDF)) + if not GDF.empty: + 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 @@ -418,6 +421,12 @@ def get_CoRegPoints_table(self): 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 def calc_rmse(self, include_outliers=False): @@ -592,9 +601,6 @@ def to_GCPList(self): GDF_row.X_IM, GDF_row.Y_IM), axis=1) self.GCPList = GDF.GCP.tolist() - if not self.q: - print('Found %s valid tie points.' % len(self.GCPList)) - return self.GCPList def test_if_singleprocessing_equals_multiprocessing_result(self): diff --git a/docs/usage/local_coreg.rst b/docs/usage/local_coreg.rst index 64b193f..7ff057d 100644 --- a/docs/usage/local_coreg.rst +++ b/docs/usage/local_coreg.rst @@ -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 -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. diff --git a/setup.py b/setup.py index 1db9ce8..e330957 100644 --- a/setup.py +++ b/setup.py @@ -64,7 +64,7 @@ # ipython is needed for testing interactive plotting 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'] diff --git a/tests/test_COREG_LOCAL.py b/tests/test_COREG_LOCAL.py index 3cda2fd..25c36db 100644 --- a/tests/test_COREG_LOCAL.py +++ b/tests/test_COREG_LOCAL.py @@ -80,9 +80,8 @@ def test_calculation_of_tie_point_grid(self): # get instance of COREG_LOCAL object CRL = COREG_LOCAL(self.ref_path, self.tgt_path, **self.coreg_kwargs) - # use the getter of the CoRegPoints_table to calculate tie point grid - # noinspection PyStatementEffect - CRL.CoRegPoints_table + # calculate tie point grid + CRL.calculate_spatial_shifts() # test tie point grid visualization with warnings.catch_warnings(): @@ -119,6 +118,8 @@ def test_calculation_of_tie_point_grid_float_coords(self): # get instance of COREG_LOCAL object CRL = COREG_LOCAL(ref, tgt, **dict(CPUs=32, **self.coreg_kwargs)) + CRL.calculate_spatial_shifts() + # use the getter of the CoRegPoints_table to calculate tie point grid # noinspection PyStatementEffect From e9d7b5d5b6e8b59861d87518733655bf81062c9b Mon Sep 17 00:00:00 2001 From: Daniel Scheffler Date: Mon, 19 Oct 2020 17:54:51 +0200 Subject: [PATCH 2/2] Fixed linting. Updated version info. Signed-off-by: Daniel Scheffler --- arosics/version.py | 4 ++-- tests/test_COREG_LOCAL.py | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/arosics/version.py b/arosics/version.py index f07b546..9a85d93 100644 --- a/arosics/version.py +++ b/arosics/version.py @@ -22,5 +22,5 @@ # with this program. If not, see . -__version__ = '1.0.2' -__versionalias__ = '2020-10-12_02' +__version__ = '1.0.3' +__versionalias__ = '2020-10-19_01' diff --git a/tests/test_COREG_LOCAL.py b/tests/test_COREG_LOCAL.py index 25c36db..45be13a 100644 --- a/tests/test_COREG_LOCAL.py +++ b/tests/test_COREG_LOCAL.py @@ -121,11 +121,6 @@ def test_calculation_of_tie_point_grid_float_coords(self): CRL.calculate_spatial_shifts() - # use the getter of the CoRegPoints_table to calculate tie point grid - # noinspection PyStatementEffect - CRL.CoRegPoints_table - - # if __name__ == '__main__': # unittest.main(argv=['first-arg-is-ignored'],exit=False, verbosity=2) #