def heights_fusion(tile): """ Merge the height maps computed for each image pair and generate a ply cloud. Args: tile: a dictionary that provides all you need to process a tile """ tile_dir = tile['dir'] height_maps = [os.path.join(tile_dir, 'pair_%d' % (i + 1), 'height_map.tif') for i in range(len(cfg['images']) - 1)] # remove spurious matches if cfg['cargarse_basura']: for img in height_maps: common.cargarse_basura(img, img) # load global mean heights global_mean_heights = [] for i in range(len(cfg['images']) - 1): x = np.loadtxt(os.path.join(cfg['out_dir'], 'global_mean_height_pair_{}.txt'.format(i+1))) global_mean_heights.append(x) # merge the height maps (applying mean offset to register) fusion.merge_n(os.path.join(tile_dir, 'height_map.tif'), height_maps, global_mean_heights, averaging=cfg['fusion_operator'], threshold=cfg['fusion_thresh']) if cfg['clean_intermediate']: for f in height_maps: common.remove(f)
def global_pointing_correction(tiles): """ Compute the global pointing corrections for each pair of images. Args: tiles: list of tile dictionaries """ for i in range(1, len(cfg['images'])): out = os.path.join(cfg['out_dir'], 'global_pointing_pair_%d.txt' % i) l = [os.path.join(t['dir'], 'pair_%d' % i) for t in tiles] np.savetxt(out, pointing_accuracy.global_from_local(l), fmt='%12.6f') if cfg['clean_intermediate']: for d in l: common.remove(os.path.join(d, 'center_keypts_sec.txt'))
def disparity_to_height(tile, i): """ Compute a height map from the disparity map of a pair of image tiles. Args: tile: dictionary containing the information needed to process a tile. i: index of the processed pair. """ out_dir = os.path.join(tile['dir'], 'pair_{}'.format(i)) height_map = os.path.join(out_dir, 'height_map.tif') x, y, w, h = tile['coordinates'] if os.path.exists(os.path.join(out_dir, 'stderr.log')): print('triangulation: stderr.log exists') print('pair_{} not processed on tile {} {}'.format(i, x, y)) return print('triangulating tile {} {} pair {}...'.format(x, y, i)) rpc1 = cfg['images'][0]['rpc'] rpc2 = cfg['images'][i]['rpc'] H_ref = os.path.join(out_dir, 'H_ref.txt') H_sec = os.path.join(out_dir, 'H_sec.txt') disp = os.path.join(out_dir, 'rectified_disp.tif') mask = os.path.join(out_dir, 'rectified_mask.png') rpc_err = os.path.join(out_dir, 'rpc_err.tif') out_mask = os.path.join(tile['dir'], 'cloud_water_image_domain_mask.png') pointing = os.path.join(cfg['out_dir'], 'global_pointing_pair_{}.txt'.format(i)) triangulation.height_map(height_map, x, y, w, h, rpc1, rpc2, H_ref, H_sec, disp, mask, rpc_err, out_mask, pointing) if cfg['clean_intermediate']: common.remove(H_ref) common.remove(H_sec) common.remove(disp) common.remove(mask) common.remove(rpc_err)
def disparity_to_height(tile, i): """ Compute a height map from the disparity map of a pair of image tiles. Args: tile: dictionary containing the information needed to process a tile. i: index of the processed pair. """ out_dir = os.path.join(tile['dir'], 'pair_{}'.format(i)) x, y, w, h = tile['coordinates'] print('triangulating tile {} {} pair {}...'.format(x, y, i)) rpc1 = cfg['images'][0]['rpcm'] rpc2 = cfg['images'][i]['rpcm'] H_ref = np.loadtxt(os.path.join(out_dir, 'H_ref.txt')) H_sec = np.loadtxt(os.path.join(out_dir, 'H_sec.txt')) disp = os.path.join(out_dir, 'rectified_disp.tif') mask = os.path.join(out_dir, 'rectified_mask.png') pointing = os.path.join(cfg['out_dir'], 'global_pointing_pair_{}.txt'.format(i)) with rasterio.open(disp, 'r') as f: disp_img = f.read().squeeze() with rasterio.open(mask, 'r') as f: mask_rect_img = f.read().squeeze() height_map = triangulation.height_map(x, y, w, h, rpc1, rpc2, H_ref, H_sec, disp_img, mask_rect_img, A=np.loadtxt(pointing)) # write height map to a file common.rasterio_write(os.path.join(out_dir, 'height_map.tif'), height_map) if cfg['clean_intermediate']: common.remove(H_ref) common.remove(H_sec) common.remove(disp) common.remove(mask)
def heights_to_ply(tile): """ Generate a ply cloud. Args: tile: a dictionary that provides all you need to process a tile """ # merge the n-1 height maps of the tile (n = nb of images) heights_fusion(tile) # compute a ply from the merged height map out_dir = tile['dir'] x, y, w, h = tile['coordinates'] plyfile = os.path.join(out_dir, 'cloud.ply') plyextrema = os.path.join(out_dir, 'plyextrema.txt') height_map = os.path.join(out_dir, 'height_map.tif') # H is the homography transforming the coordinates system of the original # full size image into the coordinates system of the crop H = np.dot(np.diag([1, 1, 1]), common.matrix_translation(-x, -y)) colors = os.path.join(out_dir, 'ref.png') if cfg['images'][0]['clr']: common.image_crop_gdal(cfg['images'][0]['clr'], x, y, w, h, colors) else: common.image_qauto( common.image_crop_gdal(cfg['images'][0]['img'], x, y, w, h), colors) triangulation.height_map_to_point_cloud(plyfile, height_map, cfg['images'][0]['rpc'], H, colors, utm_zone=cfg['utm_zone'], llbbx=tuple(cfg['ll_bbx'])) # compute the point cloud extrema (xmin, xmax, xmin, ymax) common.run("plyextrema %s %s" % (plyfile, plyextrema)) if cfg['clean_intermediate']: common.remove(height_map) common.remove(colors) common.remove( os.path.join(out_dir, 'cloud_water_image_domain_mask.png'))
def stereo_matching(tile,i): """ Compute the disparity of a pair of images on a given tile. Args: tile: dictionary containing the information needed to process a tile. i: index of the processed pair """ out_dir = os.path.join(tile['dir'], 'pair_{}'.format(i)) x, y = tile['coordinates'][:2] outputs = ['rectified_mask.png', 'rectified_disp.tif'] if os.path.exists(os.path.join(out_dir, 'stderr.log')): print('disparity estimation: stderr.log exists') print('pair_{} not processed on tile {} {}'.format(i, x, y)) return print('estimating disparity on tile {} {} pair {}...'.format(x, y, i)) rect1 = os.path.join(out_dir, 'rectified_ref.tif') rect2 = os.path.join(out_dir, 'rectified_sec.tif') disp = os.path.join(out_dir, 'rectified_disp.tif') mask = os.path.join(out_dir, 'rectified_mask.png') disp_min, disp_max = np.loadtxt(os.path.join(out_dir, 'disp_min_max.txt')) block_matching.compute_disparity_map(rect1, rect2, disp, mask, cfg['matching_algorithm'], disp_min, disp_max, timeout=cfg['mgm_timeout'], max_disp_range=cfg['max_disp_range']) # add margin around masked pixels masking.erosion(mask, mask, cfg['msk_erosion']) if cfg['clean_intermediate']: if len(cfg['images']) > 2: common.remove(rect1) common.remove(rect2) common.remove(os.path.join(out_dir,'disp_min_max.txt'))
def heights_to_ply(tile): """ Generate a ply cloud. Args: tile: a dictionary that provides all you need to process a tile """ # merge the n-1 height maps of the tile (n = nb of images) heights_fusion(tile) # compute a ply from the merged height map out_dir = tile['dir'] x, y, w, h = tile['coordinates'] plyfile = os.path.join(out_dir, 'cloud.ply') plyextrema = os.path.join(out_dir, 'plyextrema.txt') height_map = os.path.join(out_dir, 'height_map.tif') colors = os.path.join(out_dir, 'ref.tif') if cfg['images'][0]['clr']: common.image_crop_gdal(cfg['images'][0]['clr'], x, y, w, h, colors) else: common.image_qauto( common.image_crop_gdal(cfg['images'][0]['img'], x, y, w, h), colors) triangulation.height_map_to_point_cloud(plyfile, height_map, cfg['images'][0]['rpcm'], x, y, colors) # compute the point cloud extrema (xmin, xmax, xmin, ymax) common.run("plyextrema %s %s" % (plyfile, plyextrema)) if cfg['clean_intermediate']: common.remove(height_map) common.remove(colors) common.remove(os.path.join(out_dir, 'mask.png'))
def rectification_pair(tile, i): """ Rectify a pair of images on a given tile. Args: tile: dictionary containing the information needed to process a tile. i: index of the processed pair """ out_dir = os.path.join(tile['dir'], 'pair_{}'.format(i)) x, y, w, h = tile['coordinates'] img1 = cfg['images'][0]['img'] rpc1 = cfg['images'][0]['rpcm'] img2 = cfg['images'][i]['img'] rpc2 = cfg['images'][i]['rpcm'] pointing = os.path.join(cfg['out_dir'], 'global_pointing_pair_{}.txt'.format(i)) print('rectifying tile {} {} pair {}...'.format(x, y, i)) try: A = np.loadtxt(os.path.join(out_dir, 'pointing.txt')) except IOError: A = np.loadtxt(pointing) try: m = np.loadtxt(os.path.join(out_dir, 'sift_matches.txt')) except IOError: m = None cur_dir = os.path.join(tile['dir'], 'pair_{}'.format(i)) for n in tile['neighborhood_dirs']: nei_dir = os.path.join(tile['dir'], n, 'pair_{}'.format(i)) if os.path.exists(nei_dir) and not os.path.samefile(cur_dir, nei_dir): sift_from_neighborhood = os.path.join(nei_dir, 'sift_matches.txt') try: m_n = np.loadtxt(sift_from_neighborhood) # added sifts in the ellipse of semi axes : (3*w/4, 3*h/4) m_n = m_n[np.where( np.linalg.norm([(m_n[:, 0] - (x + w / 2)) / w, (m_n[:, 1] - (y + h / 2)) / h], axis=0) < 3 / 4)] if m is None: m = m_n else: m = np.concatenate((m, m_n)) except IOError: print('%s does not exist' % sift_from_neighborhood) rect1 = os.path.join(out_dir, 'rectified_ref.tif') rect2 = os.path.join(out_dir, 'rectified_sec.tif') H1, H2, disp_min, disp_max = rectification.rectify_pair( img1, img2, rpc1, rpc2, x, y, w, h, rect1, rect2, A, m, method=cfg['rectification_method'], hmargin=cfg['horizontal_margin'], vmargin=cfg['vertical_margin']) np.savetxt(os.path.join(out_dir, 'H_ref.txt'), H1, fmt='%12.6f') np.savetxt(os.path.join(out_dir, 'H_sec.txt'), H2, fmt='%12.6f') np.savetxt(os.path.join(out_dir, 'disp_min_max.txt'), [disp_min, disp_max], fmt='%3.1f') if cfg['clean_intermediate']: common.remove(os.path.join(out_dir, 'pointing.txt')) common.remove(os.path.join(out_dir, 'sift_matches.txt'))
def disparity_to_ply(tile): """ Compute a point cloud from the disparity map of a pair of image tiles. This function is called by s2p.main only if there are two input images (not three). Args: tile: dictionary containing the information needed to process a tile. """ out_dir = tile['dir'] ply_file = os.path.join(out_dir, 'cloud.ply') plyextrema = os.path.join(out_dir, 'plyextrema.txt') x, y, w, h = tile['coordinates'] rpc1 = cfg['images'][0]['rpcm'] rpc2 = cfg['images'][1]['rpcm'] print('triangulating tile {} {}...'.format(x, y)) H_ref = os.path.join(out_dir, 'pair_1', 'H_ref.txt') H_sec = os.path.join(out_dir, 'pair_1', 'H_sec.txt') pointing = os.path.join(cfg['out_dir'], 'global_pointing_pair_1.txt') disp = os.path.join(out_dir, 'pair_1', 'rectified_disp.tif') extra = os.path.join(out_dir, 'pair_1', 'rectified_disp_confidence.tif') if not os.path.exists(extra): # confidence file not always generated extra = '' mask_rect = os.path.join(out_dir, 'pair_1', 'rectified_mask.png') mask_orig = os.path.join(out_dir, 'mask.png') # prepare the image needed to colorize point cloud colors = os.path.join(out_dir, 'rectified_ref.png') if cfg['images'][0]['clr']: hom = np.loadtxt(H_ref) # we want rectified_ref.png and rectified_ref.tif to have the same size with rasterio.open(os.path.join(out_dir, 'pair_1', 'rectified_ref.tif')) as f: ww, hh = f.width, f.height common.image_apply_homography(colors, cfg['images'][0]['clr'], hom, ww, hh) else: common.image_qauto( os.path.join(out_dir, 'pair_1', 'rectified_ref.tif'), colors) # compute the point cloud with rasterio.open(disp, 'r') as f: disp_img = f.read().squeeze() with rasterio.open(mask_rect, 'r') as f: mask_rect_img = f.read().squeeze() pyproj_out_crs = geographiclib.pyproj_crs(cfg['out_crs']) proj_com = "CRS {}".format(cfg['out_crs']) xyz_array, err = triangulation.disp_to_xyz(rpc1, rpc2, np.loadtxt(H_ref), np.loadtxt(H_sec), disp_img, mask_rect_img, pyproj_out_crs, img_bbx=(x, x + w, y, y + h), A=np.loadtxt(pointing)) triangulation.filter_xyz_and_write_to_ply(ply_file, xyz_array, cfg['3d_filtering_r'], cfg['3d_filtering_n'], cfg['gsd'], colors, proj_com, confidence=extra) # compute the point cloud extrema (xmin, xmax, xmin, ymax) common.run("plyextrema %s %s" % (ply_file, plyextrema)) if cfg['clean_intermediate']: common.remove(H_ref) common.remove(H_sec) common.remove(disp) common.remove(mask_rect) common.remove(mask_orig) common.remove(colors) common.remove(os.path.join(out_dir, 'pair_1', 'rectified_ref.tif'))
def disparity_to_ply(tile): """ Compute a point cloud from the disparity map of a pair of image tiles. Args: tile: dictionary containing the information needed to process a tile. """ out_dir = os.path.join(tile['dir']) ply_file = os.path.join(out_dir, 'cloud.ply') plyextrema = os.path.join(out_dir, 'plyextrema.txt') x, y, w, h = tile['coordinates'] rpc1 = cfg['images'][0]['rpcm'] rpc2 = cfg['images'][1]['rpcm'] if os.path.exists(os.path.join(out_dir, 'stderr.log')): print('triangulation: stderr.log exists') print('pair_1 not processed on tile {} {}'.format(x, y)) return print('triangulating tile {} {}...'.format(x, y)) # This function is only called when there is a single pair (pair_1) H_ref = os.path.join(out_dir, 'pair_1', 'H_ref.txt') H_sec = os.path.join(out_dir, 'pair_1', 'H_sec.txt') pointing = os.path.join(cfg['out_dir'], 'global_pointing_pair_1.txt') disp = os.path.join(out_dir, 'pair_1', 'rectified_disp.tif') extra = os.path.join(out_dir, 'pair_1', 'rectified_disp_confidence.tif') if not os.path.exists(extra): extra = '' mask_rect = os.path.join(out_dir, 'pair_1', 'rectified_mask.png') mask_orig = os.path.join(out_dir, 'mask.png') # prepare the image needed to colorize point cloud colors = os.path.join(out_dir, 'rectified_ref.png') if cfg['images'][0]['clr']: hom = np.loadtxt(H_ref) # We want rectified_ref.png and rectified_ref.tif to have the same size with rasterio.open(os.path.join(out_dir, 'pair_1', 'rectified_ref.tif')) as f: ww, hh = f.width, f.height common.image_apply_homography(colors, cfg['images'][0]['clr'], hom, ww, hh) else: common.image_qauto(os.path.join(out_dir, 'pair_1', 'rectified_ref.tif'), colors) # compute the point cloud with rasterio.open(disp, 'r') as f: disp_img = f.read().squeeze() with rasterio.open(mask_rect, 'r') as f: mask_rect_img = f.read().squeeze() xyz_array, err = triangulation.disp_to_xyz(rpc1, rpc2, np.loadtxt(H_ref), np.loadtxt(H_sec), disp_img, mask_rect_img, int(cfg['utm_zone'][:-1]), img_bbx=(x, x+w, y, y+h), A=np.loadtxt(pointing)) # 3D filtering if cfg['3d_filtering_r'] and cfg['3d_filtering_n']: triangulation.filter_xyz(xyz_array, cfg['3d_filtering_r'], cfg['3d_filtering_n'], cfg['gsd']) # flatten the xyz array into a list and remove nan points xyz_list = xyz_array.reshape(-1, 3) valid = np.all(np.isfinite(xyz_list), axis=1) # write the point cloud to a ply file with rasterio.open(colors, 'r') as f: img = f.read() colors_list = img.transpose(1, 2, 0).reshape(-1, img.shape[0]) ply.write_3d_point_cloud_to_ply(ply_file, xyz_list[valid], colors=colors_list[valid], extra_properties=None, extra_properties_names=None, comments=["created by S2P", "projection: UTM {}".format(cfg['utm_zone'])]) # compute the point cloud extrema (xmin, xmax, xmin, ymax) common.run("plyextrema %s %s" % (ply_file, plyextrema)) if cfg['clean_intermediate']: common.remove(H_ref) common.remove(H_sec) common.remove(disp) common.remove(mask_rect) common.remove(mask_orig) common.remove(colors) common.remove(os.path.join(out_dir, 'pair_1', 'rectified_ref.tif'))
def multidisparities_to_ply(tile): """ Compute a point cloud from the disparity maps of N-pairs of image tiles. Args: tile: dictionary containing the information needed to process a tile. # There is no guarantee that this function works with z!=1 """ out_dir = os.path.join(tile['dir']) ply_file = os.path.join(out_dir, 'cloud.ply') plyextrema = os.path.join(out_dir, 'plyextrema.txt') x, y, w, h = tile['coordinates'] rpc_ref = cfg['images'][0]['rpc'] disp_list = list() rpc_list = list() mask_orig = os.path.join(out_dir, 'cloud_water_image_domain_mask.png') print('triangulating tile {} {}...'.format(x, y)) n = len(cfg['images']) - 1 for i in range(n): pair = 'pair_%d' % (i + 1) H_ref = os.path.join(out_dir, pair, 'H_ref.txt') H_sec = os.path.join(out_dir, pair, 'H_sec.txt') disp = os.path.join(out_dir, pair, 'rectified_disp.tif') mask_rect = os.path.join(out_dir, pair, 'rectified_mask.png') disp2D = os.path.join(out_dir, pair, 'disp2D.tif') rpc_sec = cfg['images'][i + 1]['rpc'] if os.path.exists(disp): # homography for warp T = common.matrix_translation(x, y) hom_ref = np.loadtxt(H_ref) hom_ref_shift = np.dot(hom_ref, T) # homography for 1D to 2D conversion hom_sec = np.loadtxt(H_sec) if cfg["use_global_pointing_for_geometric_triangulation"] is True: pointing = os.path.join(cfg['out_dir'], 'global_pointing_%s.txt' % pair) hom_pointing = np.loadtxt(pointing) hom_sec = np.dot(hom_sec, np.linalg.inv(hom_pointing)) hom_sec_shift_inv = np.linalg.inv(hom_sec) h1 = " ".join(str(x) for x in hom_ref_shift.flatten()) h2 = " ".join(str(x) for x in hom_sec_shift_inv.flatten()) # relative disparity map to absolute disparity map tmp_abs = common.tmpfile('.tif') os.environ["PLAMBDA_GETPIXEL"] = "0" common.run( 'plambda %s %s "y 0 = nan x[0] :i + x[1] :j + 1 3 njoin if" -o %s' % (disp, mask_rect, tmp_abs)) # 1d to 2d conversion tmp_1d_to_2d = common.tmpfile('.tif') common.run('plambda %s "%s 9 njoin x mprod" -o %s' % (tmp_abs, h2, tmp_1d_to_2d)) # warp tmp_warp = common.tmpfile('.tif') common.run('homwarp -o 2 "%s" %d %d %s %s' % (h1, w, h, tmp_1d_to_2d, tmp_warp)) # set masked value to NaN exp = 'y 0 = nan x if' common.run('plambda %s %s "%s" -o %s' % (tmp_warp, mask_orig, exp, disp2D)) # disp2D contains positions in the secondary image # added input data for triangulation module disp_list.append(disp2D) rpc_list.append(rpc_sec) if cfg['clean_intermediate']: common.remove(H_ref) common.remove(H_sec) common.remove(disp) common.remove(mask_rect) common.remove(mask_orig) colors = os.path.join(out_dir, 'ref.png') if cfg['images'][0]['clr']: common.image_crop_gdal(cfg['images'][0]['clr'], x, y, w, h, colors) else: common.image_qauto( common.image_crop_gdal(cfg['images'][0]['img'], x, y, w, h), colors) # compute the point cloud triangulation.multidisp_map_to_point_cloud(ply_file, disp_list, rpc_ref, rpc_list, colors, utm_zone=cfg['utm_zone'], llbbx=tuple(cfg['ll_bbx']), xybbx=(x, x + w, y, y + h)) # compute the point cloud extrema (xmin, xmax, xmin, ymax) common.run("plyextrema %s %s" % (ply_file, plyextrema)) if cfg['clean_intermediate']: common.remove(colors)
def disparity_to_ply(tile): """ Compute a point cloud from the disparity map of a pair of image tiles. Args: tile: dictionary containing the information needed to process a tile. """ out_dir = os.path.join(tile['dir']) ply_file = os.path.join(out_dir, 'cloud.ply') plyextrema = os.path.join(out_dir, 'plyextrema.txt') x, y, w, h = tile['coordinates'] rpc1 = cfg['images'][0]['rpc'] rpc2 = cfg['images'][1]['rpc'] if os.path.exists(os.path.join(out_dir, 'stderr.log')): print('triangulation: stderr.log exists') print('pair_1 not processed on tile {} {}'.format(x, y)) return print('triangulating tile {} {}...'.format(x, y)) # This function is only called when there is a single pair (pair_1) H_ref = os.path.join(out_dir, 'pair_1', 'H_ref.txt') H_sec = os.path.join(out_dir, 'pair_1', 'H_sec.txt') pointing = os.path.join(cfg['out_dir'], 'global_pointing_pair_1.txt') disp = os.path.join(out_dir, 'pair_1', 'rectified_disp.tif') extra = os.path.join(out_dir, 'pair_1', 'rectified_disp_confidence.tif') if not os.path.exists(extra): extra = '' mask_rect = os.path.join(out_dir, 'pair_1', 'rectified_mask.png') mask_orig = os.path.join(out_dir, 'cloud_water_image_domain_mask.png') # prepare the image needed to colorize point cloud colors = os.path.join(out_dir, 'rectified_ref.png') if cfg['images'][0]['clr']: hom = np.loadtxt(H_ref) roi = [[x, y], [x + w, y], [x + w, y + h], [x, y + h]] ww, hh = common.bounding_box2D(common.points_apply_homography( hom, roi))[2:] tmp = common.tmpfile('.tif') common.image_apply_homography(tmp, cfg['images'][0]['clr'], hom, ww + 2 * cfg['horizontal_margin'], hh + 2 * cfg['vertical_margin']) common.image_qauto(tmp, colors) else: common.image_qauto( os.path.join(out_dir, 'pair_1', 'rectified_ref.tif'), colors) # compute the point cloud triangulation.disp_map_to_point_cloud(ply_file, disp, mask_rect, rpc1, rpc2, H_ref, H_sec, pointing, colors, extra, utm_zone=cfg['utm_zone'], llbbx=tuple(cfg['ll_bbx']), xybbx=(x, x + w, y, y + h), xymsk=mask_orig) # compute the point cloud extrema (xmin, xmax, xmin, ymax) common.run("plyextrema %s %s" % (ply_file, plyextrema)) if cfg['clean_intermediate']: common.remove(H_ref) common.remove(H_sec) common.remove(disp) common.remove(mask_rect) common.remove(mask_orig) common.remove(colors) common.remove(os.path.join(out_dir, 'pair_1', 'rectified_ref.tif'))