def test_km2pix(self, imgheight, latextent, dc): mypixperkm = (180. / (np.pi * 3389.0)) * (imgheight * dc / latextent) pixperkm = trf.km2pix(imgheight, latextent, dc=dc, a=3389.0) assert np.isclose(mypixperkm, pixperkm, rtol=1e-10, atol=0.) # degperpix used in get_unique_craters. degperpix = (180. / (np.pi * 3389.0)) / pixperkm mydegperpix = latextent / imgheight / dc assert np.isclose(degperpix, mydegperpix, rtol=1e-10, atol=0.)
def estimate_longlatdiamkm(dim, llbd, distcoeff, coords): """First-order estimation of long/lat, and radius (km) from (Orthographic) x/y position and radius (pix). For images transformed from ~6000 pixel crops of the 30,000 pixel LROC-Kaguya DEM, this results in < ~0.4 degree latitude, <~0.2 longitude offsets (~2% and ~1% of the image, respectively) and ~2% error in radius. Larger images thus may require an exact inverse transform, depending on the accuracy demanded by the user. Parameters ---------- dim : tuple or list (width, height) of input images. llbd : tuple or list Long/lat limits (long_min, long_max, lat_min, lat_max) of image. distcoeff : float Ratio between the central heights of the transformed image and original image. coords : numpy.ndarray Array of crater x coordinates, y coordinates, and pixel radii. Returns ------- craters_longlatdiamkm : numpy.ndarray Array of crater longitude, latitude and radii in km. """ # Expand coords. long_pix, lat_pix, radii_pix = coords.T # Determine radius (km). km_per_pix = 1. / trf.km2pix(dim[1], llbd[3] - llbd[2], dc=distcoeff) radii_km = radii_pix * km_per_pix # Determine long/lat. deg_per_pix = km_per_pix * 180. / (np.pi * 3389.0) long_central = 0.5 * (llbd[0] + llbd[1]) lat_central = 0.5 * (llbd[2] + llbd[3]) # Iterative method for determining latitude. lat_deg_firstest = lat_central - deg_per_pix * (lat_pix - dim[1] / 2.) latdiff = abs(lat_central - lat_deg_firstest) # Protect against latdiff = 0 situation. latdiff[latdiff < 1e-7] = 1e-7 lat_deg = lat_central - (deg_per_pix * (lat_pix - dim[1] / 2.) * (np.pi * latdiff / 180.) / np.sin(np.pi * latdiff / 180.)) # Determine longitude using determined latitude. long_deg = long_central + (deg_per_pix * (long_pix - dim[0] / 2.) / np.cos(np.pi * lat_deg / 180.)) # Return combined long/lat/radius array. return np.column_stack((long_deg, lat_deg, radii_km))
def ResampleCraters(craters, llbd, imgheight, arad=1737.4, minpix=0): """Crops crater file, and removes craters smaller than some minimum value. Parameters ---------- craters : pandas.DataFrame Crater dataframe. llbd : list-like Long/lat limits (long_min, long_max, lat_min, lat_max) of image. imgheight : int Pixel height of image. arad : float, optional World radius in km. Defaults to Moon radius (1737.4 km). minpix : int, optional Minimium crater pixel size to be included in output. Default is 0 (equvalent to no cutoff). Returns ------- ctr_sub : pandas.DataFrame Cropped and filtered dataframe. """ # Get subset of craters within llbd limits. ctr_xlim = (craters["Long"] >= llbd[0]) & (craters["Long"] <= llbd[1]) ctr_ylim = (craters["Lat"] >= llbd[2]) & (craters["Lat"] <= llbd[3]) ctr_sub = craters.loc[ctr_xlim & ctr_ylim, :].copy() if minpix > 0: # Obtain pixel per km conversion factor. Use latitude because Plate # Carree doesn't distort along this axis. pixperkm = trf.km2pix(imgheight, llbd[3] - llbd[2], dc=1., a=arad) minkm = minpix / pixperkm # Remove craters smaller than pixel limit. ctr_sub = ctr_sub[ctr_sub["Diameter (km)"] >= minkm] ctr_sub.reset_index(inplace=True, drop=True) return ctr_sub
def PlateCarree_to_Orthographic(img, llbd, craters, iglobe=None, ctr_sub=False, arad=1737.4, origin="upper", rgcoeff=1.2, slivercut=0.): """Transform Plate Carree image and associated csv file into Orthographic. Parameters ---------- img : PIL.Image.image or str File or filename. llbd : list-like Long/lat limits (long_min, long_max, lat_min, lat_max) of image. craters : pandas.DataFrame Craters catalogue. iglobe : cartopy.crs.Geodetic instance Globe for images. If False, defaults to spherical Moon. ctr_sub : bool, optional If `True`, assumes craters dataframe includes only craters within image. If `False` (default_, llbd used to cut craters from outside image out of (copy of) dataframe. arad : float World radius in km. Default is Moon (1737.4 km). origin : "lower" or "upper", optional Based on imshow convention for displaying image y-axis. "upper" (default) means that [0,0] is upper-left corner of image; "lower" means it is bottom-left. rgcoeff : float, optional Fractional size increase of transformed image height. By default set to 1.2 to prevent loss of fidelity during transform (though warping can be so extreme that this might be meaningless). slivercut : float from 0 to 1, optional If transformed image aspect ratio is too narrow (and would lead to a lot of padding, return null images). Returns ------- imgo : PIL.Image.image Transformed, padded image in PIL.Image format. ctr_xy : pandas.DataFrame Craters with transformed x, y pixel positions and pixel radii. distortion_coefficient : float Ratio between the central heights of the transformed image and original image. centrallonglat_xy : pandas.DataFrame xy position of the central longitude and latitude. """ # If user doesn't provide Moon globe properties. if not iglobe: iglobe = ccrs.Globe(semimajor_axis=arad*1000., semiminor_axis=arad*1000., ellipse=None) # Set up Geodetic (long/lat), Plate Carree (usually long/lat, but not when # globe != WGS84) and Orthographic projections. geoproj = ccrs.Geodetic(globe=iglobe) iproj = ccrs.PlateCarree(globe=iglobe) oproj = ccrs.Orthographic(central_longitude=np.mean(llbd[:2]), central_latitude=np.mean(llbd[2:]), globe=iglobe) # Create and transform coordinates of image corners and edge midpoints. # Due to Plate Carree and Orthographic's symmetries, max/min x/y values of # these 9 points represent extrema of the transformed image. xll = np.array([llbd[0], np.mean(llbd[:2]), llbd[1]]) yll = np.array([llbd[2], np.mean(llbd[2:]), llbd[3]]) xll, yll = np.meshgrid(xll, yll) xll = xll.ravel() yll = yll.ravel() # [:,:2] because we don't need elevation data. res = iproj.transform_points(x=xll, y=yll, src_crs=geoproj)[:, :2] iextent = [min(res[:, 0]), max(res[:, 0]), min(res[:, 1]), max(res[:, 1])] res = oproj.transform_points(x=xll, y=yll, src_crs=geoproj)[:, :2] oextent = [min(res[:, 0]), max(res[:, 0]), min(res[:, 1]), max(res[:, 1])] # Sanity check for narrow images; done before the most expensive part of # the function. oaspect = (oextent[1] - oextent[0]) / (oextent[3] - oextent[2]) if oaspect < slivercut: return [None, None, None, None] if type(img) != Image.Image: img = Image.open(img).convert("L") # Warp image. imgo, imgwshp, offset = WarpImagePad(img, iproj, iextent, oproj, oextent, origin=origin, rgcoeff=rgcoeff, fillbg="black") # Convert crater x, y position. if ctr_sub: llbd_in = None else: llbd_in = llbd ctr_xy = WarpCraterLoc(craters, geoproj, oproj, oextent, imgwshp, llbd=llbd_in, origin=origin) # Shift crater x, y positions by offset (origin doesn't matter for y-shift, # since padding is symmetric). ctr_xy.loc[:, "x"] += offset[0] ctr_xy.loc[:, "y"] += offset[1] # Pixel scale for orthographic determined (for images small enough that # tan(x) approximately equals x + 1/3x^3 + ...) by l = R_moon*theta, # where theta is the latitude extent of the centre of the image. Because # projection transform doesn't guarantee central vertical axis will keep # its pixel resolution, we need to calculate the conversion coefficient # C = (res[7,1]- res[1,1])/(oextent[3] - oextent[2]). # C0*pix height/C = theta # Where theta is the latitude extent and C0 is the theta per pixel # conversion for the Plate Carree image). Thus # l_ctr = R_moon*C0*pix_ctr/C. distortion_coefficient = ((res[7, 1] - res[1, 1]) / (oextent[3] - oextent[2])) if distortion_coefficient < 0.7: raise ValueError("Distortion Coefficient cannot be" " {0:.2f}!".format(distortion_coefficient)) pixperkm = trf.km2pix(imgo.size[1], llbd[3] - llbd[2], dc=distortion_coefficient, a=arad) ctr_xy["Diameter (pix)"] = ctr_xy["Diameter (km)"] * pixperkm # Determine x, y position of central lat/long. centrallonglat = pd.DataFrame({"Long": [xll[4]], "Lat": [yll[4]]}) centrallonglat_xy = WarpCraterLoc(centrallonglat, geoproj, oproj, oextent, imgwshp, llbd=llbd_in, origin=origin) # Shift central long/lat centrallonglat_xy.loc[:, "x"] += offset[0] centrallonglat_xy.loc[:, "y"] += offset[1] return [imgo, ctr_xy, distortion_coefficient, centrallonglat_xy]