# -*- coding: utf-8 -*- """ Created on Fri Jan 12 16:20:15 2018 @author: pyang """ from geoarray import GeoArray from arosics import COREG #print(dir(GeoArray)) #im_reference = r'C:\DEV\FuelMapping\Arc_Version_Test\Hill_Country_Pilot\AC_Images\S2A_MSIL2A_20170130T171541_N0204_R112_T14RMT_20170130T171741.SAFE\GRANULE\L2A_T14RMT_A008403_20170130T171541\IMG_DATA\R10m\L2A_T14RMT_20170130T171541_B02_10m.jp2' #im_reference = r'C:\DEV\FuelMapping\registration\Sample_Images\L2A_T14RMT_20170430T171301_B04_10m__shifted_to__L2A_T14RMT_20171022T171339_B04_10m.tif' #im_target = '/path/to/your/tgt_image.bsq' im_reference = r'c:\DEV\FuelMapping\registration\Multiband_Images\S2B_MSIL2A_T14RMT_N0205_20171022_10m.tif' geoArr = GeoArray(im_reference) #print(help(geoArr)) #CR = COREG(im_reference, im_target, wp=(354223, 5805559), ws=(256,256)) #CR.calculate_spatial_shifts() #for single band image #im_reference = r'C:\DEV\FuelMapping\registration\Sample_Images\L2A_T14RMT_20171022T171339_B04_10m.jp2' #im_target = r'C:\DEV\FuelMapping\registration\Sample_Images\L2A_T14RMT_20170430T171301_B04_10m.jp2' #shiftedfn = im_target[0:-4] + "_pyshifted" + '.tif' #print(shiftedfn) ##print(help(CRL.correct_shifts())) #for full band image #im_reference = r'c:\DEV\FuelMapping\registration\Multiband_Images\S2B_MSIL2A_T14RMT_N0205_20171022_10m.tif' #im_target = r'c:\DEV\FuelMapping\registration\Multiband_Images\S2A_MSIL2A_T14RMT_N0205_20170430_10m.tif' #shiftedfn = im_target[0:-4] + "_pyfullbandshifted" + '.tif' #CRL = COREG(im_reference,im_target,shiftedfn,fmt_out='GTiff') #CRL.correct_shifts() #shiftedarray.save(shiftedfn,fmt='GTiff',creationOptions="WRITE_METADATA=YES") #
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
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 __init__(self, im_ref, im_tgt, grid_res, max_points=None, window_size=(256, 256), path_out=None, fmt_out='ENVI', out_crea_options=None, projectDir=None, r_b4match=1, s_b4match=1, max_iter=5, max_shift=5, tieP_filter_level=3, min_reliability=60, rs_max_outlier=10, rs_tolerance=2.5, align_grids=True, match_gsd=False, out_gsd=None, target_xyGrid=None, resamp_alg_deshift='cubic', resamp_alg_calc='cubic', footprint_poly_ref=None, footprint_poly_tgt=None, data_corners_ref=None, data_corners_tgt=None, outFillVal=-9999, nodata=(None, None), calc_corners=True, binary_ws=True, force_quadratic_win=True, mask_baddata_ref=None, mask_baddata_tgt=None, CPUs=None, progress=True, v=False, q=False, 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 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 NOTE: Points are selected randomly from the given point grid (specified by 'grid_res'). If the point does not provide enough points, all available points are chosen. :param window_size(tuple): custom matching window size [pixels] (default: (256,256)) :param path_out(str): target path of the coregistered image - if None (default), no output is written to disk - if 'auto': /dir/of/im1/<im1>__shifted_to__<im0>.bsq :param fmt_out(str): 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). Refer to http://www.gdal.org/formats_list.html to get a full list of supported formats. :param out_crea_options(list): GDAL creation options for the output image, e.g. ["QUALITY=80", "REVERSIBLE=YES", "WRITE_METADATA=YES"] :param projectDir(str): name of a project directory where to store all the output results. If given, name is inserted into all automatically generated output paths. :param r_b4match(int): band of reference image to be used for matching (starts with 1; default: 1) :param s_b4match(int): band of shift image to be used for matching (starts with 1; default: 1) :param max_iter(int): maximum number of iterations for matching (default: 5) :param max_shift(int): maximum shift distance in reference image pixel units (default: 5 px) :param tieP_filter_level(int): filter tie points used for shift correction in different levels (default: 3). NOTE: lower levels are also included if a higher level is chosen - Level 0: no tie point filtering - Level 1: Reliablity filtering - filter all tie points out that have a low reliability according to internal tests - Level 2: SSIM filtering - filters all tie points out where shift correction does not increase image similarity within matching window (measured by mean structural similarity index) - Level 3: RANSAC outlier detection :param min_reliability(float): Tie point filtering: minimum reliability threshold, below which tie points are marked as false-positives (default: 60%) - accepts values between 0% (no reliability) and 100 % (perfect reliability) HINT: decrease this value in case of poor signal-to-noise ratio of your input data :param rs_max_outlier(float): RANSAC tie point filtering: proportion of expected outliers (default: 10%) :param rs_tolerance(float): RANSAC tie point filtering: percentage tolerance for max_outlier_percentage (default: 2.5%) :param out_gsd (float): 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 :param align_grids (bool): 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 = True :param match_gsd (bool): True: match the input pixel size to the reference pixel size, default = False :param target_xyGrid(list): a list with a target x-grid and a target y-grid like [[15,45], [15,45]] This overrides 'out_gsd', 'align_grids' and 'match_gsd'. :param resamp_alg_deshift(str) the resampling algorithm to be used for shift correction (if neccessary) valid algorithms: nearest, bilinear, cubic, cubic_spline, lanczos, average, mode, max, min, med, q1, q3 default: cubic :param resamp_alg_calc(str) the resampling algorithm to be used for all warping processes during calculation of spatial shifts (valid algorithms: nearest, bilinear, cubic, cubic_spline, lanczos, average, mode, max, min, med, q1, q3) default: cubic (highly recommended) :param footprint_poly_ref(str): footprint polygon of the reference image (WKT string or shapely.geometry.Polygon), e.g. 'POLYGON ((299999 6000000, 299999 5890200, 409799 5890200, 409799 6000000, 299999 6000000))' :param footprint_poly_tgt(str): footprint polygon of the image to be shifted (WKT string or shapely.geometry.Polygon) e.g. 'POLYGON ((299999 6000000, 299999 5890200, 409799 5890200, 409799 6000000, 299999 6000000))' :param data_corners_ref(list): map coordinates of data corners within reference image. ignored if footprint_poly_ref is given. :param data_corners_tgt(list): map coordinates of data corners within image to be shifted. ignored if footprint_poly_tgt is given. :param outFillVal(int): if given the generated tie point grid is filled with this value in case no match could be found during co-registration (default: -9999) :param nodata(tuple): no data values for reference image and image to be shifted :param calc_corners(bool): calculate true positions of the dataset corners in order to get a useful matching window position within the actual image overlap (default: True; deactivated if 'data_corners_im0' and 'data_corners_im1' are 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): 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): 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 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 CPUs(int): number of CPUs to use during calculation of tie point grid (default: None, which means 'all CPUs available') :param progress(bool): show progress bars (default: True) :param v(bool): verbose mode (default: False) :param q(bool): quiet mode (default: False) :param ignore_errors(bool): Useful for batch processing. (default: False) """ # assertions / input validation assert gdal.GetDriverByName( fmt_out), "'%s' is not a supported GDAL driver." % fmt_out if match_gsd and out_gsd: warnings.warn( "'-out_gsd' is ignored because '-match_gsd' is set.\n") if out_gsd: assert isinstance(out_gsd, list) and len( out_gsd) == 2, 'out_gsd must be a list with two values.' if PY2 and (CPUs is None or (isinstance(CPUs, int) and CPUs > 1)): CPUs = 1 warnings.warn( 'Multiprocessing is currently not supported for Python 2. Using singleprocessing.' ) self.params = dict([ x for x in locals().items() if x[0] != "self" and not x[0].startswith('__') ]) self.imref = GeoArray(im_ref, nodata=nodata[0], progress=progress, q=q) self.im2shift = GeoArray(im_tgt, nodata=nodata[1], progress=progress, q=q) self.path_out = path_out # updated by self.set_outpathes self.fmt_out = fmt_out self.out_creaOpt = out_crea_options self._projectDir = projectDir self.grid_res = grid_res self.max_points = max_points self.window_size = window_size self.max_shift = max_shift self.max_iter = max_iter self.tieP_filter_level = tieP_filter_level self.min_reliability = min_reliability self.rs_max_outlier = rs_max_outlier self.rs_tolerance = rs_tolerance self.align_grids = align_grids self.match_gsd = match_gsd self.out_gsd = out_gsd self.target_xyGrid = target_xyGrid self.rspAlg_DS = resamp_alg_deshift # TODO convert integers to strings self.rspAlg_calc = resamp_alg_calc self.calc_corners = calc_corners self.nodata = nodata self.outFillVal = outFillVal self.bin_ws = binary_ws self.force_quadratic_win = force_quadratic_win self.CPUs = CPUs self.path_verbose_out = '' # TODO self.v = v self.q = q if not v else False # overridden by v self.progress = progress if not q else False # overridden by v self.ignErr = ignore_errors # FIXME this is not yet implemented for COREG_LOCAL assert self.tieP_filter_level in range( 4), 'Invalid tie point filter level.' assert isinstance(self.imref, GeoArray) and isinstance(self.im2shift, GeoArray), \ 'Something went wrong with the creation of GeoArray instances for reference or target image. The created ' \ 'instances do not seem to belong to the GeoArray class. If you are working in Jupyter Notebook, reset ' \ 'the kernel and try again.' COREG.__dict__['_set_outpathes'](self, self.imref, self.im2shift) # make sure that the output directory of coregistered image is the project directory if a project directory is # given if path_out and projectDir and os.path.basename(self.path_out): self.path_out = os.path.join(self.projectDir, os.path.basename(self.path_out)) gdal.AllRegister() try: # ignore_errors must be False because in case COREG init fails, coregistration for the whole scene fails self.COREG_obj = COREG( self.imref, self.im2shift, ws=window_size, footprint_poly_ref=footprint_poly_ref, footprint_poly_tgt=footprint_poly_tgt, data_corners_ref=data_corners_ref, data_corners_tgt=data_corners_tgt, resamp_alg_calc=self.rspAlg_calc, calc_corners=calc_corners, r_b4match=r_b4match, s_b4match=s_b4match, max_iter=max_iter, max_shift=max_shift, nodata=nodata, mask_baddata_ref=None, # see below mask_baddata_tgt=None, CPUs=self.CPUs, force_quadratic_win=self.force_quadratic_win, binary_ws=self.bin_ws, progress=self.progress, v=v, q=q, ignore_errors=False) except Exception: warnings.warn( '\nFirst attempt to check if functionality of co-registration failed. Check your ' 'input data and parameters. The following error occurred:', stacklevel=3) raise if pyfftw: self.check_if_fftw_works() # add bad data mask # (mask is not added during initialization of COREG object in order to avoid bad data area errors there) if mask_baddata_ref is not None: self.COREG_obj.ref.mask_baddata = mask_baddata_ref if mask_baddata_tgt is not None: self.COREG_obj.shift.mask_baddata = mask_baddata_tgt self._tiepoint_grid = None # set by self.tiepoint_grid self._CoRegPoints_table = None # set by self.CoRegPoints_table self._coreg_info = None # set by self.coreg_info self.deshift_results = None # set by self.correct_shifts() self._success = None # set by self.success property
def test_shift_calculation_nonquadratic_pixels(self): """Test with default parameters - should compute X/Y shifts properly and write the de-shifted target image.""" # overwrite gt and prj ref = GeoArray(self.ref_path) ref.to_mem() ref.filePath = None ref.gt = [330000.00000001, 5.8932, 0.0, 5862000.0000001, 0.0, -10.1] tgt = GeoArray(self.tgt_path) tgt.to_mem() tgt.filePath = None tgt.gt = [335440.0000001, 5.8933, 0.0, 5866490.0000001, 0.0, -10.1] CR = self.run_shift_detection_correction( ref, tgt, **dict(self.coreg_kwargs, wp=(341500.0, 5861440.0), footprint_poly_ref=None, footprint_poly_tgt=None)) self.assertTrue(CR.success)
def test_shift_calculation_with_image_coords_only(self): """Test with default parameters - should compute X/Y shifts properly and write the de-shifted target image.""" # FIXME fails when executed alone # overwrite gt and prj ref = GeoArray(self.ref_path) ref.to_mem() ref.filePath = None ref.gt = [0, 1, 0, 0, 0, -1] ref.prj = '' tgt = GeoArray(self.tgt_path) tgt.to_mem() tgt.filePath = None tgt.gt = [0, 1, 0, 0, 0, -1] tgt.prj = '' CR = self.run_shift_detection_correction( ref, tgt, **dict(self.coreg_kwargs, wp=(1200, -1600), footprint_poly_ref=None, footprint_poly_tgt=None)) self.assertTrue(CR.success)
def to_sensor_geometry(self, lons: np.ndarray, lats: np.ndarray): GT = Geometry_Transformer(lons=lons, lats=lats, nprocs=self.CPUs) data_sensorgeo = GT.to_sensor_geometry(self.dem) return GeoArray(data_sensorgeo)
def regional_mean(a, region=None): """ Average along lon/lat input: - a: DimArray instance including lat, lon axis - region, optional: [lon, lat]: Point [lon_ll, lat_ll, lon_ur, lat_ur]: BoxRegion or Region instance output: - GeoArray instance, regional average Notes: ------ Will adjust longitude axis over [0, 360] or [-180, 180] as necessary Examples: --------- >>> lon = [0, 50., 100.] >>> lat = [0, 50.] >>> a = GeoArray([[1, 2, 4],[4, 5, 6]], lon=lon, lat=lat) >>> rm = regional_mean(a) >>> rm 3.3767428905946684 Which just weights data with cos(lat) >>> lon2, lat2 = np.meshgrid(lon, lat) >>> w = np.cos(np.radians(lat2)) >>> rm2 = np.sum(a.values*w)/np.sum(w) >>> round(rm2, 4) 3.3767 GeoArray detects the weights automatically with name "lat" >>> rm3 = a.mean() >>> round(rm3,4) 3.3767 >>> a.mean(weights=None) 3.6666666666666665 Also works for multi-dimensional data shaped in any kind of order >>> a = a.newaxis('z',[0,1,2]) >>> a = a.transpose(('lon','z','lat')) >>> regional_mean(a) geoarray: 3 non-null elements (0 null) dimensions: 'z' 0 / z (3): 0 to 2 array([ 3.37674289, 3.37674289, 3.37674289]) """ regobj = regmod.check(region) dims = ('lat','lon') a = GeoArray(a) # make it a GeoArray to check lon, lat if not set(dims).issubset(a.dims): raise ValueError("does not have lon, lat axes: {}".format(a)) # rearrange dimensions with dims first flatdims = [ax.name for ax in a.axes if ax.name not in dims] newdims = dims + tuple(flatdims) if a.dims != newdims: a = a.transpose(newdims) # If 2-D return a scalar if a.dims == ('lat', 'lon'): return regobj.mean(a.lon,a.lat, a.values) # flatten all except lat, lon, and make it the first axis agrp = a.group(flatdims, insert=0) grpaxis = agrp.axes[0] # grouped axis # iterate over the first, grouped axis results = [] for i in range(grpaxis.size): res = regobj.mean(a.lon,a.lat, agrp.values[i]) results.append(res) # flat object grp_ave = a._constructor(results, [grpaxis]) average = grp_ave.ungroup() # now ungroup return average