def coreg_info(self): """Return a dictionary containing everthing to correct the detected local displacements of the target image.""" if self._coreg_info: return self._coreg_info else: 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(), 'original map info': geotransform2mapinfo(self.imref.gt, self.imref.prj), 'reference projection': self.imref.prj, 'reference geotransform': self.imref.gt, 'reference grid': [[self.imref.gt[0], self.imref.gt[0] + self.imref.gt[1]], [self.imref.gt[3], self.imref.gt[3] + self.imref.gt[5]]], 'reference extent': { 'cols': self.imref.xgsd, 'rows': self.imref.ygsd }, # FIXME not needed anymore 'success': self.success } return self.coreg_info
def _get_updated_map_info_meanShifts(self) -> list: """Return the updated map info of the target image, shifted on the basis of the mean X/Y shifts.""" original_map_info = geotransform2mapinfo(self.im2shift.gt, self.im2shift.prj) updated_map_info = copy(original_map_info) updated_map_info[3] = str( float(original_map_info[3]) + self.tiepoint_grid.mean_x_shift_map) updated_map_info[4] = str( float(original_map_info[4]) + self.tiepoint_grid.mean_y_shift_map) return updated_map_info
def __init__(self, im2shift: Union[GeoArray, str], coreg_results: dict, **kwargs) -> None: """Get an instance of DESHIFTER. :param im2shift: path of an image to be de-shifted or alternatively a GeoArray object :param dict coreg_results: the results of the co-registration as given by COREG.coreg_info or COREG_LOCAL.coreg_info :keyword int path_out: /output/directory/filename for coregistered results :keyword str fmt_out: raster file format for output file. ignored if path_out is None. can be any GDAL compatible raster file format (e.g. 'ENVI', 'GTIFF'; default: ENVI) :keyword list out_crea_options: GDAL creation options for the output image, e.g., ["QUALITY=20", "REVERSIBLE=YES", "WRITE_METADATA=YES"] :keyword int band2process: The index of the band to be processed within the given array (starts with 1), default = None (all bands are processed) :keyword float nodata: no data value of the image to be de-shifted :keyword float out_gsd: output pixel size in units of the reference coordinate system (default = pixel size of the input array), given values are overridden by match_gsd=True :keyword bool align_grids: True: align the input coordinate grid to the reference (does not affect the output pixel size as long as input and output pixel sizes are compatible (5:30 or 10:30 but not 4:30), default = False :keyword bool match_gsd: True: match the input pixel size to the reference pixel size, default = False :keyword list target_xyGrid: a list with an x-grid and a y-grid like [[15,45], [15,45]]. This overrides 'out_gsd', 'align_grids' and 'match_gsd'. :keyword int min_points_local_corr: number of valid tie points, below which a global shift correction is performed instead of a local correction (global X/Y shift is then computed as the mean shift of the remaining points) (default: 5 tie points) :keyword str resamp_alg: the resampling algorithm to be used if neccessary (valid algorithms: nearest, bilinear, cubic, cubic_spline, lanczos, average, mode, max, min, med, q1, q3) :keyword bool cliptoextent: True: clip the input image to its actual bounds while deleting possible no data areas outside of the actual bounds, default = False :keyword list clipextent: xmin, ymin, xmax, ymax - if given the calculation of the actual bounds is skipped. The given coordinates are automatically snapped to the output grid. :keyword int CPUs: number of CPUs to use (default: None, which means 'all CPUs available') :keyword bool progress: show progress bars (default: True) :keyword bool v: verbose mode (default: False) :keyword bool q: quiet mode (default: False) """ # private attributes self._grids_alignable = None # store args / kwargs self.init_args = dict([ x for x in locals().items() if x[0] != "self" and not x[0].startswith('__') ]) self.init_kwargs = self.init_args['kwargs'] # unpack args self.im2shift = im2shift if isinstance( im2shift, GeoArray) else GeoArray(im2shift) self.GCPList = coreg_results[ 'GCPList'] if 'GCPList' in coreg_results else None self.ref_gt = coreg_results['reference geotransform'] self.ref_grid = coreg_results['reference grid'] self.ref_prj = coreg_results['reference projection'] # unpack kwargs self.path_out = kwargs.get('path_out', None) self.fmt_out = kwargs.get('fmt_out', 'ENVI') self.out_creaOpt = kwargs.get('out_crea_options', []) self.band2process = kwargs.get('band2process', None) # starts with 1 # FIXME why? self.band2process = \ self.band2process - 1 if self.band2process is not None else None # internally handled as band index self.nodata = kwargs.get('nodata', self.im2shift.nodata) self.align_grids = kwargs.get('align_grids', False) self.min_points_local_corr = kwargs.get('min_points_local_corr', 5) self.rspAlg = kwargs.get('resamp_alg', 'cubic') # TODO accept also integers self.cliptoextent = kwargs.get('cliptoextent', False) self.clipextent = kwargs.get('clipextent', None) self.CPUs = kwargs.get('CPUs', None) self.v = kwargs.get('v', False) self.q = kwargs.get('q', False) if not self.v else False # overridden by v self.progress = kwargs.get( 'progress', True) if not self.q else False # overridden by q self.im2shift.nodata = kwargs.get('nodata', self.im2shift.nodata) self.im2shift.q = self.q self.shift_prj = self.im2shift.projection self.shift_gt = list(self.im2shift.geotransform) # in case of local shift correction and local coreg results contain less points than min_points_local_corr: # force global correction based on mean X/Y shifts if 'GCPList' in coreg_results and len( coreg_results['GCPList']) < self.min_points_local_corr: warnings.warn( 'Only %s valid tie point(s) could be identified. A local shift correction is therefore not ' 'reasonable and could cause artifacts in the output image. The target image is ' 'corrected globally with the mean X/Y shift of %.3f/%.3f pixels.' % (len(self.GCPList), coreg_results['mean_shifts_px']['x'], coreg_results['mean_shifts_px']['y'])) self.GCPList = None coreg_results['updated map info'] = coreg_results[ 'updated map info means'] # in case of global shift correction -> the updated map info from coreg_results already has the final map info # BUT: this will be updated in correct_shifts() if clipextent is given or warping is needed if not self.GCPList: mapI = coreg_results['updated map info'] self.updated_map_info = mapI or geotransform2mapinfo( self.shift_gt, self.shift_prj) self.updated_gt = mapinfo2geotransform( self.updated_map_info) or self.shift_gt self.original_map_info = coreg_results['original map info'] self.updated_projection = self.ref_prj self.out_grid = self._get_out_grid( ) # needs self.ref_grid, self.im2shift self.out_gsd = [ abs(self.out_grid[0][1] - self.out_grid[0][0]), abs(self.out_grid[1][1] - self.out_grid[1][0]) ] # xgsd, ygsd # assertions assert self.rspAlg in _dict_rspAlg_rsp_Int.keys(), \ "'%s' is not a supported resampling algorithm." % self.rspAlg if self.band2process is not None: assert self.im2shift.bands - 1 >= self.band2process >= 0, \ "The %s '%s' has %s %s. So 'band2process' must be %s%s. Got %s." \ % (self.im2shift.__class__.__name__, self.im2shift.basename, self.im2shift.bands, 'bands' if self.im2shift.bands > 1 else 'band', 'between 1 and ' if self.im2shift.bands > 1 else '', self.im2shift.bands, self.band2process + 1) # set defaults for general class attributes self.is_shifted = False # this is not included in COREG.coreg_info self.is_resampled = False # this is not included in COREG.coreg_info self.tracked_errors = [] self.arr_shifted = None # set by self.correct_shifts self.GeoArray_shifted = None # set by self.correct_shifts
def correct_shifts(self) -> collections.OrderedDict: if not self.q: print('Correcting geometric shifts...') t_start = time.time() if not self.warping_needed: """NO RESAMPLING NEEDED""" self.is_shifted = True self.is_resampled = False xmin, ymin, xmax, ymax = self._get_out_extent() if not self.q: print( "NOTE: The detected shift is corrected by updating the map info of the target image only, i.e., " "without any resampling. Set the 'align_grids' parameter to True if you need the target and the " "reference coordinate grids to be aligned.") if self.cliptoextent: # TODO validate results # TODO -> output extent does not seem to be the requested one! (only relevant if align_grids=False) # get shifted array shifted_geoArr = GeoArray(self.im2shift[:], tuple(self.updated_gt), self.shift_prj) # clip with target extent # NOTE: get_mapPos() does not perform any resampling as long as source and target projection are equal self.arr_shifted, self.updated_gt, self.updated_projection = \ shifted_geoArr.get_mapPos((xmin, ymin, xmax, ymax), self.shift_prj, fillVal=self.nodata, band2get=self.band2process) self.updated_map_info = geotransform2mapinfo( self.updated_gt, self.updated_projection) else: # array keeps the same; updated gt and prj are taken from coreg_info self.arr_shifted = self.im2shift[:, :, self.band2process] \ if self.band2process is not None else self.im2shift[:] out_geoArr = GeoArray(self.arr_shifted, self.updated_gt, self.updated_projection, q=self.q) out_geoArr.nodata = self.nodata # equals self.im2shift.nodata after __init__() out_geoArr.metadata = self.im2shift.metadata[[self.band2process]] \ if self.band2process is not None else self.im2shift.metadata self.GeoArray_shifted = out_geoArr if self.path_out: out_geoArr.save(self.path_out, fmt=self.fmt_out) else: # FIXME equal_prj==False ist noch NICHT implementiert """RESAMPLING NEEDED""" # FIXME avoid reading the whole band if clip_extent is passed in_arr = self.im2shift[:, :, self.band2process] \ if self.band2process is not None and self.im2shift.ndim == 3 else self.im2shift[:] if not self.GCPList: # apply XY-shifts to input image gt 'shift_gt' in order to correct the shifts before warping self.shift_gt[0], self.shift_gt[3] = self.updated_gt[ 0], self.updated_gt[3] # get resampled array out_arr, out_gt, out_prj = \ warp_ndarray(in_arr, self.shift_gt, self.shift_prj, self.ref_prj, rspAlg=_dict_rspAlg_rsp_Int[self.rspAlg], in_nodata=self.nodata, out_nodata=self.nodata, out_gsd=self.out_gsd, out_bounds=self._get_out_extent(), # always returns an extent snapped to the target grid gcpList=self.GCPList, # polynomialOrder=str(3), # options='-refine_gcps 500 1.9', # warpOptions=['-refine_gcps 500 1.9'], # options='-wm 10000',# -order 3', # options=['-order 3'], # options=['GDAL_CACHEMAX 800 '], # warpMemoryLimit=125829120, # 120MB CPUs=self.CPUs, progress=self.progress, q=self.q) out_geoArr = GeoArray(out_arr, out_gt, out_prj, q=self.q) out_geoArr.nodata = self.nodata # equals self.im2shift.nodata after __init__() out_geoArr.metadata = self.im2shift.metadata[[self.band2process]] \ if self.band2process is not None else self.im2shift.metadata self.arr_shifted = out_arr self.updated_gt = out_gt self.updated_projection = out_prj self.updated_map_info = geotransform2mapinfo(out_gt, out_prj) self.GeoArray_shifted = out_geoArr self.is_shifted = True self.is_resampled = True if self.path_out: out_geoArr.save(self.path_out, fmt=self.fmt_out, creationOptions=self.out_creaOpt) # validation if not is_coord_grid_equal( self.updated_gt, *self.out_grid, tolerance=1.e8): raise RuntimeError( 'DESHIFTER output dataset has not the desired target pixel grid. Target grid ' 'was %s. Output geotransform is %s.' % (str(self.out_grid), str(self.updated_gt))) # TODO to be continued (extent, map info, ...) if self.v: print('Time for shift correction: %.2fs' % (time.time() - t_start)) return self.deshift_results