def compute_offset(ref_dem_ds, src_dem_ds, src_dem_fn, mode='nuth', remove_outliers=True, max_offset=100, \ max_dz=100, slope_lim=(0.1, 40), mask_list=['glaciers',], plot=True): #Make sure the input datasets have the same resolution/extent #Use projection of source DEM ref_dem_clip_ds, src_dem_clip_ds = warplib.memwarp_multi([ref_dem_ds, src_dem_ds], \ res='max', extent='intersection', t_srs=src_dem_ds, r='cubic') #Compute size of NCC and SAD search window in pixels res = float(geolib.get_res(ref_dem_clip_ds, square=True)[0]) max_offset_px = (max_offset/res) + 1 #print(max_offset_px) pad = (int(max_offset_px), int(max_offset_px)) #This will be updated geotransform for src_dem src_dem_gt = np.array(src_dem_clip_ds.GetGeoTransform()) #Load the arrays ref_dem = iolib.ds_getma(ref_dem_clip_ds, 1) src_dem = iolib.ds_getma(src_dem_clip_ds, 1) print("Elevation difference stats for uncorrected input DEMs (src - ref)") diff = src_dem - ref_dem static_mask = get_mask(src_dem_clip_ds, mask_list, src_dem_fn) diff = np.ma.array(diff, mask=static_mask) if diff.count() == 0: sys.exit("No overlapping, unmasked pixels shared between input DEMs") if remove_outliers: diff = outlier_filter(diff, f=3, max_dz=max_dz) #Want to use higher quality DEM, should determine automatically from original res/count #slope = get_filtered_slope(ref_dem_clip_ds, slope_lim=slope_lim) slope = get_filtered_slope(src_dem_clip_ds, slope_lim=slope_lim) print("Computing aspect") #aspect = geolib.gdaldem_mem_ds(ref_dem_clip_ds, processing='aspect', returnma=True, computeEdges=False) aspect = geolib.gdaldem_mem_ds(src_dem_clip_ds, processing='aspect', returnma=True, computeEdges=False) ref_dem_clip_ds = None src_dem_clip_ds = None #Apply slope filter to diff #Note that we combine masks from diff and slope in coreglib diff = np.ma.array(diff, mask=np.ma.getmaskarray(slope)) #Get final mask after filtering static_mask = np.ma.getmaskarray(diff) #Compute stats for new masked difference map print("Filtered difference map") diff_stats = malib.print_stats(diff) dz = diff_stats[5] print("Computing sub-pixel offset between DEMs using mode: %s" % mode) #By default, don't create output figure fig = None #Default horizntal shift is (0,0) dx = 0 dy = 0 #Sum of absolute differences if mode == "sad": ref_dem = np.ma.array(ref_dem, mask=static_mask) src_dem = np.ma.array(src_dem, mask=static_mask) m, int_offset, sp_offset = coreglib.compute_offset_sad(ref_dem, src_dem, pad=pad) #Geotransform has negative y resolution, so don't need negative sign #np array is positive down #GDAL coordinates are positive up dx = sp_offset[1]*src_dem_gt[1] dy = sp_offset[0]*src_dem_gt[5] #Normalized cross-correlation of clipped, overlapping areas elif mode == "ncc": ref_dem = np.ma.array(ref_dem, mask=static_mask) src_dem = np.ma.array(src_dem, mask=static_mask) m, int_offset, sp_offset, fig = coreglib.compute_offset_ncc(ref_dem, src_dem, \ pad=pad, prefilter=False, plot=plot) dx = sp_offset[1]*src_dem_gt[1] dy = sp_offset[0]*src_dem_gt[5] #Nuth and Kaab (2011) elif mode == "nuth": #Compute relationship between elevation difference, slope and aspect fit_param, fig = coreglib.compute_offset_nuth(diff, slope, aspect, plot=plot) if fit_param is None: print("Failed to calculate horizontal shift") else: #fit_param[0] is magnitude of shift vector #fit_param[1] is direction of shift vector #fit_param[2] is mean bias divided by tangent of mean slope #print(fit_param) dx = fit_param[0]*np.sin(np.deg2rad(fit_param[1])) dy = fit_param[0]*np.cos(np.deg2rad(fit_param[1])) med_slope = malib.fast_median(slope) nuth_dz = fit_param[2]*np.tan(np.deg2rad(med_slope)) print('Median dz: %0.2f\nNuth dz: %0.2f' % (dz, nuth_dz)) #dz = nuth_dz elif mode == "all": print("Not yet implemented") #Want to compare all methods, average offsets #m, int_offset, sp_offset = coreglib.compute_offset_sad(ref_dem, src_dem) #m, int_offset, sp_offset = coreglib.compute_offset_ncc(ref_dem, src_dem) elif mode == "none": print("Skipping alignment, writing out DEM with median bias over static surfaces removed") dst_fn = outprefix+'_med%0.1f.tif' % dz iolib.writeGTiff(src_dem_orig + dz, dst_fn, src_dem_ds) sys.exit() #Note: minus signs here since we are computing dz=(src-ref), but adjusting src return -dx, -dy, -dz, static_mask, fig
def compute_offset(dem1_ds, dem2_ds, dem2_fn, mode='nuth', max_offset_m=100, remove_outliers=True, apply_mask=True): #Make sure the input datasets have the same resolution/extent #Use projection of source DEM dem1_clip_ds, dem2_clip_ds = warplib.memwarp_multi([dem1_ds, dem2_ds], \ res='max', extent='intersection', t_srs=dem2_ds) #Compute size of NCC and SAD search window in pixels res = float(geolib.get_res(dem1_clip_ds, square=True)[0]) max_offset_px = (max_offset_m / res) + 1 #print(max_offset_px) pad = (int(max_offset_px), int(max_offset_px)) #This will be updated geotransform for dem2 dem2_gt = np.array(dem2_clip_ds.GetGeoTransform()) #Load the arrays dem1 = iolib.ds_getma(dem1_clip_ds, 1) dem2 = iolib.ds_getma(dem2_clip_ds, 1) #Compute difference for unaligned inputs print("Elevation difference stats for uncorrected input DEMs") #Shouldn't need to worry about common mask here, as both inputs are ma diff_euler = dem2 - dem1 static_mask = None if apply_mask: #Need dem2_fn here to find TOA fn static_mask = get_mask(dem2_clip_ds, dem2_fn) dem1 = np.ma.array(dem1, mask=static_mask) dem2 = np.ma.array(dem2, mask=static_mask) diff_euler = np.ma.array(diff_euler, mask=static_mask) static_mask = np.ma.getmaskarray(diff_euler) if diff_euler.count() == 0: sys.exit("No overlapping, unmasked pixels shared between input DEMs") #Compute stats for new masked difference map diff_stats = malib.print_stats(diff_euler) dz = diff_stats[5] #This needs further testing if remove_outliers: med = diff_stats[5] nmad = diff_stats[6] f = 3 rmin = med - f * nmad rmax = med + f * nmad #Use IQR #rmin = diff_stats[7] #rmax = diff_stats[8] diff_euler = np.ma.masked_outside(diff_euler, rmin, rmax) #Should also apply to original dem1 and dem2 for sad and ncc print("Computing sub-pixel offset between DEMs using mode: %s" % mode) #By default, don't create output figure fig = None #Sum of absolute differences if mode == "sad": m, int_offset, sp_offset = coreglib.compute_offset_sad(dem1, dem2, pad=pad) #Geotransform has negative y resolution, so don't need negative sign #np array is positive down #GDAL coordinates are positive up dx = sp_offset[1] * dem2_gt[1] dy = sp_offset[0] * dem2_gt[5] #Normalized cross-correlation of clipped, overlapping areas elif mode == "ncc": m, int_offset, sp_offset, fig = coreglib.compute_offset_ncc(dem1, dem2, \ pad=pad, prefilter=False, plot=True) dx = sp_offset[1] * dem2_gt[1] dy = sp_offset[0] * dem2_gt[5] #Nuth and Kaab (2011) elif mode == "nuth": print("Computing slope and aspect") dem1_slope = geolib.gdaldem_mem_ds(dem1_clip_ds, processing='slope', returnma=True) dem1_aspect = geolib.gdaldem_mem_ds(dem1_clip_ds, processing='aspect', returnma=True) #Compute relationship between elevation difference, slope and aspect fit_param, fig = coreglib.compute_offset_nuth(diff_euler, dem1_slope, dem1_aspect) #fit_param[0] is magnitude of shift vector #fit_param[1] is direction of shift vector #fit_param[2] is mean bias divided by tangent of mean slope #print(fit_param) dx = fit_param[0] * np.sin(np.deg2rad(fit_param[1])) dy = fit_param[0] * np.cos(np.deg2rad(fit_param[1])) #med_slope = malib.fast_median(dem1_slope) #dz = fit_param[2]*np.tan(np.deg2rad(med_slope)) elif mode == "all": print("Not yet implemented") #Want to compare all methods, average offsets #m, int_offset, sp_offset = coreglib.compute_offset_sad(dem1, dem2) #m, int_offset, sp_offset = coreglib.compute_offset_ncc(dem1, dem2) #This is a hack to apply the computed median bias correction for shpclip area only elif mode == "none": print( "Skipping alignment, writing out DEM with median bias over static surfaces removed" ) dst_fn = outprefix + '_med%0.1f.tif' % dz iolib.writeGTiff(dem2_orig + dz, dst_fn, dem2_ds) sys.exit() #Note: minus signs here since we are computing dz=(src-ref), but adjusting src return -dx, -dy, -dz, static_mask, fig