def min_max_heights_from_bbx(im, lon_m, lon_M, lat_m, lat_M, rpc): """ Compute min, max heights from bounding box Args: im: path to an image file lon_m, lon_M, lat_m, lat_M: bounding box Returns: hmin, hmax: min, max heights """ # open image dataset = rasterio.open(im, 'r') # convert lon/lat to im projection x_im_proj, y_im_proj = geographiclib.pyproj_transform([lon_m, lon_M], [lat_m, lat_M], 4326, dataset.crs.to_epsg()) # convert im projection to pixel pts = [] pts.append(~dataset.transform * (x_im_proj[0], y_im_proj[0])) pts.append(~dataset.transform * (x_im_proj[1], y_im_proj[1])) px = [p[0] for p in pts] py = [p[1] for p in pts] # get footprint [px_min, px_max, py_min, py_max] = map(int, [np.amin(px), np.amax(px)+1, np.amin(py), np.amax(py)+1]) # limits of im extract x, y, w, h = px_min, py_min, px_max - px_min + 1, py_max - py_min + 1 sizey, sizex = dataset.shape x0 = np.clip(x, 0, sizex-1) y0 = np.clip(y, 0, sizey-1) w -= (x0-x) h -= (y0-y) w = np.clip(w, 0, sizex - 1 - x0) h = np.clip(h, 0, sizey - 1 - y0) # get value for each pixel if (w != 0) and (h != 0): array = dataset.read(1, window=((y0, y0 + h), (x0, x0 + w))).astype(float) array[array == -32768] = np.nan hmin = np.nanmin(array) hmax = np.nanmax(array) if cfg['exogenous_dem_geoid_mode'] is True: offset = geographiclib.geoid_to_ellipsoid((lat_m + lat_M)/2, (lon_m + lon_M)/2, 0) hmin += offset hmax += offset return hmin, hmax else: print("WARNING: rpc_utils.min_max_heights_from_bbx: access window out of range") print("returning coarse range from rpc") return altitude_range_coarse(rpc, cfg['rpc_alt_range_scale_factor'])
def stereo_corresp_to_xyz(rpc1, rpc2, pts1, pts2, out_crs=None): """ Compute a point cloud from stereo correspondences between two images using RPC camera models. No need to go through the disparity map Args: rpc1, rpc2 (rpcm.RPCModel): camera models pts1, pts2 (arrays): 2D arrays of shape (N, 2) containing the image coordinates of N 2d keypoints matched beween im1 and im2, i.e. cooridnates in the same row of these arrays back-project to the same 3D point out_crs (pyproj.crs.CRS): object defining the desired coordinate reference system for the output xyz map Returns: xyz: array of shape (h, w, 3) where each pixel contains the 3D coordinates of the triangulated point in the coordinate system defined by `out_crs` err: array of shape (h, w) where each pixel contains the triangulation error """ # copy rpc coefficients to an RPCStruct object rpc1_c_struct = RPCStruct(rpc1, delta=0.1) rpc2_c_struct = RPCStruct(rpc2, delta=0.1) # get number of points to triangulate n = pts1.shape[0] # define the argument types of the stereo_corresp_to_lonlatalt function from disp_to_h.so lib.stereo_corresp_to_lonlatalt.argtypes = (ndpointer(dtype=c_double, shape=(n, 3)), ndpointer(dtype=c_float, shape=(n, 1)), ndpointer(dtype=c_float, shape=(n, 2)), ndpointer(dtype=c_float, shape=(n, 2)), c_int, POINTER(RPCStruct), POINTER(RPCStruct)) # call the stereo_corresp_to_lonlatalt function from disp_to_h.so lonlatalt = np.zeros((n, 3), dtype='float64') err = np.zeros((n, 1), dtype='float32') lib.stereo_corresp_to_lonlatalt(lonlatalt, err, pts1.astype('float32'), pts2.astype('float32'), n, byref(rpc1_c_struct), byref(rpc2_c_struct)) # output CRS conversion in_crs = geographiclib.pyproj_crs("epsg:4979") if out_crs and out_crs != in_crs: x, y, z = geographiclib.pyproj_transform(lonlatalt[:, 0], lonlatalt[:, 1], in_crs, out_crs, lonlatalt[:, 2]) xyz_array = np.column_stack((x, y, z)).astype(np.float32) else: xyz_array = lonlatalt return xyz_array, err
def height_map_to_point_cloud(cloud, heights, rpc, off_x=None, off_y=None, crop_colorized=''): """ Computes a color point cloud from a height map. Args: cloud: path to the output points cloud (ply format) heights: height map, sampled on the same grid as the crop_colorized image. In particular, its size is the same as crop_colorized. rpc: instances of the rpcm.RPCModel class off_{x,y} (optional, default None): coordinates of the origin of the crop we are dealing with in the pixel coordinates of the original full size image crop_colorized (optional, default ''): path to a colorized crop of a Pleiades image """ with rasterio.open(heights) as src: h_map = src.read(1) h, w = h_map.shape heights = h_map.ravel() indices = np.indices((h, w)) non_nan_ind = np.where(~np.isnan(heights))[0] alts = heights[non_nan_ind] cols = indices[1].ravel()[non_nan_ind] rows = indices[0].ravel()[non_nan_ind] if off_x or off_y: cols = cols + (off_x or 0) rows = rows + (off_y or 0) # localize pixels lons = np.empty_like(heights, dtype=np.float64) lats = np.empty_like(heights, dtype=np.float64) lons[non_nan_ind], lats[non_nan_ind] = rpc.localization(cols, rows, alts) # output CRS conversion in_crs = geographiclib.pyproj_crs("epsg:4979") out_crs = geographiclib.pyproj_crs(cfg['out_crs']) proj_com = "CRS {}".format(cfg['out_crs']) if out_crs != in_crs: x, y, z = geographiclib.pyproj_transform(lons, lats, in_crs, out_crs, heights) else: x, y, z = lons, lats, heights xyz_array = np.column_stack((x, y, z)).reshape(h, w, 3) filter_xyz_and_write_to_ply(cloud, xyz_array, cfg['3d_filtering_r'], cfg['3d_filtering_n'], cfg['gsd'], crop_colorized, proj_com)
def roi_process(rpc, ll_poly, utm_zone=None, use_srtm=False): """ Convert a longitude/latitude polygon into a rectangular bounding box in image coordinates Args: rpc (rpcm.RPCModel): camera model ll_poly (array): 2D array of shape (n, 2) containing the vertices (longitude, latitude) of the polygon utm_zone: force the zone number to be used when defining `utm_bbx`. If not specified, the default UTM zone for the given geography is used. use_srtm (bool): whether or not to use SRTM DEM to estimate the average ground altitude of the ROI. Returns: x, y, w, h: four integers defining a rectangular region of interest (ROI) in the image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. """ if not utm_zone: utm_zone = geographiclib.compute_utm_zone(*ll_poly.mean(axis=0)) cfg['utm_zone'] = utm_zone # convert lon lat polygon to utm epsg = geographiclib.epsg_code_from_utm_zone(utm_zone) easting, northing = geographiclib.pyproj_transform(ll_poly[:, 0], ll_poly[:, 1], 4326, epsg) east_min = min(easting) east_max = max(easting) nort_min = min(northing) nort_max = max(northing) cfg['utm_bbx'] = (east_min, east_max, nort_min, nort_max) # project lon lat vertices into the image if use_srtm: lon, lat = np.mean(ll_poly, axis=0) z = srtm4.srtm4(lon, lat) img_pts = rpc.projection(ll_poly[:, 0], ll_poly[:, 1], z) else: img_pts = rpc.projection(ll_poly[:, 0], ll_poly[:, 1], rpc.alt_offset) # return image roi x, y, w, h = common.bounding_box2D(list(zip(*img_pts))) return {'x': x, 'y': y, 'w': w, 'h': h}
def roi_process(rpc, ll_poly, use_srtm=False, exogenous_dem=None, exogenous_dem_geoid_mode=True): """ Convert a (lon, lat) polygon into a rectangular bounding box in image space. Args: rpc (rpcm.RPCModel): camera model ll_poly (array): 2D array of shape (n, 2) containing the vertices (longitude, latitude) of the polygon use_srtm (bool): whether or not to use SRTM DEM to estimate the average ground altitude of the ROI. Returns: x, y, w, h: four integers defining a rectangular region of interest (ROI) in the image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. """ if use_srtm and (exogenous_dem is not None): raise ValueError("use_srtm and exogenous_dem are mutually exclusive") # project lon lat vertices into the image lon, lat = np.mean(ll_poly, axis=0) if exogenous_dem is not None: with rasterio.open(exogenous_dem) as src: x, y = geographiclib.pyproj_transform(lon, lat, 4326, src.crs.to_epsg()) z = list(src.sample([(x, y)]))[0][0] if exogenous_dem_geoid_mode is True: z = geographiclib.geoid_to_ellipsoid(lat, lon, z) elif use_srtm: z = srtm4.srtm4(lon, lat) else: z = rpc.alt_offset img_pts = rpc.projection(ll_poly[:, 0], ll_poly[:, 1], z) # return image roi x, y, w, h = common.bounding_box2D(list(zip(*img_pts))) return {'x': x, 'y': y, 'w': w, 'h': h}
def disp_to_xyz(rpc1, rpc2, H1, H2, disp, mask, out_crs=None, img_bbx=None, A=None): """ Compute a height map from a disparity map, using RPC camera models. Args: 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 out_crs (pyproj.crs.CRS): object defining the desired coordinate reference system for the output xyz map img_bbx (4-tuple): col_min, col_max, row_min, row_max defining the unrectified image domain to process. A (array): 3x3 array with the pointing correction matrix for im2 Returns: xyz: array of shape (h, w, 3) where each pixel contains the 3D coordinates of the triangulated point in the coordinate system defined by `out_crs` err: array of shape (h, w) where each pixel contains the triangulation error """ # copy rpc coefficients to an RPCStruct object rpc1_c_struct = RPCStruct(rpc1) rpc2_c_struct = RPCStruct(rpc2) # handle optional arguments if A is not None: # apply pointing correction H2 = np.dot(H2, np.linalg.inv(A)) if img_bbx is None: img_bbx = (-np.inf, np.inf, -np.inf, np.inf) # define the argument types of the disp_to_lonlatalt function from disp_to_h.so h, w = disp.shape lib.disp_to_lonlatalt.argtypes = (ndpointer(dtype=c_double, shape=(h, w, 3)), ndpointer(dtype=c_float, shape=(h, w)), ndpointer(dtype=c_float, shape=(h, w)), ndpointer(dtype=c_float, shape=(h, w)), ndpointer(dtype=c_float, shape=(h, w)), c_int, c_int, ndpointer(dtype=c_double, shape=(9, )), ndpointer(dtype=c_double, shape=(9, )), POINTER(RPCStruct), POINTER(RPCStruct), ndpointer(dtype=c_float, shape=(4, ))) # call the disp_to_lonlatalt function from disp_to_h.so lonlatalt = np.zeros((h, w, 3), dtype='float64') err = np.zeros((h, w), dtype='float32') dispx = disp.astype('float32') dispy = np.zeros((h, w), dtype='float32') msk = mask.astype('float32') lib.disp_to_lonlatalt(lonlatalt, err, dispx, dispy, msk, w, h, H1.flatten(), H2.flatten(), byref(rpc1_c_struct), byref(rpc2_c_struct), np.asarray(img_bbx, dtype='float32')) # output CRS conversion in_crs = geographiclib.pyproj_crs("epsg:4979") if out_crs and out_crs != in_crs: # reshape the lonlatlat array into a 3-column 2D-array lonlatalt = lonlatalt.reshape(-1, 3) x, y, z = geographiclib.pyproj_transform(lonlatalt[:, 0], lonlatalt[:, 1], in_crs, out_crs, lonlatalt[:, 2]) xyz_array = np.column_stack((x, y, z)).reshape(h, w, 3).astype(np.float32) else: xyz_array = lonlatalt return xyz_array, err