def difference_dems(dem1, dem2, out_dem=None, in_mem=False): logger.debug('Difference DEMs:\nDEM1: {}\nDEM2:{}'.format(dem1, dem2)) clipped = clip_minbb([dem1, dem2], in_mem=True) dem1_clipped = Raster(clipped[0]) dem2_clipped = Raster(clipped[1]) diff = dem1_clipped.MaskedArray - dem2_clipped.MaskedArray if out_dem: logger.debug('Writing difference to: {}'.format(out_dem)) dem1_clipped.WriteArray(diff, out_path=out_dem) return out_dem
def dem_rmse(dem1_path, dem2_path, max_diff=None, outfile=None, out_diff=None, plot=False, show_plot=False, save_plot=None, bins=10, log_scale=True): # Load DEMs as arrays logger.info('Loading DEMs...') dem1 = Raster(dem1_path) dem2 = Raster(dem2_path) if dem1.geotransform != dem2.geotransform: logger.warning('''DEM geotransforms do not match. Clipping to minimum bounding box in memory....''') dem1 = None dem2 = None clipped = clip_minbb(rasters=[dem1_path, dem2_path], in_mem=True, out_format='vrt') logger.debug('Clipping complete. Reloading DEMs...') dem1 = Raster(clipped[0]) arr1 = dem1.MaskedArray dem1 = None logger.debug('DEM1 loaded and array extracted...') dem2 = Raster(clipped[1]) arr2 = dem2.MaskedArray dem2 = None logger.debug('DEM2 loaded and array extracted...') else: arr1 = dem1.MaskedArray dem1 = None arr2 = dem2.MaskedArray dem2 = None # Compute RMSE logger.info('Computing RMSE...') diffs = arr1 - arr2 # Remove any differences bigger than max_diff if max_diff: logger.debug( 'Checking for large differences, max_diff: {}'.format(max_diff)) size_uncleaned = diffs.size diffs = diffs[abs(diffs) < max_diff] size_cleaned = diffs.size if size_uncleaned != size_cleaned: logger.debug( 'Removed differences over max_diff ({}) from RMSE calculation...' .format(max_diff)) logger.debug('Size before: {:,}'.format(size_uncleaned)) logger.debug('Size after: {:,}'.format(size_cleaned)) logger.debug('Pixels removed: {:.2f}% of overlap area'.format( ((size_uncleaned - size_cleaned) / size_uncleaned) * 100)) sq_diff = diffs**2 mean_sq = sq_diff.sum() / sq_diff.count() logger.debug('Mean square error: {}'.format(mean_sq)) rmse = np.sqrt(mean_sq) # Report differences diffs_valid_count = diffs.count() min_diff = diffs.min() max_diff = diffs.max() logger.debug('Minimum difference: {:.2f}'.format(min_diff)) logger.debug('Maximum difference: {:.2f}'.format(max_diff)) logger.debug('Pixels considered: {:,}'.format(diffs_valid_count)) logger.info('RMSE: {:.2f}'.format(rmse)) # Write text file of results if outfile: with open(outfile, 'w') as of: of.write("DEM1: {}\n".format(dem1_path)) of.write("DEM2: {}\n".format(dem2_path)) of.write('RMSE: {:.2f}\n'.format(rmse)) of.write('Pixels considered: {:,}\n'.format(diffs_valid_count)) of.write('Minimum difference: {:.2f}\n'.format(min_diff)) of.write('Maximum difference: {:.2f}\n'.format(max_diff)) # Write raster file of results if out_diff: logger.info('Out diff not supported, skipping writing.') # dem1.WriteArray(diffs, out_diff) # Plot results # TODO: Add legend # TODO: Incorporate min/max differences based on max_diff argument if plot: plt.style.use('ggplot') fig, ax = plt.subplots(1, 1) ax.hist(diffs.compressed().flatten(), log=log_scale, bins=bins, edgecolor='white', alpha=0.875) ax.annotate('RMSE: {:.3f}'.format(rmse), xy=(76, 0.75), xycoords='axes fraction') plt.legend(loc="upper left") plt.tight_layout() if save_plot: plt.savefig(save_plot) if show_plot: plt.show() return rmse
def stack_rasters(rasters, minbb=True, rescale=False): """ Stack single band rasters into a multiband raster. Parameters ---------- rasters : list List of rasters to stack. Reference raster for NoData value, projection, etc. is the first raster provided. rescale : bool True to rescale rasters to 0 to 1. Returns ------- np.array : path.abspath : path to write multiband raster to. """ # TODO: Add default clipping to reference window if minbb: logger.info('Clipping to overlap area...') rasters = clip_minbb(rasters, in_mem=True, out_format='vrt') # Determine a raster to use for reference ref = Raster(rasters[0]) # Check for SRS match between reference and other rasters srs_matches = [same_srs(rasters[0], r) for r in rasters[1:]] if not all(srs_matches): logger.warning("""Spatial references do not match, match status between reference and rest:\n{}""".format( '\n'.join(srs_matches))) # Initialize the stacked array with just the reference array if ref.depth > 1: stacked = ref.GetBandAsArray(1, mask=True) if rescale: stacked = (stacked - stacked.min()) / (stacked.max() - stacked.min()) for i in range(ref.depth - 1): band = ref.GetBandAsArray(i + 2, mask=True) if rescale: band = (band - band.min()) / (band.max() - band.min()) stacked = np.ma.dstack([stacked, band]) for i, rast in enumerate(rasters[1:]): ma = Raster(rast).MaskedArray if np.ma.isMaskedArray(ma): if rescale: # Rescale to between 0 and 1 ma = (ma - ma.min()) / (ma.max() - ma.min()) # Replace mask/nodata value in array with reference values # ma_mask = ma.mask # ma = np.ma.masked_where(ma_mask, ma) # ma.set_fill_value(ref.nodata_val) # ma = ma.filled(ma.fill_value) stacked = np.ma.dstack([stacked, ma]) ma = None # Revert to original NoData value (stacking changes) # stacked.set_fill_value(ref.nodata_val) ref = None return stacked
def rmse_compare(dem1_path, dem2_path, dem2pca_path, max_diff=None, outfile=None, plot=False, save_plot=None, show_plot=False, bins=20, log_scale=True): # Load DEMs as arrays logger.info('Loading DEMs...') dem1 = Raster(dem1_path) dem2 = Raster(dem2_path) dem2pca = Raster(dem2pca_path) if dem1.geotransform != dem2.geotransform or dem1.geotransform != dem2pca.geotransform: logger.warning('DEM geotransforms do not match.') # Check for pixel size match if (((dem1.geotransform[1] != dem2.geotransform[1]) or (dem1.geotransform[1] != dem2pca.geotransform[1]) or (dem1.geotransform[5] != dem2.geotransform[5]) or (dem2.geotransform[5] != dem2pca.geotransform[5]))): logger.info('DEM pixel sizes do not match, translating to match.') p1 = dem1.src_path p2 = dem2.src_path dem1 = None dem2 = None # max = match_pixel_size([p1, p2], r'/vsimem/matching_px.tif', resampleAlg='cubic', in_mem=True) dem1 = Raster(p1) dem2 = Raster(p2) logger.info('DEM1 sz: {} {}'.format(dem1.x_sz, dem1.y_sz)) logger.info('DEM2 sz: {} {}'.format(dem2.x_sz, dem2.y_sz)) # Check for size match if ((dem1.x_sz != dem2.x_sz) or (dem1.x_sz != dem2pca.x_sz) or (dem1.y_sz != dem2.y_sz) or (dem1.y_sz != dem2pca.y_sz)): logger.info('DEM sizes do not match. Clipping to minimum bounding box in memory....') dem1 = None dem2 = None clipped = clip_minbb(rasters=[dem1_path, dem2_path, dem2pca_path], in_mem=True, out_format='vrt') logger.debug('Clipping complete. Reloading DEMs...') dem1 = Raster(clipped[0]) logger.debug('DEM1 loaded and array extracted...') dem2 = Raster(clipped[1]) logger.debug('DEM2 loaded and array extracted...') dem2pca = Raster(clipped[2]) logger.debug('DEM1 sz: {} {}'.format(dem1.x_sz, dem1.y_sz)) logger.debug('DEM2 sz: {} {}'.format(dem2.x_sz, dem2.y_sz)) arr1 = dem1.MaskedArray dem1 = None arr2 = dem2.MaskedArray dem2 = None arr2pca = dem2pca.MaskedArray else: arr1 = dem1.MaskedArray dem1 = None arr2 = dem2.MaskedArray dem2 = None arr2pca = dem2pca.MaskedArray dem2pca = None #### PRE-ALIGNMENT #### # Compute RMSE logger.info('Computing RMSE pre-alignment...') diffs = arr1 - arr2 # Remove any differences bigger than max_diff if max_diff: logger.debug('Checking for large differences, max_diff: ' '{}'.format(max_diff)) size_uncleaned = diffs.size diffs = diffs[abs(diffs) < max_diff] size_cleaned = diffs.size if size_uncleaned != size_cleaned: logger.debug('Removed differences over max_diff ({}) from RMSE ' 'calculation...'.format(max_diff)) logger.debug('Size before: {:,}'.format(size_uncleaned)) logger.debug('Size after: {:,}'.format(size_cleaned)) logger.debug('Pixels removed: {:.2f}% of overlap area'.format( ((size_uncleaned-size_cleaned)/size_uncleaned)*100)) sq_diff = diffs**2 mean_sq = sq_diff.sum() / sq_diff.count() rmse = np.sqrt(mean_sq) # Report differences diffs_valid_count = diffs.count() min_diff = diffs.min() max_diff = diffs.max() logger.debug('Minimum difference: {:.2f}'.format(min_diff)) logger.debug('Maximum difference: {:.2f}'.format(max_diff)) logger.debug('Pixels considered: {:,}'.format(diffs_valid_count)) logger.info('RMSE: {:.2f}'.format(rmse)) # Write text file of results if outfile: with open(outfile, 'w') as of: of.write("DEM1: {}\n".format(dem1_path)) of.write("DEM2: {}\n".format(dem2_path)) of.write('Pixels considered: {:,}\n'.format(diffs_valid_count)) of.write('Minimum difference: {:.2f}\n'.format(min_diff)) of.write('Maximum difference: {:.2f}\n\n'.format(max_diff)) of.write('RMSE: {:.2f}\n'.format(rmse)) #### POST ALIGNMENT #### # Compute RMSE logger.info('Computing RMSE post-alignment...') diffs_pca = arr1 - arr2pca # Remove any differences bigger than max_diff if max_diff: size_uncleaned = diffs.size diffs_pca = diffs_pca[abs(diffs_pca) < max_diff] size_cleaned = diffs.size if size_uncleaned != size_cleaned: logger.debug('Removed differences over max_diff ({}) from RMSE ' 'calculation...'.format(max_diff)) logger.debug('Size before: {:,}'.format(size_uncleaned)) logger.debug('Size after: {:,}'.format(size_cleaned)) logger.debug('Pixels removed: {:.2f}% of overlap area'.format( ((size_uncleaned-size_cleaned)/size_uncleaned)*100)) # Remove any differences bigger than max_diff if max_diff: logger.debug('Checking for large differences, max_diff: ' '{}'.format(max_diff)) size_uncleaned = diffs.size diffs = diffs[abs(diffs) < max_diff] size_cleaned = diffs.size if size_uncleaned != size_cleaned: logger.debug('Removed differences over max_diff ({}) from RMSE ' 'calculation...'.format(max_diff)) logger.debug('Size before: {:,}'.format(size_uncleaned)) logger.debug('Size after: {:,}'.format(size_cleaned)) logger.debug('Pixels removed: {:.2f}% of overlap area'.format( ((size_uncleaned-size_cleaned)/size_uncleaned)*100)) sq_diff_pca = diffs_pca**2 mean_sq_pca = sq_diff_pca.sum() / sq_diff_pca.count() rmse_pca = np.sqrt(mean_sq_pca) # Report differences diffs_pca_valid_count = diffs_pca.count() min_diff_pca = diffs_pca.min() max_diff_pca = diffs_pca.max() logger.debug('Minimum difference: {:.2f}'.format(min_diff_pca)) logger.debug('Maximum difference: {:.2f}'.format(max_diff_pca)) logger.debug('Pixels considered: {:,}'.format(diffs_pca_valid_count)) logger.info('RMSE: {:.2f}'.format(rmse_pca)) # Add to text file of results if outfile: with open(outfile, 'a') as of: of.write("DEM1: {}\n".format(dem1_path)) of.write("DEM2pca: {}\n".format(dem2pca_path)) of.write('Pixels considered pca: {:,}\n'.format(diffs_pca_valid_count)) of.write('Minimum difference pca: {:.2f}\n'.format(min_diff_pca)) of.write('Maximum difference pca: {:.2f}\n\n'.format(max_diff_pca)) of.write('RMSEpca: {:.2f}\n'.format(rmse_pca)) # Plot results # TODO: Add legend and RMSE annotations # TODO: Incorporate min/max differences based on max_diff argument if plot: plt.style.use('ggplot') fig, ax = plt.subplots(2, 1) # Plot unaligned differences with line at 0 ax[0].hist(diffs.compressed().flatten(), log=log_scale, bins=bins, edgecolor='white', alpha=0.75, range=[min([diffs.min(), diffs_pca.min()]), max([diffs.min(), diffs_pca.max()]) ]) ax[0].axvline(x=0, linewidth=2, color='black') # Plot aligned differences with line at 0 ax[1].hist(diffs_pca.compressed().flatten(), log=log_scale, bins=bins, edgecolor='white', color='b', alpha=0.75, range=[min([diffs.min(), diffs_pca.min()]), max([diffs.min(), diffs_pca.max()])]) ax[1].axvline(x=0, linewidth=2, color='black') # Annotation and titles ax[0].set_title('Pre-Alignment') ax[0].annotate('RMSE: {:.2f}'.format(rmse), xy=(0.05, 0.95), xycoords='axes fraction') ax[1].set_title('Post-Alignment') ax[1].annotate('RMSE: {:.2f}'.format(rmse_pca), xy=(0.05, 0.95), xycoords='axes fraction') plt.legend() plt.tight_layout() if save_plot: plt.savefig(save_plot) if show_plot: plt.show()