def transfer_map(in_map, H, x, y, w, h, out_map): """ Transfer the heights computed on the rectified grid to the original Pleiades image grid. Args: in_map: path to the input map, usually a height map or a mask, sampled on the rectified grid H: path to txt file containing a numpy 3x3 array representing the rectifying homography x, y, w, h: four integers defining the rectangular ROI in the original image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. out_map: path to the output map """ # write the inverse of the resampling transform matrix. In brief it is: # homography * translation # This matrix transports the coordinates of the original cropped and # grid (the one desired for out_height) to the rectified cropped and # grid (the one we have for height) HH = np.dot(np.loadtxt(H), common.matrix_translation(x, y)) # apply the homography # write the 9 coefficients of the homography to a string, then call synflow # to produce the flow, then backflow to apply it # zero:256x256 is the iio way to create a 256x256 image filled with zeros hij = ' '.join(['%r' % num for num in HH.flatten()]) common.run( 'synflow hom "%s" zero:%dx%d /dev/null - | BILINEAR=1 backflow - %s %s' % (hij, w, h, in_map, out_map))
def cloud_water_image_domain(x, y, w, h, rpc, roi_gml=None, cld_gml=None, wat_msk=None): """ Compute a mask for pixels masked by clouds, water, or out of image domain. Args: x, y, w, h: coordinates of the ROI roi_gml (optional): path to a gml file containing a mask defining the area contained in the full image cld_gml (optional): path to a gml file containing a mask defining the areas covered by clouds Returns: 2D array containing the output binary mask. 0 indicate masked pixels, 1 visible pixels. """ # coefficients of the transformation associated to the crop H = common.matrix_translation(-x, -y) hij = ' '.join([str(el) for el in H.flatten()]) mask = np.ones((h, w), dtype=np.bool) if roi_gml is not None: # image domain mask (polygons) tmp = common.tmpfile('.png') subprocess.check_call('cldmask %d %d -h "%s" %s %s' % (w, h, hij, roi_gml, tmp), shell=True) with rasterio.open(tmp, 'r') as f: mask = np.logical_and(mask, f.read().squeeze()) if not mask.any(): return mask if cld_gml is not None: # cloud mask (polygons) tmp = common.tmpfile('.png') subprocess.check_call('cldmask %d %d -h "%s" %s %s' % (w, h, hij, cld_gml, tmp), shell=True) with rasterio.open(tmp, 'r') as f: mask = np.logical_and(mask, ~f.read().squeeze().astype(bool)) if not mask.any(): return mask if wat_msk is not None: # water mask (raster) x, y, w, h = map(int, (x, y, w, h)) with rasterio.open(wat_msk, 'r') as f: mask = np.logical_and(mask, f.read(window=((y, y+h), (x, x+w))).squeeze()) return mask
def register_horizontally_translation(matches, H1, H2, flag='center'): """ Adjust rectifying homographies with a translation to modify the disparity range. Args: matches: list of pairs of 2D points, stored as a Nx4 numpy array H1, H2: two homographies, stored as numpy 3x3 matrices flag: option needed to control how to modify the disparity range: 'center': move the barycenter of disparities of matches to zero 'positive': make all the disparities positive 'negative': make all the disparities negative. Required for Hirshmuller stereo (java) Returns: H2: corrected homography H2 The matches are provided in the original images coordinate system. By transforming these coordinates with the provided homographies, we obtain matches whose disparity is only along the x-axis. The second homography H2 is corrected with a horizontal translation to obtain the desired property on the disparity range. """ # transform the matches according to the homographies p1 = common.points_apply_homography(H1, matches[:, :2]) x1 = p1[:, 0] y1 = p1[:, 1] p2 = common.points_apply_homography(H2, matches[:, 2:]) x2 = p2[:, 0] y2 = p2[:, 1] # for debug, print the vertical disparities. Should be zero. if cfg['debug']: print("Residual vertical disparities: max, min, mean. Should be zero") print(np.max(y2 - y1), np.min(y2 - y1), np.mean(y2 - y1)) # compute the disparity offset according to selected option t = 0 if (flag == 'center'): t = np.mean(x2 - x1) if (flag == 'positive'): t = np.min(x2 - x1) if (flag == 'negative'): t = np.max(x2 - x1) # correct H2 with a translation return np.dot(common.matrix_translation(-t, 0), H2)
def alt_to_disp(rpc1, rpc2, x, y, alt, H1, H2, A=None): """ Converts an altitude into a disparity. Args: rpc1: an instance of the rpcm.RPCModel class for the reference image rpc2: an instance of the rpcm.RPCModel class for the secondary image x, y: coordinates of the point in the reference image alt: altitude above the WGS84 ellipsoid (in meters) of the point H1, H2: rectifying homographies A (optional): pointing correction matrix Returns: the horizontal disparity of the (x, y) point of im1, assuming that the 3-space point associated has altitude alt. The disparity is made horizontal thanks to the two rectifying homographies H1 and H2. """ xx, yy = rpc_utils.find_corresponding_point(rpc1, rpc2, x, y, alt)[0:2] p1 = np.vstack([x, y]).T p2 = np.vstack([xx, yy]).T if A is not None: print("rpc_utils.alt_to_disp: applying pointing error correction") # correct coordinates of points in im2, according to A p2 = common.points_apply_homography(np.linalg.inv(A), p2) p1 = common.points_apply_homography(H1, p1) p2 = common.points_apply_homography(H2, p2) #AG---------------------------------------------- x1 = p1[:, 0] y1 = p1[:, 1] x2 = p2[:, 0] y2 = p2[:, 1] # correct H2 with a translation so the disparities are negative (inspired in registration.register_horizontally_translation t = np.max(x2 - x1) + 3 # el 3 es para estar lejos del cero H2 = np.dot(common.matrix_translation(-t, 0), H2) #------------------------------------------------- # np.testing.assert_allclose(p1[:, 1], p2[:, 1], atol=0.1) disp = p2[:, 0] - p1[:, 0] - t return disp, H2
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 rectification_homographies(matches, x, y, w, h): """ Computes rectifying homographies from point matches for a given ROI. The affine fundamental matrix F is estimated with the gold-standard algorithm, then two rectifying similarities (rotation, zoom, translation) are computed directly from F. Args: matches: numpy array of shape (n, 4) containing a list of 2D point correspondences between the two images. x, y, w, h: four integers defining the rectangular ROI in the first image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. Returns: S1, S2, F: three numpy arrays of shape (3, 3) representing the two rectifying similarities to be applied to the two images and the corresponding affine fundamental matrix. """ # estimate the affine fundamental matrix with the Gold standard algorithm F = estimation.affine_fundamental_matrix(matches) # compute rectifying similarities S1, S2 = estimation.rectifying_similarities_from_affine_fundamental_matrix( F, cfg['debug']) if cfg['debug']: y1 = common.points_apply_homography(S1, matches[:, :2])[:, 1] y2 = common.points_apply_homography(S2, matches[:, 2:])[:, 1] err = np.abs(y1 - y2) print("max, min, mean rectification error on point matches: ", end=' ') print(np.max(err), np.min(err), np.mean(err)) # pull back top-left corner of the ROI to the origin (plus margin) pts = common.points_apply_homography( S1, [[x, y], [x + w, y], [x + w, y + h], [x, y + h]]) x0, y0 = common.bounding_box2D(pts)[:2] T = common.matrix_translation(-x0, -y0) return np.dot(T, S1), np.dot(T, S2), F
def height_map(x, y, w, h, rpc1, rpc2, H1, H2, disp, mask, utm_zone, A=None): """ Computes an altitude map, on the grid of the original reference image, from a disparity map given on the grid of the rectified reference image. Args: x, y, w, h (ints): rectangular AOI in the original image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. rpc1, rpc2 (rpcm.RPCModel): camera models H1, H2 (arrays): 3x3 numpy arrays defining the rectifying homographies disp, mask (array): 2D arrays of shape (h, w) representing the diparity and mask maps utm_zone (int): desired UTM zone number (between 1 and 60) for the output xyz map A (array): 3x3 array with the pointing correction matrix for im2 Returns: array of shape (h, w) with the height map """ xyz, err = disp_to_xyz(rpc1, rpc2, H1, H2, disp, mask, utm_zone, A=A) height_map = xyz[:, :, 2].squeeze() # transfer the rectified height map onto an unrectified height map H = np.dot(H1, common.matrix_translation(x, y)) out = ndimage.affine_transform(np.nan_to_num(height_map).T, H, output_shape=(w, h), order=1).T # nearest-neighbor interpolation of nan locations in the resampled image if np.isnan(height_map).any(): i = ndimage.affine_transform(np.isnan(height_map).T, H, output_shape=(w, h), order=0).T i = ndimage.binary_dilation(i, structure=np.ones((3, 3))) out[i] = np.nan # put nans back in the resampled image return out
def rectify_pair(im1, im2, rpc1, rpc2, x, y, w, h, out1, out2, A=None, sift_matches=None, method='rpc', hmargin=0, vmargin=0): """ Rectify a ROI in a pair of images. Args: im1, im2: paths to two GeoTIFF image files rpc1, rpc2: two instances of the rpcm.RPCModel class x, y, w, h: four integers defining the rectangular ROI in the first image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. out1, out2: paths to the output rectified crops A (optional): 3x3 numpy array containing the pointing error correction for im2. This matrix is usually estimated with the pointing_accuracy module. sift_matches (optional): Nx4 numpy array containing a list of sift matches, in the full image coordinates frame method (default: 'rpc'): option to decide wether to use rpc of sift matches for the fundamental matrix estimation. {h,v}margin (optional): horizontal and vertical margins added on the sides of the rectified images Returns: H1, H2: Two 3x3 matrices representing the rectifying homographies that have been applied to the two original (large) images. disp_min, disp_max: horizontal disparity range """ # compute real or virtual matches if method == 'rpc': # find virtual matches from RPC camera models matches = rpc_utils.matches_from_rpc(rpc1, rpc2, x, y, w, h, cfg['n_gcp_per_axis']) # correct second image coordinates with the pointing correction matrix if A is not None: matches[:, 2:] = common.points_apply_homography( np.linalg.inv(A), matches[:, 2:]) elif method == 'sift': matches = sift_matches else: raise Exception( "Unknown value {} for argument 'method'".format(method)) if matches is None or len(matches) < 4: raise NoRectificationMatchesError( "No or not enough matches found to rectify image pair") # compute rectifying homographies H1, H2, F = rectification_homographies(matches, x, y, w, h) if cfg['register_with_shear']: # compose H2 with a horizontal shear to reduce the disparity range a = np.mean(rpc_utils.altitude_range(rpc1, x, y, w, h)) lon, lat, alt = rpc_utils.ground_control_points( rpc1, x, y, w, h, a, a, 4) x1, y1 = rpc1.projection(lon, lat, alt)[:2] x2, y2 = rpc2.projection(lon, lat, alt)[:2] m = np.vstack([x1, y1, x2, y2]).T m = np.vstack({tuple(row) for row in m}) # remove duplicates due to no alt range H2 = register_horizontally_shear(m, H1, H2) # compose H2 with a horizontal translation to center disp range around 0 if sift_matches is not None: sift_matches = filter_matches_epipolar_constraint( F, sift_matches, cfg['epipolar_thresh']) if len(sift_matches) < 1: warnings.warn( "Need at least one sift match for the horizontal registration", category=NoHorizontalRegistrationWarning, ) else: H2 = register_horizontally_translation(sift_matches, H1, H2) # compute disparity range if cfg['debug']: out_dir = os.path.dirname(out1) np.savetxt(os.path.join(out_dir, 'sift_matches_disp.txt'), sift_matches, fmt='%9.3f') visualisation.plot_matches( im1, im2, rpc1, rpc2, sift_matches, x, y, w, h, os.path.join(out_dir, 'sift_matches_disp.png')) disp_m, disp_M = disparity_range(rpc1, rpc2, x, y, w, h, H1, H2, sift_matches, A) # recompute hmargin and homographies hmargin = int(np.ceil(max([hmargin, np.fabs(disp_m), np.fabs(disp_M)]))) T = common.matrix_translation(hmargin, vmargin) H1, H2 = np.dot(T, H1), np.dot(T, H2) # compute output images size roi = [[x, y], [x + w, y], [x + w, y + h], [x, y + h]] pts1 = common.points_apply_homography(H1, roi) x0, y0, w0, h0 = common.bounding_box2D(pts1) # check that the first homography maps the ROI in the positive quadrant np.testing.assert_allclose(np.round([x0, y0]), [hmargin, vmargin], atol=.01) # apply homographies and do the crops common.image_apply_homography(out1, im1, H1, w0 + 2 * hmargin, h0 + 2 * vmargin) common.image_apply_homography(out2, im2, H2, w0 + 2 * hmargin, h0 + 2 * vmargin) return H1, H2, disp_m, disp_M
def image_tile_mask(x, y, w, h, roi_gml=None, cld_gml=None, raster_mask=None, img_shape=None, border_margin=10): """ Compute a validity mask for an image tile from vector/raster image masks. Args: x, y, w, h (ints): top-left pixel coordinates and size of the tile roi_gml (str): path to a gml file containing a mask defining the valid area in the input reference image cld_gml (str): path to a gml file containing a mask defining the cloudy areas in the input reference image raster_mask (str): path to a raster mask file img_shape (tuple): height and width of the reference input (full) image border_margin (int): width, in pixels, of a stripe of pixels to discard along the reference input image borders Returns: 2D array containing the output binary mask. 0 indicate masked pixels, 1 visible pixels. """ x, y, w, h = map(int, (x, y, w, h)) # coefficients of the transformation associated to the crop H = common.matrix_translation(-x, -y) hij = ' '.join([str(el) for el in H.flatten()]) mask = np.ones((h, w), dtype=np.bool) if roi_gml is not None: # image domain mask (polygons) tmp = common.tmpfile('.png') subprocess.check_call('cldmask %d %d -h "%s" %s %s' % (w, h, hij, roi_gml, tmp), shell=True) with rasterio.open(tmp, 'r') as f: mask = np.logical_and(mask, f.read().squeeze().astype(bool)) if not mask.any(): return mask if cld_gml is not None: # cloud mask (polygons) tmp = common.tmpfile('.png') subprocess.check_call('cldmask %d %d -h "%s" %s %s' % (w, h, hij, cld_gml, tmp), shell=True) with rasterio.open(tmp, 'r') as f: mask = np.logical_and(mask, ~f.read().squeeze().astype(bool)) if not mask.any(): return mask if raster_mask is not None: with rasterio.open(raster_mask, 'r') as f: mask = np.logical_and(mask, f.read(window=((y, y+h), (x, x+w)), boundless=True).squeeze()) # image borders mask if img_shape is not None: m = np.ones(img_shape, dtype=np.bool) m[:border_margin] = 0 # first rows m[-border_margin:] = 0 # last rows m[:, :border_margin] = 0 # first columns m[:, -border_margin:] = 0 # last columns mask = np.logical_and(mask, common.crop_array(m, x, y, w, h)) return mask
def rectify_pair(im1, im2, rpc1, rpc2, x, y, w, h, out1, out2, A=None, sift_matches=None, method='rpc', hmargin=0, vmargin=0): """ Rectify a ROI in a pair of images. Args: im1, im2: paths to two image files rpc1, rpc2: paths to the two xml files containing RPC data x, y, w, h: four integers defining the rectangular ROI in the first image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. out1, out2: paths to the output rectified crops A (optional): 3x3 numpy array containing the pointing error correction for im2. This matrix is usually estimated with the pointing_accuracy module. sift_matches (optional): Nx4 numpy array containing a list of sift matches, in the full image coordinates frame method (default: 'rpc'): option to decide wether to use rpc of sift matches for the fundamental matrix estimation. {h,v}margin (optional): horizontal and vertical margins added on the sides of the rectified images Returns: H1, H2: Two 3x3 matrices representing the rectifying homographies that have been applied to the two original (large) images. disp_min, disp_max: horizontal disparity range """ # read RPC data rpc1 = rpc_model.RPCModel(rpc1) rpc2 = rpc_model.RPCModel(rpc2) # compute real or virtual matches if method == 'rpc': # find virtual matches from RPC camera models matches = rpc_utils.matches_from_rpc(rpc1, rpc2, x, y, w, h, cfg['n_gcp_per_axis']) # correct second image coordinates with the pointing correction matrix if A is not None: matches[:, 2:] = common.points_apply_homography( np.linalg.inv(A), matches[:, 2:]) else: matches = sift_matches # compute rectifying homographies H1, H2, F = rectification_homographies(matches, x, y, w, h) if cfg['register_with_shear']: # compose H2 with a horizontal shear to reduce the disparity range a = np.mean(rpc_utils.altitude_range(rpc1, x, y, w, h)) lon, lat, alt = rpc_utils.ground_control_points( rpc1, x, y, w, h, a, a, 4) x1, y1 = rpc1.inverse_estimate(lon, lat, alt)[:2] x2, y2 = rpc2.inverse_estimate(lon, lat, alt)[:2] m = np.vstack([x1, y1, x2, y2]).T m = np.vstack({tuple(row) for row in m}) # remove duplicates due to no alt range H2 = register_horizontally_shear(m, H1, H2) # compose H2 with a horizontal translation to center disp range around 0 if sift_matches is not None: sift_matches = filter_matches_epipolar_constraint( F, sift_matches, cfg['epipolar_thresh']) if len(sift_matches) < 10: print('WARNING: no registration with less than 10 matches') else: H2 = register_horizontally_translation(sift_matches, H1, H2) # compute disparity range if cfg['debug']: out_dir = os.path.dirname(out1) np.savetxt(os.path.join(out_dir, 'sift_matches_disp.txt'), sift_matches, fmt='%9.3f') visualisation.plot_matches( im1, im2, rpc1, rpc2, sift_matches, x, y, w, h, os.path.join(out_dir, 'sift_matches_disp.png')) disp_m, disp_M = disparity_range(rpc1, rpc2, x, y, w, h, H1, H2, sift_matches, A) # recompute hmargin and homographies hmargin = int(np.ceil(max([hmargin, np.fabs(disp_m), np.fabs(disp_M)]))) T = common.matrix_translation(hmargin, vmargin) H1, H2 = np.dot(T, H1), np.dot(T, H2) # compute rectifying homographies for non-epipolar mode (rectify the secondary tile only) if block_matching.rectify_secondary_tile_only(cfg['matching_algorithm']): H1_inv = np.linalg.inv(H1) H1 = np.eye( 3 ) # H1 is replaced by 2-D array with ones on the diagonal and zeros elsewhere H2 = np.dot(H1_inv, H2) T = common.matrix_translation(-x + hmargin, -y + vmargin) H1 = np.dot(T, H1) H2 = np.dot(T, H2) # compute output images size roi = [[x, y], [x + w, y], [x + w, y + h], [x, y + h]] pts1 = common.points_apply_homography(H1, roi) x0, y0, w0, h0 = common.bounding_box2D(pts1) # check that the first homography maps the ROI in the positive quadrant np.testing.assert_allclose(np.round([x0, y0]), [hmargin, vmargin], atol=.01) # apply homographies and do the crops common.image_apply_homography(out1, im1, H1, w0 + 2 * hmargin, h0 + 2 * vmargin) common.image_apply_homography(out2, im2, H2, w0 + 2 * hmargin, h0 + 2 * vmargin) if block_matching.rectify_secondary_tile_only(cfg['matching_algorithm']): pts_in = [[0, 0], [disp_m, 0], [disp_M, 0]] pts_out = common.points_apply_homography(H1_inv, pts_in) disp_m = pts_out[1, :] - pts_out[0, :] disp_M = pts_out[2, :] - pts_out[0, :] return H1, H2, disp_m, disp_M
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)