def gauss_fltr_pyramid(dem, size=None, full=False, origmask=False): """Pyaramidal downsampling approach for gaussian smoothing Avoids the need for large kernels, very fast Needs testing """ dem = malib.checkma(dem) levels = int(np.floor(np.log2(size))) #print levels dim = (np.floor(np.array(dem.shape) / float(2**levels) + 1) * (2**levels)).astype(int) #print dem.shape #print dim #Can do something with np.pad here #np.pad(a_fp.filled(), 1, mode='constant', constant_values=(a_fp.fill_value,)) dem2 = np.full(dim, dem.fill_value) offset = np.floor((dim - np.array(dem.shape)) / 2.0).astype(int) #print offset #dem2[0:dem.shape[0],0:dem.shape[1]] = dem.data dem2[offset[0]:dem.shape[0] + offset[0], offset[1]:dem.shape[1] + offset[1]] = dem.data dem2 = np.ma.masked_equal(dem2, dem.fill_value) #dem2 = dem for n in range(levels): print(dem2.shape) dim = (np.floor(np.array(dem2.shape) / 2.0 + 1) * 2).astype(int) #dem2 = gauss_fltr_astropy(dem2, size=5, origmask=origmask) #dem2 = gauss_fltr_astropy(dem2, size=5) dem2 = gauss_fltr_astropy(dem2, size=5) #Note: Should use zoom with same bilinear interpolation here for consistency #However, this doesn't respect nan #dem2 = zoom(dem2, 0.5, order=1, prefilter=False, cval=dem.fill_value) dem2 = dem2[::2, ::2] if full: print("Resizing to original input dimensions") from scipy.ndimage import zoom for n in range(levels): print(dem2.shape) #Note: order 1 is bilinear dem2 = zoom(dem2, 2, order=1, prefilter=False, cval=dem.fill_value) #dem2 = zoom(dem2, 2**levels, order=1, prefilter=False, cval=dem2.fill_value) print(dem2.shape) #This was for power of 2 offset #offset = (2**levels)/2 #print offset #dem2 = dem2[offset:dem.shape[0]+offset,offset:dem.shape[1]+offset] #Use original offset dem2 = dem2[offset[0]:dem.shape[0] + offset[0], offset[1]:dem.shape[1] + offset[1]] if origmask: print("Applying original mask") #Allow filling of interior holes, but use original outer edge maskfill = malib.maskfill(dem) #dem2 = np.ma.array(dem2, mask=np.ma.getmaskarray(dem)) dem2 = np.ma.array(dem2, mask=maskfill, fill_value=dem.fill_value) return dem2
def median_fltr_skimage(dem, radius=3, erode=1, origmask=False): """ Older skimage.filter.median_filter This smooths, removes noise and fills in nodata areas with median of valid pixels! Effectively an inpainting routine """ #Note, ndimage doesn't properly handle ma - convert to nan dem = malib.checkma(dem) dem = dem.astype(np.float64) #Mask islands if erode > 0: print("Eroding islands smaller than %s pixels" % (erode * 2)) dem = malib.mask_islands(dem, iterations=erode) print("Applying median filter with radius %s" % radius) #Note: this funcitonality was present in scikit-image 0.9.3 import skimage.filter dem_filt_med = skimage.filter.median_filter(dem, radius, mask=~dem.mask) #Starting in version 0.10.0, this is the new filter #This is the new filter, but only supports uint8 or unit16 #import skimage.filters #import skimage.morphology #dem_filt_med = skimage.filters.rank.median(dem, disk(radius), mask=~dem.mask) #dem_filt_med = skimage.filters.median(dem, skimage.morphology.disk(radius), mask=~dem.mask) #Now mask all nans #skimage assigns the minimum value as nodata #CHECK THIS, seems pretty hacky #Also, looks like some valid values are masked at this stage, even though they should be above min ndv = np.min(dem_filt_med) #ndv = dem_filt_med.min() + 0.001 out = np.ma.masked_less_equal(dem_filt_med, ndv) #Should probably replace the ndv with original ndv out.set_fill_value(dem.fill_value) if origmask: print("Applying original mask") #Allow filling of interior holes, but use original outer edge #maskfill = malib.maskfill(dem, iterations=radius) maskfill = malib.maskfill(dem) #dem_filt_gauss = np.ma.array(dem_filt_gauss, mask=dem.mask, fill_value=dem.fill_value) out = np.ma.array(out, mask=maskfill, fill_value=dem.fill_value) return out
def gauss_fltr_astropy(dem, size=None, sigma=None, origmask=False, fill_interior=False): """Astropy gaussian filter properly handles convolution with NaN http://stackoverflow.com/questions/23832852/by-which-measures-should-i-set-the-size-of-my-gaussian-filter-in-matlab width1 = 3; sigma1 = (width1-1) / 6; Specify width for smallest feature of interest and determine sigma appropriately sigma is width of 1 std in pixels (not multiplier) scipy and astropy both use cutoff of 4*sigma on either side of kernel - 99.994% 3*sigma on either side of kernel - 99.7% If sigma is specified, filter width will be a multiple of 8 times sigma Alternatively, specify filter size, then compute sigma: sigma = (size - 1) / 8. If size is < the required width for 6-8 sigma, need to use different mode to create kernel mode 'oversample' and 'center' are essentially identical for sigma 1, but very different for sigma 0.3 The sigma/size calculations below should work for non-integer sigma """ #import astropy.nddata import astropy.convolution dem = malib.checkma(dem) #Generate 2D gaussian kernel for input sigma and size #Default size is 8*sigma in x and y directions #kernel = astropy.nddata.make_kernel([size, size], sigma, 'gaussian') #Size must be odd if size is not None: size = int(np.floor(size/2)*2 + 1) size = max(size, 3) #Truncate the filter at this many standard deviations. Default is 4.0 truncate = 3.0 if size is not None and sigma is None: sigma = (size - 1) / (2*truncate) elif size is None and sigma is not None: #Round up to nearest odd int size = int(np.ceil((sigma * (2*truncate) + 1)/2)*2 - 1) elif size is None and sigma is None: #Use default parameters sigma = 1 size = int(np.ceil((sigma * (2*truncate) + 1)/2)*2 - 1) size = max(size, 3) kernel = astropy.convolution.Gaussian2DKernel(sigma, x_size=size, y_size=size, mode='oversample') print("Applying gaussian smoothing filter with size %i and sigma %0.3f (sum %0.3f)" % \ (size, sigma, kernel.array.sum())) #This will fill holes #np.nan is float #dem_filt_gauss = astropy.nddata.convolve(dem.astype(float).filled(np.nan), kernel, boundary='fill', fill_value=np.nan) #dem_filt_gauss = astropy.convolution.convolve(dem.astype(float).filled(np.nan), kernel, boundary='fill', fill_value=np.nan) #Added normalization to ensure filtered values are not brightened/darkened if kernelsum != 1 dem_filt_gauss = astropy.convolution.convolve(dem.astype(float).filled(np.nan), kernel, boundary='fill', fill_value=np.nan, normalize_kernel=True) #This will preserve original ndv pixels, applying original mask after filtering if origmask: print("Applying original mask") #Allow filling of interior holes, but use original outer edge if fill_interior: mask = malib.maskfill(dem) else: mask = dem.mask dem_filt_gauss = np.ma.array(dem_filt_gauss, mask=mask, fill_value=dem.fill_value) out = np.ma.fix_invalid(dem_filt_gauss, copy=False, fill_value=dem.fill_value) out.set_fill_value(dem.fill_value.astype(dem.dtype)) return out.astype(dem.dtype)