def test_resolution(clouds_list, expected_dsm, r=4): raster, _ = plyflatten_from_plyfiles_list(clouds_list, resolution=r) raster = raster[:, :, 0] reference, (rx, ry) = expected_dsm assert abs(raster.shape[0] * r - reference.shape[0] * rx) <= 2 * max(r, rx) assert abs(raster.shape[1] * r - reference.shape[1] * ry) <= 2 * max(r, ry)
def test_plyflatten(): # Test data f = data_path("input_ply/cloud.ply") raster, profile = plyflatten_from_plyfiles_list([f], resolution=0.4) test_raster = raster[:, :, 0] # keep only band with height # Expected data e = data_path("expected_output/plyflatten/dsm_40cm.tiff") with rasterio.open(e) as src: expected_raster = src.read(1) expected_crs = src.crs expected_transform = src.transform expected_is_tiled = src.is_tiled expected_nodata = src.nodata # Check that both rasters are equal pixel-wise within a tolerance assert np.allclose(test_raster, expected_raster, equal_nan=True) # Check that both images have the same CRS test_crs = profile['crs'] assert test_crs == expected_crs # Check that both images have the same transform test_transform = profile['transform'] assert np.allclose(test_transform, expected_transform) test_is_tiled = profile['tiled'] assert test_is_tiled == expected_is_tiled test_nodata = profile.get('nodata') if expected_nodata and math.isnan(expected_nodata): assert math.isnan(test_nodata) else: assert test_nodata == expected_nodata
def main(): parser = argparse.ArgumentParser( description=(f"{__title__}: {__description__}")) parser.add_argument("list_plys", nargs="+", help=("Space-separated list of .ply files")) parser.add_argument("dsm_path", help=("Path to output DSM file")) parser.add_argument( "--resolution", default=1, type=float, help=("Resolution of the DSM in meters (defaults to 1m)"), ) args = parser.parse_args() raster, profile = plyflatten_from_plyfiles_list(args.list_plys, args.resolution) raster = raster[:, :, 0] profile["dtype"] = raster.dtype profile["height"] = raster.shape[0] profile["width"] = raster.shape[1] profile["count"] = 1 profile["driver"] = "GTiff" with rasterio.open(args.dsm_path, "w", **profile) as f: f.write(raster, 1)
def test_std(clouds_list, expected_std): raster, _ = plyflatten_from_plyfiles_list(clouds_list, resolution=1, std=True) assert raster.shape[2] == 2 raster = raster[:, :, 1] np.testing.assert_allclose(expected_std, raster, equal_nan=True)
def plys_to_dsm(tile): """ Generates DSM from plyfiles (cloud.ply) Args: tile: a dictionary that provides all you need to process a tile """ out_dsm = os.path.join(tile['dir'], 'dsm.tif') out_conf = os.path.join(tile['dir'], 'confidence.tif') r = cfg['dsm_resolution'] xmin, xmax, ymin, ymax = np.loadtxt( os.path.join(tile['dir'], "plyextrema.txt")) if not all(np.isfinite([xmin, xmax, ymin, ymax])): # then the ply is empty return # compute xoff, yoff, xsize, ysize on a grid of unit r xoff = np.floor(xmin / r) * r xsize = int(1 + np.floor((xmax - xoff) / r)) yoff = np.ceil(ymax / r) * r ysize = int(1 - np.floor((ymin - yoff) / r)) roi = xoff, yoff, xsize, ysize clouds = [ os.path.join(tile['dir'], n_dir, 'cloud.ply') for n_dir in tile['neighborhood_dirs'] ] raster, profile = plyflatten_from_plyfiles_list(clouds, resolution=r, roi=roi, radius=cfg['dsm_radius'], sigma=cfg['dsm_sigma']) # save output image with utm georeferencing common.rasterio_write(out_dsm, raster[:, :, 0], profile=profile) # export confidence (optional) # note that the plys are assumed to contain the fields: # [x(float32), y(float32), z(float32), r(uint8), g(uint8), b(uint8), confidence(optional, float32)] # so the raster has 4 or 5 columns: [z, r, g, b, confidence (optional)] if raster.shape[-1] == 5: common.rasterio_write(out_conf, raster[:, :, 4], profile=profile)
def main(): parser = argparse.ArgumentParser( description=(f"{__title__}: {__description__}")) parser.add_argument("list_plys", nargs="+", help=("Space-separated list of .ply files")) parser.add_argument("dsm_path", help=("Path to output DSM file")) parser.add_argument( "--std", help=("Path to (optional) output standard deviation map")) parser.add_argument( "--cnt", help=("Path to (optional) output counts per grid point map")) parser.add_argument( "--resolution", default=1, type=float, help=("Resolution of the DSM in meters (defaults to 1m)"), ) args = parser.parse_args() raster, profile = plyflatten_from_plyfiles_list(args.list_plys, args.resolution, std=args.std is not None or args.cnt is not None) profile["dtype"] = raster.dtype profile["height"] = raster.shape[0] profile["width"] = raster.shape[1] profile["count"] = 1 profile["driver"] = "GTiff" with rasterio.open(args.dsm_path, "w", **profile) as f: f.write(raster[:, :, 0], 1) if args.cnt: with rasterio.open(args.cnt, "w", **profile) as f: f.write(raster[:, :, -1], 1) if args.std: raster = raster[:, :, :-1] n = raster.shape[2] assert n % 2 == 0 with rasterio.open(args.std, "w", **profile) as f: f.write(raster[:, :, n // 2], 1)
def test_distributed_plyflatten(): print('Running end2end with distributed plyflatten dsm ...') test_cfg = s2p.read_config_file(data_path('input_triplet/config.json')) s2p.main(test_cfg) outdir = test_cfg['out_dir'] computed = common.gdal_read_as_array_with_nans( os.path.join(outdir, 'dsm.tif')) print('Running plyflatten dsm reference ...') clouds_list = glob.glob( os.path.join(outdir, "tiles", "*", "*", "cloud.ply")) res = test_cfg['dsm_resolution'] roi = None raster, _ = plyflatten_from_plyfiles_list(clouds_list, resolution=res, roi=roi) expected = raster[:, :, 0] compare_dsm(computed, expected, 0, 0)
def test_plyflatten_from_plyfiles_list(clouds_list, expected_dsm): raster, _ = plyflatten_from_plyfiles_list(clouds_list, resolution=2) raster = raster[:, :, 0] expected_raster, _ = expected_dsm np.testing.assert_allclose(expected_raster, raster, equal_nan=True)
def test_resolution(clouds_list, expected_raster): raster, _ = plyflatten_from_plyfiles_list(clouds_list, resolution=4) raster = raster[:, :, 0] assert raster.shape[0] == expected_raster.shape[0] / 2 assert raster.shape[1] == expected_raster.shape[1] / 2