def cumulative_freq_plot(rast, band=0, mask=None, bins=100, xlim=None, nodata=-9999): ''' Plots an empirical cumulative frequency curve for the input raster array in a given band. ''' if mask is not None: arr = binary_mask(rast, mask) else: arr = rast.copy() if nodata is not None: arr = subarray(arr) values, base = np.histogram(arr, bins=bins) cumulative = np.cumsum(values) # Evaluate the cumulative distribution plt.plot(base[:-1], cumulative, c='blue') # Plot the cumulative function plt.set_title('Empirical Cumulative Distribution: Band %d' % band) if xlim is not None: axes = plt.gca() axes.set_xlim(xlim) plt.show() return arr
def interpolate_endmember_map( spectra, em_locations, window, q=3, n=2, labels=None, cval=0, nodata=-9999, multiprocess=False): ''' Creates synthetic image endmember maps (arrays); these endmembers are spatially interpolated from known endmember candidates. The end goal is a map (array) with an endmember spectra at every pixel, for one or more endmembers. Arguments: spectra The multi-band raster from which candidate endmember spectra will be synthesized; a (p x m x n) array. em_locations A single band, classified raster array denoting the locations of endmember candidates; a (1 x m x n) array. window A 2D NumPy array that serves as the moving window, kernel, or filter used in SASMA. q The number of endmember classes to synthesize. n The dimensionality of the spectral subspace. labels The values in em_locations that uniquely identify. each endmember class; default is [1,2,...,q] cval The constant value: Replaces NoData and used in areas outside the window; see NOTE below. nodata The NoData value. multiprocess True to generate multiple processes, one per endmember-band combination, i.e., q*n processes. Example of this routine on a tiny numeric array: ex = np.where(np.ndarray((1,5,5), dtype=np.int16) % 2 == 0, 0, 1) em_map = np.multiply(ex.repeat(ex, 3, 0), np.round(np.random.rand(3,5,5), 2)) NODATA = 0 # Example of a NoData value to filter window = np.ravel(kernel_idw_l1(3, band_num=1)) avg_map = generic_filter(em_map[0,...], lambda x: np.sum(np.multiply(x, window)) / np.sum( np.multiply(np.where(x == NODATA, 0, 1), window)), mode = 'constant', cval = 0, footprint = np.ones((3,3))) # If you want to place the original (raw) values into the averaged map np.where(em_map[0,...] > 0, em_map[0,...], avg_map) NOTE: For performance, NoData areas are filled with zero (0); this way, they do not contribute to the spatial sum (i.e., sum([0,...,0]) == 0) and they can also be removed from the sum of weights that is the divisor. In most cases, this shouldn't be an issue (minimum noise fraction components almost never equal zero); however, if areas that equal zero should be considered in the weighted sum, simply change cval. ''' shp = spectra.shape if labels is None: # In absence of user-defined endmember class labels, use integers labels = range(1, (q + 1)) assert len(labels) <= spectra.shape[0], 'The spectra array must have p bands for p endmember class labels' masked_spectra = [ # Extract the individual endmember "band" images binary_mask(spectra[j,...].reshape((1, shp[1], shp[2])), np.where(em_locations == i, 1, 0), nodata = nodata, invert = True) for i, j in list(product(labels, range(0, n))) ] if multiprocess: with ProcessPoolExecutor(max_workers = len(masked_spectra)) as executor: result = executor.map( partial(interpolate_endmember_spectra, window = window, nodata = nodata), masked_spectra) result = list(result) else: result = [] for em_map in masked_spectra: result.append( interpolate_endmember_spectra(em_map, window, cval, nodata)) # "Let's get the band back together again" synth_ems = [] for i in range(0, q): # Group bands by endmember type synth_ems.append(np.concatenate(result[(i*n):((i+1)*n)], axis = 0)) return synth_ems
def __init__(self, path=None, mask=None, cut_dim=None, ravel=True, transform=True, nodata=None, feature_limit=90000, selected_feature_limit=30, epsg=None, keyword=None, verbose=False): self.__nodata__ = nodata self.__raveled__ = ravel self.__limit__ = feature_limit self.__sel_limit__ = selected_feature_limit self.__verbose__ = verbose self.epsg = epsg self.size = (9, 9) self.dpi = 72 if path is not None: assert os.path.exists(path), 'No such file or directory' ds = gdal.Open(path) # (p, lat, lng) self.keyword = keyword # A "nickname" for this raster self.__wd__ = os.path.dirname(path) self.__gt__ = ds.GetGeoTransform() self.__wkt__ = ds.GetProjection() self.spatial_ref = {'gt': self.__gt__, 'wkt': self.__wkt__} if keyword is None: # Look for a date (7-8 numbers) set off by underscores date_match = re.compile(r'.*_(?P<date>\d{7,8})_.*').match( os.path.basename(path)) if date_match is not None: self.keyword = date_match.groups()[0] # Apply the MNF transformation? if transform: self.features = mnf_rotation(ds.ReadAsArray()) # (lng, lat, p) else: self.features = ds.ReadAsArray().transpose() if cut_dim: # Get rid of extraneous dimensionality self.features = self.features[..., 0:cut_dim] ds = None # Apply a mask? if mask is not None: if type(mask) == str: mask, gt, wkt = as_array(mask) else: if not isinstance(mask, np.ndarray): mask = mask.ReadAsArray() self.features = binary_mask(self.features.transpose(), mask, nodata=nodata).transpose() mask = None # Create random features self.rfeatures = self.features.copy().reshape( (self.features.shape[0] * self.features.shape[1], self.features.shape[2])) np.random.shuffle(self.rfeatures) # Limit the size of the stored array; keep first 90,000 (300*300) if ravel and nodata is not None: # Remove all "rows" (pixels) where there is a NoData value in any "column" (band) self.rfeatures = self.rfeatures[(self.rfeatures != nodata).any( axis=1), :] if self.__limit__ is not None: self.rfeatures = self.rfeatures[0:self.__limit__, :] else: self.rfeatures = self.rfeatures.reshape(self.features.shape) # If a limit was specified, select the first N random pixels if self.__limit__ is not None: r = int(np.sqrt(self.__limit__)) self.rfeatures = self.rfeatures.reshape( self.features.shape)[0:r, 0:r, :] ds = None