def test_platecarreetoorthographic(self, box):
        """Test full Plate Carree to orthographic transform.

        Assumes input_data_gen's image and crater position warping
        functions are correct. Output of this function was tested by visual
        inspection.
        """

        box = np.array(box, dtype='int32')
        # Crop image.
        img = np.asanyarray(self.img.crop(box))

        # Determine long/lat and output projection.
        llbd = self.get_llbd(box)
        oproj = ccrs.Orthographic(central_longitude=np.mean(llbd[:2]),
                                  central_latitude=np.mean(llbd[2:]),
                                  globe=self.iglobe)

        # Determine coordinates of image limits in input and output projection
        # coordinates.
        iextent, oextent, ores = self.get_extents(llbd, self.geoproj,
                                                  self.iproj, oproj)

        # Obtain image from igen.WarpImagePad.
        imgo, imgwshp, offset = igen.WarpImagePad(
            img, self.iproj, iextent, oproj, oextent, origin="upper",
            rgcoeff=1.2, fillbg="black")

        ctr_xy = igen.WarpCraterLoc(self.craters, self.geoproj, oproj,
                                    oextent, imgwshp, llbd=llbd,
                                    origin="upper")
        ctr_xy.loc[:, "x"] += offset[0]
        ctr_xy.loc[:, "y"] += offset[1]

        distortion_coefficient = ((ores[7, 1] - ores[1, 1]) /
                                  (oextent[3] - oextent[2]))
        pixperkm = trf.km2pix(imgo.size[1], llbd[3] - llbd[2],
                              dc=distortion_coefficient, a=1737.4)
        ctr_xy["Diameter (pix)"] = ctr_xy["Diameter (km)"] * pixperkm


        # Determine x, y position of central lat/long.
        centrallonglat = pd.DataFrame({"Long": [np.mean(llbd[:2])],
                                       "Lat": [np.mean(llbd[2:])]})
        centrallonglat_xy = igen.WarpCraterLoc(centrallonglat, self.geoproj,
                                               oproj, oextent, imgwshp,
                                               llbd=llbd, origin="upper")
        centrallonglat_xy.loc[:, "x"] += offset[0]
        centrallonglat_xy.loc[:, "y"] += offset[1]

        img_pc, ctr_pc, dc_pc, cll_pc = igen.PlateCarree_to_Orthographic(
            self.img.crop(box), llbd, self.craters, iglobe=self.iglobe,
            ctr_sub=False, arad=1737.4, origin="upper", rgcoeff=1.2,
            slivercut=0.)

        assert np.all(np.asanyarray(img_pc) == np.asanyarray(imgo))
        assert np.all(ctr_pc == ctr_xy)
        assert dc_pc == distortion_coefficient
        assert np.all(cll_pc == centrallonglat_xy)
Beispiel #2
0
 def test_km2pix(self, imgheight, latextent, dc):
     mypixperkm = (180. / (np.pi * 1737.4)) * (imgheight * dc / latextent)
     pixperkm = trf.km2pix(imgheight, latextent, dc=dc, a=1737.4)
     assert np.isclose(mypixperkm, pixperkm, rtol=1e-10, atol=0.)
     # degperpix used in get_unique_craters.
     degperpix = (180. / (np.pi * 1737.4)) / pixperkm
     mydegperpix = latextent / imgheight / dc
     assert np.isclose(degperpix, mydegperpix, rtol=1e-10, atol=0.)
Beispiel #3
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 * 1737.4)
    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 test_resamplecraters(self, llbd, minpix):
     ctr_xlim = ((self.craters['Long'] >= llbd[0]) &
                 (self.craters['Long'] <= llbd[1]))
     ctr_ylim = ((self.craters['Lat'] >= llbd[2]) &
                 (self.craters['Lat'] <= llbd[3]))
     ctr_sub = self.craters.loc[ctr_xlim & ctr_ylim, :]
     imgheight = int(3000. * (llbd[3] - llbd[2]) / 180.)
     pixperkm = trf.km2pix(imgheight, llbd[3] - llbd[2])
     minkm = minpix / pixperkm
     ctr_sub = ctr_sub[ctr_sub['Diameter (km)'] >= minkm]
     ctr_sub.reset_index(drop=True, inplace=True)
     ctr_rs = igen.ResampleCraters(self.craters, llbd, imgheight,
                                   minpix=minpix)
     assert np.all(ctr_rs == ctr_sub)
Beispiel #5
0
def ResampleCraters(craters, llbd, imgheight, arad=3390, 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
Beispiel #6
0
def PlateCarree_to_Orthographic(img,
                                llbd,
                                craters,
                                iglobe=None,
                                ctr_sub=False,
                                arad=3390,
                                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]

    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]

    print(ctr_xy)
    print(centrallonglat_xy)

    return [imgo, ctr_xy, distortion_coefficient, centrallonglat_xy]