Ejemplo n.º 1
0
def cassini_surfintercerpt(utc, output=False):
    target = 'TITAN'
    fixref = 'IAU_TITAN'
    dref = 'IAU_TITAN'
    method = 'ELLIPSOID'
    abcorr = 'NONE'
    obsrvr = 'CASSINI'
    state = cassini_phase(utc)
    dvec = spice.vhat(-state[:3])

    et = spice.str2et(utc)

    [point, trgepc, srfvec] = spice.sincpt(method, target, et, fixref, abcorr,
                                           obsrvr, dref, dvec)
    temp = spice.reclat(point)

    radius = temp[0]
    colat = 90 + spice.convrt(temp[1], "RADIANS", "DEGREES")
    lon = np.mod(spice.convrt(temp[2], "RADIANS", "DEGREES"), 360)

    if output:
        print("Distance to Intercept Point", spice.vnorm(srfvec))
        print("Radius", radius)
        print("Colatitude", colat)
        print("Longtitude", lon)

    return point, [radius, colat, lon], spice.vnorm(srfvec)
Ejemplo n.º 2
0
def dn2iof(DN, et, exptime, mode='4X4'):
    
    """
    Convert DN values in array, to I/F values.
    
    Assumes target of Jupiter.
    
    Parameters
    -----

    DN: Array of DN values.
    
    ET: Time in ET
    
    exptime: Exposure time in seconds
    
    Optional Parameters
    -----
    Mode: '1X1' or '4X4'.
    
    """
        
    RSOLAR_LORRI_1X1 = 221999.98  # Diffuse sensitivity, LORRI 1X1. Units are (DN/s/pixel)/(erg/cm^2/s/A/sr)
    RSOLAR_LORRI_4X4 = 3800640.0  # Diffuse sensitivity, LORRI 1X1. Units are (DN/s/pixel)/(erg/cm^2/s/A/sr)
    
    C = DN # Get the DN values of the ring. Typical value is 1 DN.
    
    # Define the solar flux, from Hal's paper.
    
    FSOLAR_LORRI  = 176.	     	    # We want to be sure to use LORRI value, not MVIC value!
    F_solar = FSOLAR_LORRI # Flux from Hal's paper

    if '4' in mode:    
        RSOLAR = RSOLAR_LORRI_4X4
    if '1' in mode:
        RSOLAR = RSOLAR_LORRI_1X1
    
    # Calculate the Jup-Sun distance, in AU.
    
    km2au = 1 / (u.au/u.km).to('1')
    
    # et = sp.utc2et(t_group['UTC'][0])
    
    (st,lt) = sp.spkezr('Jupiter', et, 'J2000', 'LT', 'New Horizons')
    r_nh_jup = sp.vnorm(st[0:3]) * km2au # NH distance, in AU
    
    (st,lt) = sp.spkezr('Jupiter', et, 'J2000', 'LT', 'Sun')
    r_sun_jup = sp.vnorm(st[0:3]) * km2au # NH distance, in AU
    
    # pixscale_km =  (r_nh_jup/km2au * (0.3*hbt.d2r / 256))  # km per pix (assuming 4x4)
    
    TEXP = exptime
    
    I = C / TEXP / RSOLAR   # Could use RSOLAR, RJUPITER, or RPLUTO. All v similar, except for spectrum assumed.
    
    # Apply Hal's conversion formula from p. 7, to compute I/F and print it.
    
    IoF = math.pi * I * r_sun_jup**2 / F_solar # Equation from Hal's paper

    return IoF
Ejemplo n.º 3
0
    def vector_params(self, vector) : 

        ret = np.full(shape = [len(self.keys)], fill_value = -1000.0)
        
        # Calculate the distance of the boresight and the center of the target
        origin = np.array([0.0, 0.0, 0.0])
        nearpoint, rayradius = spice.nplnpt(self.scloc, vector, origin)

        # Get the intercept to calculate the radius of of the target 
        normal = spice.surfpt(origin, 1000.0 * nearpoint, self.radii[0], self.radii[1], self.radii[2])
        ret[self.map['limb_distance']] = rayradius - spice.vnorm(normal)
    
        # Calculate the 'occultation' latitude and longitude 
        ret[self.map['bodyintercept']], ret[self.map['limb_lat']], ret[self.map['limb_lon']] = spice.reclat(nearpoint) 

        # Test if the boresight intersects with our target surface 
        try: 
            point = spice.surfpt(self.scloc, vector, self.radii[0], self.radii[1], self.radii[2])
            intercept = True
        except: 
            intercept = False
    
        if (intercept) : 
        
            # Get some angles 
            ret[self.map['phase']], ret[self.map['incidence']], ret[self.map['emission']] = spice.illum(self.target, self.et , self.abcorr, self.spacecraft, point)

            # Calculate the planetocentric coordinates 
            distance, ret[self.map['lon']], ret[self.map['lat']] = spice.reclat(point)
            ret[self.map['losdist']] = spice.vnorm(self.scloc - point)
            
             # Calculate the planetographic coordinates
            ret[self.map['lon_graphic']], ret[self.map['lat_graphic']], bodyintercept = spice.recpgr(self.target, point, self.radii[0], self.flattening)
            
            # Get the localtime, and convert to decimal hours 
            hr, min, sc, time, ampm = spice.et2lst(self.et, self.target_id, ret[self.map['lon']], 'PLANETOCENTRIC')
            ret[self.map['localtime']] = hr + min / 60.0 + sc / 3600.0
            
        # For the angles, convert radians to degrees
        for key in self.angles : 
            if (ret[self.map[key]] != -1000.0) : 
                    ret[self.map[key]] = np.rad2deg(ret[self.map[key]])

        # Makes sure longitudes wrap 0 to 360, spice returns the Earth-like -180 to 180. 
        longitudes = ['lon', 'limb_lon', 'lon_graphic']
        for key in longitudes : 
            ret[self.map[key]] = ret[self.map[key]] % 360

        return np.array(ret)
Ejemplo n.º 4
0
def get_pixel_size_km(probe: str, body: str, time: datetime,
                      fov_full_angle_deg: float, fov_full_px: int) -> float:
    """ Calculates size of one pixel on body's surface in km at given time.

    :param probe: SPICE name of probe
    :param body: SPICE name of target body
    :param time: datetime of computation
    :param fov_full_angle_deg: full angle of one FOV dimension
    :param fov_full_px: full pixel count of the same FOV dimension
    :return: length of square covered by one pixel in kilometers
    """
    if fov_full_angle_deg <= 0.0:
        raise ValueError("fov_full_angle_deg must be positive.")
    if fov_full_angle_deg > 90.0:
        raise ValueError(
            f"with fov_full_angle_deg = {fov_full_angle_deg} the calculation would be wildly inaccurate."
        )
    if fov_full_px < 1:
        raise ValueError("fov_full_px must be at least 1")

    et = datetime2et(time)
    nadir_vec = spy.subpnt("INTERCEPT/ELLIPSOID", body, et, f"IAU_{body}",
                           "LT+S", probe)[2]
    nadir_dist = spy.vnorm(nadir_vec)
    half_angle_rad = 0.5 * fov_full_angle_deg * np.pi / 180
    fov_half_px = fov_full_px / 2
    half_angle_km = np.tan(half_angle_rad) * nadir_dist
    return half_angle_km / fov_half_px
Ejemplo n.º 5
0
    def __init__(self, file):
        
            # Get the speed relative to MU69

        file_tm = 'kernels_kem_prime.tm'
        
        # Start up SPICE if needed
        
        if (sp.ktotal('ALL') == 0):
            sp.furnsh(file_tm)    
            
        utc_ca = '2019 1 Jan 05:33:00'
        et_ca  = sp.utc2et(utc_ca) 
        (st,lt) = sp.spkezr('New Horizons', et_ca, 'J2000', 'LT', 'MU69')
        
        velocity = sp.vnorm(st[3:6])*u.km/u.s
    
        # Save the velocity (relative to MU69)

        self.velocity = velocity

        # Save the name of file to read
        
        self.file = file

        # Save the area of the s/c
        
        self.area_sc = (1*u.m)**2
        
        return
Ejemplo n.º 6
0
 def update_text(date_time):
     obs2SunVector = sp.spkpos("SUN", date_time, referenceFrame,
                               SPICE_ABERRATION_CORRECTION,
                               SPICE_OBSERVER)[0]
     obs2SunUnitVector = obs2SunVector / sp.vnorm(obs2SunVector)
     #    obs2SunAngle = sp.vsep(obs2SunUnitVector, np.asfarray([0.0, 0.0, 1.0]))
     return obs2SunUnitVector
Ejemplo n.º 7
0
def EoM_Nbody(y, t, masas):
    M = len(y)
    N = int(M / 6)

    #Vector de estado
    r = np.zeros((N, 3))
    v = np.zeros((N, 3))

    #Vectores nulo con las derivadas
    drdt = np.zeros((N, 3))
    dvdt = np.zeros((N, 3))

    #Asignacin de los vectores de estado
    for i in range(N):
        r[i] = y[3 * i:3 * i + 3]
        v[i] = y[3 * N + 3 * i:3 * N + 3 * i + 3]

    # Ecuaciones de movimiento
    for i in range(N):
        drdt[i] = v[i]
        for j in range(N):
            if i == j: continue
            dvdt[i] += -masas[j] / spy.vnorm(r[i] - r[j])**3 * (r[i] - r[j])

    # Devuelve derivadas
    dydt = np.array([])
    for i in range(N):
        dydt = np.concatenate((dydt, drdt[i]))
    for i in range(N):
        dydt = np.concatenate((dydt, dvdt[i]))
    return dydt
Ejemplo n.º 8
0
def sunDistanceAU(time: str, target: str) -> float:
    """Returns distance in AU between Sun and observed body from MRO."""

    base_kernel_path = Path(isis.environ["ISIS3DATA"]) / "base" / "kernels"
    lsk = sorted(Path(base_kernel_path / "lsk").glob("naif*.tls"))[-1]
    pck = sorted(Path(base_kernel_path / "spk").glob("de*.bsp"))[-1]
    sat = sorted(Path(base_kernel_path / "spk").glob("mar*.bsp"))[-1]

    sclk = sorted(
        Path(
            Path(isis.environ["ISIS3DATA"]) / "mro" / "kernels" / "sclk"
        ).glob("MRO_SCLKSCET.*.65536.tsc")
    )[-1]

    spiceypy.furnsh([str(lsk), str(pck), str(sat), str(sclk)])

    et = spiceypy.scs2e(-74999, time)

    targ = target.lower()
    if targ == "sky" or targ == "cal" or targ == "phobos" or targ == "deimos":
        targ = "mars"

    (sunv, lt) = spiceypy.spkpos(targ, et, "J2000", "LT+S", "sun")

    sunkm = spiceypy.vnorm(sunv)
    # Return in AU units
    return sunkm / 1.49597870691e8
Ejemplo n.º 9
0
def EoM_CRTBP(y, t, alpha):
    r1 = np.array([-alpha, 0, 0])
    r2 = np.array([1 - alpha, 0, 0])
    omega = np.array([0, 0, 1])

    r = y[:3]
    v = y[3:]

    R1 = r - r1
    R2 = r - r2

    drdt = v
    dvdt = -(1 - alpha) / spy.vnorm(
        R1)**3 * R1 - alpha / spy.vnorm(R2)**3 * R2 - np.cross(
            omega, np.cross(omega, r)) - 2 * np.cross(omega, v)

    dydt = drdt.tolist() + dvdt.tolist()

    return dydt
Ejemplo n.º 10
0
def findBoresightUsed(et, boresight_vectors, boresight_names):

    obs2SunVector = sp.spkpos("SUN", et, SPICE_REFERENCE_FRAME, SPICE_ABERRATION_CORRECTION, SPICE_OBSERVER)
    
    v_norm = obs2SunVector[0]/sp.vnorm(obs2SunVector[0])
    
    v_sep_min = 999.0
    for boresight_vector, boresight_name in zip(boresight_vectors, boresight_names):
        v_sep = sp.vsep(v_norm, boresight_vector) * sp.dpr() * 60.0 #arcmins difference
        if v_sep < v_sep_min:
            v_sep_min = v_sep
            boresight_found = boresight_name
    return boresight_found, v_sep_min
Ejemplo n.º 11
0
def printBoresights(angleSeparationA, angleSeparationB):
    """input manual rotation angles from SPICE kernels to calculate new and old boresight"""
    oldSoBoresight = [0.0, 0.0, 1.0]
    oldUVISBoresight = [0.0, 0.0, 1.0]
    rotationMatrixSoUVIS = sp.pxform("TGO_NOMAD_SO", "TGO_NOMAD_UVIS_OCC",
                                     sp.utc2et("2018 APR 01 00:00:00 UTC"))
    oldSoBoresightUVIS = np.dot(oldSoBoresight, rotationMatrixSoUVIS.T)
    oldBoresightSeparation = sp.vsep(oldUVISBoresight,
                                     oldSoBoresightUVIS) * sp.dpr() * 60.0
    print("oldBoresightSeparation")
    print(oldBoresightSeparation)

    print("angleSeparationB")
    print(angleSeparationB)
    #####SAVE THIS IT WORKS!!!######
    newSoBoresightTGO = np.asfarray([
            -1.0 * np.sin(angleSeparationB / sp.dpr()), \
            np.sin(angleSeparationA / sp.dpr()) * np.cos(angleSeparationB / sp.dpr()), \
            np.cos(angleSeparationA / sp.dpr()) * np.cos(angleSeparationB / sp.dpr())])

    print("newSoBoresightTGO, vnorm = %0.6f" % sp.vnorm(newSoBoresightTGO))
    print(newSoBoresightTGO)

    newUVISBoresightTGO = np.asfarray(
        [-0.922221097920913, -0.386613383297695, 0.006207330031467])
    oldSoBoresightTGO = np.asfarray([-0.92156, -0.38819, 0.00618])
    oldUVISBoresightTGO = np.asfarray(
        [-0.92207347097, -0.3869614566418, 0.0064300242046])

    oldNewSoBoresightSeparation = sp.vsep(newSoBoresightTGO,
                                          oldSoBoresightTGO) * sp.dpr() * 60.0
    print("oldNewSoBoresightSeparation")
    print(oldNewSoBoresightSeparation)

    oldNewUVISBoresightSeparation = sp.vsep(
        newUVISBoresightTGO, oldUVISBoresightTGO) * sp.dpr() * 60.0
    print("oldNewUVISBoresightSeparation")
    print(oldNewUVISBoresightSeparation)

    newSoUVISBoresightSeparation = sp.vsep(
        newSoBoresightTGO, newUVISBoresightTGO) * sp.dpr() * 60.0
    print("newSoUVISBoresightSeparation")
    print(newSoUVISBoresightSeparation)

    oldSoUVISBoresightSeparation = sp.vsep(
        oldSoBoresightTGO, oldUVISBoresightTGO) * sp.dpr() * 60.0
    print("oldSoUVISBoresightSeparation")
    print(oldSoUVISBoresightSeparation)
Ejemplo n.º 12
0
    def sc_pos(self, timestr):

        # Convert the timestring to ephemeris time
        self.et = spice.str2et(str(timestr))
        self.et = self.et + self.time_offset

        self.data['sc_wrt_planet'], self.data['target_light_time'] = spice.spkpos(self.spacecraft, self.et, self.iref, 'NONE', self.target)
        self.data['target_distance'] = spice.vnorm(self.data['sc_wrt_planet'])
        self.data['sc_wrt_sun'], light_times = spice.spkpos(self.spacecraft, self.et, self.iref, 'NONE', 'SUN')

        #self.ticks = spice.sce2t(self.sc_id, self.et)

        # Retrieve the position of the spacecraft relative to the target 
        state, self.ltcorr = spice.spkezr(self.target, self.et, self.iref, self.abcorr, self.spacecraft)
        scloc = state[0:3]

        # Get the sub-spacecraft coordinates
        point, epoch, vector = spice.subpnt('NEAR POINT/ELLIPSOID', self.target, self.et, self.framestring, self.abcorr, self.spacecraft)
        self.distance, self.data['lon_sc'], self.data['lat_sc'] = spice.reclat(point)

        # Get the localtime of the spaceship        
        hr, min, sc, time, ampm = spice.et2lst(self.et, self.target_id, self.data['lon_sc'], 'PLANETOCENTRIC')
        self.data['localtime_sc'] = hr + min / 60.0 + sc / 3600.0
        
        # Get the subsolar coordinates
        point, epoch, vector = spice.subslr('NEAR POINT/ELLIPSOID', self.target, self.et, self.framestring, self.abcorr, self.spacecraft)
        distance, self.data['lon_sun'], self.data['lat_sun'] = spice.reclat(point)

        # Convert angles from radians to degrees
        for key in ['lat_sc', 'lon_sc', 'lat_sun', 'lon_sun'] : self.data[key] = np.rad2deg(self.data[key])
        for key in ['lon_sc', 'lon_sun'] : self.data[key] = self.data[key] % 360
	
        state_planet = spice.spkssb(self.target_id, self.et, self.iref)
        inert2p = spice.pxform(self.iref, self.framestring, self.et)
        self.scloc = np.matmul(-inert2p, scloc) 

        self.i2p = spice.pxform(self.instrument, self.framestring, self.et)
        
        return self.data
Ejemplo n.º 13
0
def dooneRandom():
    ### Random normal unit vector
    unitNormal = sp.vhat([r.uniform(-1, 1) for i in xrange(3)])

    ### Random positive semi-axes' lengths
    semi_axes = [abs(r.uniform(.5, 100)) for i in xrange(3)]

    ### Solve for surface point with that surface normal
    xyz = norm2surfpt(semi_axes, unitNormal)

    ### Use spiceypy.surfnm() to calculate surface unit normal vector, at
    ### solved-for surface point vector xyz, to compare to input unit normal
    ### vector, and calculate error magnitude, errMag

    surfnm = sp.surfnm(semi_axes[0], semi_axes[1], semi_axes[2], xyz)
    errVec = sp.vsub(surfnm, unitNormal)
    errMag = sp.vnorm(errVec)

    if doLog:
        pprint.pprint(dict(errVec=errVec, errMag=errMag))

    ###return error magnitude
    return errMag
      f'{TIME_ARRAY[indices_min[1]]}')
print('\n')

#%%

# ... but is the ion tail "aiming" towards the trajectory of the spacecraft?
# (at least within a few degrees?)
# Compute the angular distance between the trajectories' closest approach

# Set the closest approach vectors, based on the obtained indices for ATLAS and
# the Solar Orbiter, respectively
VEC_ATLAS_AP = atlas_vecs[indices_min[0]]
VEC_SOLAR_ORB_AP = solar_orb_vecs[indices_min[1]]

# Determine the norm of both closest approach vectors
ATLAS_NORM_AP = spiceypy.vnorm(VEC_ATLAS_AP)
SOLORB_NORM_AP = spiceypy.vnorm(VEC_SOLAR_ORB_AP)

# Compute the dot product
DOT_PRODUCT_AP = np.dot(VEC_ATLAS_AP, VEC_SOLAR_ORB_AP)

# Compute the angle
ANGULAR_DIST_AP = np.degrees(np.arccos((DOT_PRODUCT_AP) \
                                       / (ATLAS_NORM_AP * SOLORB_NORM_AP)))

# Print the angular distance between ATLAS' ion tail direction and the position
# vector of the spacecraft at the closest approach
print('Minimum angular distance between a possible ion tail and the ' \
      'Solar Orbiter\'s trajectory in degrees: ' \
      f'{np.round(ANGULAR_DIST_AP, 2)}')
Ejemplo n.º 15
0
SOLAR_SYSTEM_DF.loc[:, 'POS_SSB_WRT_SUN'] = \
    SOLAR_SYSTEM_DF['ET'].apply(lambda x: spiceypy.spkgps(targ=0, \
                                                          et=x, \
                                                          ref='ECLIPJ2000', \
                                                          obs=10)[0])

# Now the SSB position vector is scaled with the Sun's radius
SOLAR_SYSTEM_DF.loc[:, 'POS_SSB_WRT_SUN_SCALED'] = \
    SOLAR_SYSTEM_DF['POS_SSB_WRT_SUN'].apply(lambda x: x / RADIUS_SUN)

# Finally the distance between the Sun and the SSB is computed. The length
# (norm) of the vector needs to be determined with the SPICE function vnorm().
# numpy provides an identical function in: numpy.linalg.norm()
SOLAR_SYSTEM_DF.loc[:, 'SSB_WRT_SUN_SCALED_DIST'] = \
    SOLAR_SYSTEM_DF['POS_SSB_WRT_SUN_SCALED'].apply(lambda x: \
                                                    spiceypy.vnorm(x))

#%%

# Import the matplotlib library
from matplotlib import pyplot as plt

# Set a figure
FIG, AX = plt.subplots(figsize=(12, 8))

# Plot the distance between the Sun and the SSB
AX.plot(SOLAR_SYSTEM_DF['UTC'], SOLAR_SYSTEM_DF['SSB_WRT_SUN_SCALED_DIST'], \
        color='tab:blue')

# Set a label for the x and y axis and color the y ticks accordingly
AX.set_xlabel('Date in UTC')
Ejemplo n.º 16
0
    degPerHour = 2.0 * dpr * twopi / hpd

    xTolerance = lambda xDiff: xDiff < 1e-10
    xDiffTolerance = lambda x, xExpect: xTolerance(abs(x - xExpect))

    et, iPass = et0, 0
    while et < (et0 + spd + 1):

        ### Get the state of the MINUTE and HOUR bodies every half hour
        stMinute, lt = sp.spkezr(sMinute, et, 'J2000', 'NONE', sClock)
        stHour, lt = sp.spkezr(sHour, et, 'J2000', 'NONE', sClock)

        if (iPass % 2):
            ### On the half hour, the minute hand will be at RA=180, along [-1,0,0]
            assert xTolerance(
                sp.vnorm(sp.vsub([-1.0, 0., 0.], sp.vhat(stMinute[:3]))))
        else:
            ### On the hour, the minute hand will be at RA=0, at [+1,0,0]
            assert xTolerance(
                sp.vnorm(sp.vsub([1.0, 0., 0.], sp.vhat(stMinute[:3]))))
            vsepDeg = dpr * sp.vsep(stHour[:3], stMinute[:3])
            vsepExpectDeg = (degPerHour * (iPass >> 1)) % 360
            ### On the hour, the houre hand will be (30 * iPass) degrees clockwise
            ### from +X, and therefor also the same from the minute hand
            assert xDiffTolerance(vsepDeg, vsepExpectDeg) or xDiffTolerance(
                (360 - vsepDeg), vsepExpectDeg)

        ### Step to next half hour and pass
        et += halfHour
        iPass += 1
Ejemplo n.º 17
0
### fixed star position with LT+S correction
earth2star_lts = sp.spkcpt(vstar, ssb, j2000, et, j2000, "observer", LTS,
                           "earth")[0]

if do_debug:
    ### Ensure that light-time correction is zero for earth->star vector
    ### for star at fixed positon relative to SSB
    earth2star_lt = sp.spkcpt(vstar, ssb, j2000, et, j2000, "observer", LT,
                              "earth")[0]
    assert 0.0 == sp.vnormg(sp.vsubg(earth2star_none, earth2star_lt, 6), 6)

### Calculate differences between gaiif_util.py and SPICE results ...
ab_vec_spice = sp.vsub(earth2star_lts[:3], earth2star_none[:3])
ab_vec_gaia = sp.vscl(range2star, sp.vsub(uvstar_ab, uvstar_noab))
ab_vec_diff = sp.vsub(ab_vec_spice, ab_vec_gaia)
ab_diff_frac = sp.vnorm(ab_vec_diff) / sp.vnorm(ab_vec_spice)

no_ab_diff = sp.vsep(uvstar_noab, sp.vhat(earth2star_none[:3]))
ab_diff = sp.vsep(uvstar_ab, sp.vhat(earth2star_lts[:3]))
ab_diff_frac = sp.vsep(uvstar_ab, sp.vhat(earth2star_lts[:3]))

try:
    ### ... Success
    assert 1e-15 > no_ab_diff
    assert 1e-8 > ab_diff
except:
    ### ... Failure
    print(
        dict(no_ab_diff=no_ab_diff,
             ab_diff=ab_diff,
             ab_diff_frac=ab_diff_frac,
Ejemplo n.º 18
0
    def __init__(self, dir, do_force=False, do_verbose=False, nmax=None, prefix='lor', do_save=False) :   

        """
        Init method: load the index and all files.
        
        This does not align, register, stack, or anything like that. It just loads the images.
        
        If a saved .pkl file is available, then it will be used instead of reading individual images.
        
        Parameters
        ----
        dir:
            The directory with all the files
            
        Optional keyword parameters
        ----
        do_force:
            Force loading stack from original files, rather than from a .pkl file.
        do_verbose:
            List each file explicitly when reading
        prefix:
            A text prefix which each filename must match (e.g., 'lor' to match only LORRI files).
        do_save:
            If set, save the results of this stacking as a .pkl file
            
        """
        
        # If we are passed a pickle file, then restore the file from pickle, rather than by reading in explicity.
        # I haven't tested this -- not sure if it works.
        
        if 'pkl' in dir:
            self.file_save = dir
            self.load()
            return
        
        name_target = 'MU69'
            
        do_lorri_destripe = True  # I didn't use this at first, but it is a clear improvement.
        
        do_lorri_dedark   = True  # Remove dark current from LORRI?
        
        files1 = glob.glob(os.path.join(dir,      prefix + '*.fit*'))     # Look in dir
        files2 = glob.glob(os.path.join(dir, '*', prefix + '*.fit*'))     # Look in subdirs
        
        files = files1 + files2
        
        # Truncate the list, if requested
        
        if (nmax):
            files = files[0:nmax]
            
        num_files = len(files)

        self.file_save      = os.path.join(dir, 'image_stack_n{}.pkl'.format(num_files))
        
        # Initialize the center of this image. The shfits of each image are taken to be relative to this.
        # It could be that we just keep this at zero. Time will tell.
        
        self.shift_x_pix_center = 0
        self.shift_y_pix_center = 0
        
        # Set the internal zoom level, which will be used when flattening
        
        self.zoom = 1
        
        # Set a flag to indicate if flattened or not
        
        self.flattened = False
        
        # If a save file exists, then load it, and immediately return
        
        if (os.path.isfile(self.file_save)) and not(do_force):
            self.load()
            return
        
        mode     = []
        exptime  = []
        filename_short = []
        exptime  = []
        visitnam = []
        sapname  = []
        sapdesc  = []
        reqid    = []
        et       = []
        utc      = []
        target   = []
                
        # Set up the table 't'. This is an astropy table within the stack, that has a list of all of the 
        # useful image parameters taken from the FITS header.
        
        # Fields in the table are:
        #   filename_short
        #   exptime
        #   visitname
        #   sapname
        #   sapdesc
        #   target
        #   reqid
        #   et
        #   utc
        #   shift_x_pix -- the shift of this image, relative to the zero point (tbd)
        #   shift_y_pix -- the shift of this image, relative to the zero point (tbd)
        #   ra    -- 
        #   dec
        #   angle
        #   dx_pix -- x dimension
        #   dy_pix -- y dimension
        
        
        self.t = Table(  [[],  [],            [],          [],         [],        [],       [],       [],      [],   [], 
                                                    [], [], [], [], [], [], [], [], [], [], [], [] ],
            names=('filename', 'filename_short', 'exptime', 'visitname', 'sapname', 'sapdesc', 'target', 'reqid', 'et',  'utc', 
                  'shift_x_pix', 'shift_y_pix', 'ra_center', 'dec_center', 'angle', 'dx_pix', 'dy_pix', 
                  'pixscale_x_km', 'pixscale_y_km', 'dist_target_km', 'wcs', 'data'),
            dtype = ('U150', 'U50',           'float64', 'U50',      'U50',     'U50',     'U50',    'U50',   'float64', 'U50', 
                    'float64', 'float64', 'float64', 'float64', 'float64', 'float64', 
                    'float64', 'float64', 'float64', 'float64', 'object', 'object'  ))
        
        # Read the LORRI dark frame. This is a one-off frame for MU69 approach, made by Marc Buie.
        # It is only valid for 4x4 LORRI.
        # Units are DN/sec of dark current.
        
        if do_lorri_dedark:
            file_dark = '/Users/throop/Data/MU69_Approach/2018dark_mwb_v2.fits'
            hdu = fits.open(file_dark)
            arr_lorri_dark = hdu['PRIMARY'].data
            hdu.close()
            
        if (len(files)):
            print("Reading {} files from {}".format(len(files), dir))
            
            for i,file in enumerate(files):
            
                # Read the data
            
                hdulist        = fits.open(file)
                arr            = hdulist[0].data
                err            = hdulist[1].data
                quality        = hdulist[2].data
                backplane_radius = hdulist['RADIUS_EQ'].data
                
                dx_pix         = hbt.sizex(arr)   # Usually 256 or 1024
                dy_pix         = hbt.sizey(arr)
                
                filename_short = os.path.basename(file).replace('.fits', '').replace('lor_', '')\
                         .replace('_0x633_pwcs','')\
                         .replace('_0x630_pwcs','')
                exptime = hdulist[0].header['EXPTIME']
                visitnam= hdulist[0].header['VISITNAM']
                sapname = hdulist[0].header['SAPNAME']
                sapdesc = hdulist[0].header['SAPDESC']
                target  = hdulist[0].header['TARGET']
                reqid   = hdulist[0].header['REQID']
                et      = hdulist[0].header['SPCSCET']
                angle   = hdulist[0].header['SPCEMEN']*hbt.d2r # Boresight roll angle
                utc     = sp.et2utc(et, 'C', 1)
            
                if do_verbose:
                    print("Read {}/{} {}".format(i, len(files), filename_short))
                else:
                    print(".", end="")
    
                hdulist.close()
            
                # Destripe if requested (aka remove jailbars)
                
                if do_lorri_destripe:
                    arr = hbt.lorri_destripe(arr)
                
                # Calibrate out dark current, if requested, and if a 4X4
                
                if do_lorri_dedark and (hbt.sizex(arr) == 256):
                    arr = arr - exptime * arr_lorri_dark
                
                # Read the WCS coords of this file.
                # XXX Suppress warnings about WCS SIP coords which Buie's files get.
                # However, looks like AstroPy doesn't use proper warning mechanism??
                
                with warnings.catch_warnings():
                    warnings.simplefilter("ignore")
                    w = WCS(file)
                
                # Get the RA/Dec location of the central pixel, in radians
                
                ra_center  = hdulist[0].header['CRVAL1']*hbt.d2r
                dec_center = hdulist[0].header['CRVAL2']*hbt.d2r
                
                pixscale_x = abs(hdulist[0].header['CD1_1']*hbt.d2r)  # radians per 4x4 pixel
                pixscale_y = abs(hdulist[0].header['CD2_2']*hbt.d2r)  # Sometimes this is negative?
                
                # Initialize the shift amount for this image, in pixels
                
                shift_x_pix = 0
                shift_y_pix = 0
                
                # Calc the distance to MU69, and the pixel scale (non-zoomed)
                
                (st, lt) = sp.spkezr(name_target, et, 'J2000', 'LT', 'New Horizons')
                dist_target_km = sp.vnorm(st[0:2])
                pixscale_x_km = dist_target_km * pixscale_x
                pixscale_y_km = dist_target_km * pixscale_y
                
                # Load the values for this image into a row of the astropy table
                # *** Need to add backplane data here, as planes[] or backplanes[] ***
            
                self.t.add_row(
                          [file, filename_short, exptime, visitnam, sapname, sapdesc, target, reqid, 
                           et, utc, 
                           shift_x_pix, shift_y_pix,
                           ra_center,   
                           dec_center,  
                           angle,
                           dx_pix, dy_pix, # X and Y dimensions
                           pixscale_x_km, pixscale_y_km,
                           dist_target_km,
                           w,              # WCS object 
                           arr])           # Actual imaging data 
            
            # End of loop over files
        else:
            print(f"No files found in {dir}!")
            return
        
#        if do_verbose:
        
        print("\n") # Print CR at end of "...."
            
        # Sort by ET.
            
        self.t.sort('et')
        
        # Save the pixel scale, from most recent image. We assume that the pixel scale of all frames is identical.
        
        self.pixscale_x = pixscale_x
        self.pixscale_y = pixscale_y
        
        self.pixscale_x_km = pixscale_x_km
        self.pixscale_y_km = pixscale_y_km
        
        self.dist_target_km   = dist_target_km
        
        self.et               = et 
        
        # Save the image size, from most recent image
        
        self.dx_pix     = dx_pix
        self.dy_pix     = dy_pix
        
        # Save the image stack size so it can be easily retrieved
        
        self.size       = (len(files), dx_pix, dy_pix) 
        
        # Finally, remove a few columns that we don't need, or that are wrong.
        
        self.t.remove_column('sapdesc')
        self.t.remove_column('sapname')
        self.t.remove_column('target')
        self.t.remove_column('visitname')

        # Initialize the 'indices' vector, which indicates which planes we use for flattening
    
        self.indices = np.ones(len(self.t), dtype=bool)
        
        # Initialize the num_planes vector to set the size
        
        self.num_planes = len(files)
    
        # If we generated the files manually (not by reloading), and flag is not set, then offer to save them
        
        # if not(do_save):
        #     # print(f'File to save: {self.file_save}')
        #     answer = input(f'Save to pickle file {self.file_save.split("/")[-1]}? ')
        #     if ('y' in answer):
        #         do_save = True
                
        if do_save:
            self.save()            
            
        # Return. Looks like an init method should not return anything.
    
        print()
Ejemplo n.º 19
0
        
        plt.plot((radec_corners[:,0]-ra_jup)*hbt.r2d * (-1), (radec_corners[:,1]-dec_jup)*hbt.r2d,
                 label = f'{index_group}/{index_image}') # Plot all the four points for this box
                
        is_limb_left = (radec_corners[0,0]-ra_jup)>0
        
        if is_limb_left:
            name_limb_arr.append('Left')
        else:
            name_limb_arr.append('Right')

        # Get the distance from Jupiter center to image center
        
        vec_nh_center = sp.radrec(1, radec_center[0][0], radec_center[0][1])  # Vector from NH, to center of LORRI frame
        ang_jup_center = sp.vsep(vec_nh_jup, vec_nh_center)                   # Ang Sep btwn Jup and LORRI, radians
        dist_jup_center_rj = ang_jup_center * sp.vnorm(vec_nh_jup) / rj_km    # Convert from radians into RJ
        width_lorri = 0.3*hbt.d2r                                             # LORRI full width, radians
    
        dist_jup_center_rj_range = np.array([ang_jup_center-width_lorri/2, ang_jup_center+width_lorri/2]) * \
            sp.vnorm(vec_nh_jup) / rj_km                                      # Finally, we have min and max dist
                                                                              # in the central row of array, in rj
        range_rj_arr.append(sp.vnorm(vec_nh_jup)/rj_km)
   
        # Calc the elevation angle (aka sub-obs lat on Jup)
        
        mx = sp.pxform('J2000', 'IAU_JUPITER', t_i['ET'])
        vec_nh_jup_jup = sp.mxv(mx, vec_nh_jup)
        (dist, lon, lat) = sp.recrad(-vec_nh_jup_jup)
        
        angle_elev_arr.append(lat * hbt.r2d)  # Save the sub-obs latitude, in degrees
        
Ejemplo n.º 20
0
def nh_ort1_make_stacks():
    
    """
    This program takes a directory full of individual NH KEM Hazard frames, stacks them, and subtracts
    a stack of background field. This reveals rings, etc. in the area.
    
    Written for NH MU69 ORT1, Jan-2018.  
    """
    
    do_force = False  # Boolean: Do we force reloading of all of the images from FITS, or just restore from pkl?
                      # Pkl (aka False) is faster. But if we have made changes to the core algorithms, must
                      # reload from disk (aka True).
    
    stretch_percent = 90    
    stretch = astropy.visualization.PercentileInterval(stretch_percent) # PI(90) scales to 5th..95th %ile.
    
    reqids_haz  = ['K1LR_HAZ00', 'K1LR_HAZ01', 'K1LR_HAZ02', 'K1LR_HAZ03', 'K1LR_HAZ04']
    reqid_field = 'K1LR_MU69ApprField_115d_L2_2017264'
    
    dir_data    = '/Users/throop/Data/ORT1/throop/backplaned/'

    zoom = 4
    
    # Set the edge padding large enough s.t. all output stacks will be the same size.
    # This value is easy to compute: loop over all stacks, and take max of stack.calc_padding()[0]
    
    padding = 61   
    # Start up SPICE if needed
    
    if (sp.ktotal('ALL') == 0):
        sp.furnsh('kernels_kem_prime.tm')
        
    # Set the RA/Dec of MU69. We could look this up from SPICE but it changes slowly, so just keep it fixed for now.
    
    radec_mu69 = (4.794979838984583, -0.3641418801015417)
    
    # Load and stack the field images
    
    stack_field = image_stack(os.path.join(dir_data, reqid_field), do_force=do_force)
    stack_field.align(method = 'wcs', center = radec_mu69)
    img_field  = stack_field.flatten(zoom=zoom, padding=padding)
    
    if do_force:
        stack_field.save()

    hbt.figsize((12,12))
    hbt.set_fontsize(15)
    
    for reqid in reqids_haz:
        stack_haz = image_stack(os.path.join(dir_data, reqid), do_force=do_force)
        stack_haz.align(method = 'wcs', center = radec_mu69)
        img_haz  = stack_haz.flatten(zoom=zoom, padding=padding)

        if do_force:
            stack_haz.save()
            
        # Make the plot
        
        diff = img_haz - img_field
        diff_trim = hbt.trim_image(diff)
        plt.imshow(stretch(diff_trim))
        plt.title(f"{reqid} - field, zoom = {zoom}")

        # Save the stacked image as a FITS file
        
        file_out = os.path.join(dir_data, reqid, "stack_n{}_z{}.fits".format(stack_haz.size[0], zoom))
        hdu = fits.PrimaryHDU(stretch(diff_trim))
        hdu.writeto(file_out, overwrite=True)
        print(f'Wrote: {file_out}')    
        
        # Save the stack as a PNG
        
        file_out_plot_stack = file_out.replace('.fits', '.png')
        plt.savefig(file_out_plot_stack, bbox_inches='tight')
        print("Wrote: {}".format(file_out_plot_stack))

        # Display it 
        # This must be done *after* the plt.savefig()
        
        plt.show()
        
        # Make a radial profile
        
        pos =  np.array(np.shape(diff))/2
        (radius, profile) = get_radial_profile_circular(diff, pos=pos, width=1)
    
        hbt.figsize((10,8))
        hbt.set_fontsize(15)
        plt.plot(radius, profile)
        plt.xlim((0, 50*zoom))
        plt.ylim((-1,np.amax(profile)))
        plt.xlabel('Radius [pixels]')
        plt.title(f'Ring Radial Profile, {reqid}, zoom={zoom}')
        plt.ylabel('Median DN')
        plt.show()

# =============================================================================
# Calculate how many DN MU69 should be at encounter (K-20d, etc.)
# Or alternatively, convert all of my DN values, to I/F values
# =============================================================================

        # Convert DN values in array, to I/F values
            
        RSOLAR_LORRI_1X1 = 221999.98  # Diffuse sensitivity, LORRI 1X1. Units are (DN/s/pixel)/(erg/cm^2/s/A/sr)
        RSOLAR_LORRI_4X4 = 3800640.0  # Diffuse sensitivity, LORRI 1X1. Units are (DN/s/pixel)/(erg/cm^2/s/A/sr)
        
        C = profile # Get the DN values of the ring. Typical value is 1 DN.
        
        # Define the solar flux, from Hal's paper.
        
        FSOLAR_LORRI  = 176.	     	    # We want to be sure to use LORRI value, not MVIC value!
        F_solar = FSOLAR_LORRI # Flux from Hal's paper
        
        RSOLAR = RSOLAR_LORRI_4X4
        
        # Calculate the MU69-Sun distance, in AU (or look it up). 
        
        km2au = 1 / (u.au/u.km).to('1')
        
        et = stack_haz.t['et'][0]
        (st,lt) = sp.spkezr('MU69', et, 'J2000', 'LT', 'New Horizons')
        r_nh_mu69 = sp.vnorm(st[0:3]) * km2au # NH distance, in AU
        
        (st,lt) = sp.spkezr('MU69', et, 'J2000', 'LT', 'Sun')
        r_sun_mu69 = sp.vnorm(st[0:3]) * km2au # NH distance, in AU
        
        pixscale_km =  (r_nh_mu69/km2au * (0.3*hbt.d2r / 256)) / zoom # km per pix (assuming 4x4)
        
        TEXP = stack_haz.t['exptime'][0]
        
        I = C / TEXP / RSOLAR   # Could use RSOLAR, RJUPITER, or RPLUTO. All v similar, except for spectrum assumed.
        
        # Apply Hal's conversion formula from p. 7, to compute I/F and print it.
        
        IoF = math.pi * I * r_sun_mu69**2 / F_solar # Equation from Hal's paper
        
        plt.plot(radius * pixscale_km, IoF)
        plt.xlim((0, 50000))
        plt.ylim((-1e-7, 4e-7))
#        plt.ylim((0,np.amax(IoF)))
#        plt.yscale('log')
        plt.xlabel('Radius [km]')
        plt.title(f'Ring Radial Profile, {reqid}, zoom={zoom}')
        plt.ylabel('Median I/F')
        file_out_plot_profile = file_out.replace('.fits', '_profile.png')
        plt.savefig(file_out_plot_profile, bbox_inches='tight')
        plt.show()
        print(f'Wrote: {file_out_plot_profile}')
        
        # Write it to a table
        t = Table([radius, radius * pixscale_km, profile, IoF], names = ['RadiusPixels', 'RadiusKM', 'DN/pix', 'I/F'])
        file_out_table = file_out.replace('.fits', '_profile.txt')
        t.write(file_out_table, format='ascii', overwrite=True)
        print("Wrote: {}".format(file_out_table))
Ejemplo n.º 21
0
    d_radius = 500

if (sequence == 'D211'):
    d_radius = 1000

if (sequence == 'D202_LORRI'):
    d_radius = 500

if (sequence == 'D305_LORRI'):
    d_radius = 4000
    
# Look up the distance using SPICE

for name_body_i in name_body:
  (vec_body,junk) = sp.spkezr(name_body_i, et, 'IAU_PLUTO', 'LT', 'Pluto')
  d_pluto_body[name_body_i] = sp.vnorm(vec_body[0:3])
   
# Generate a pixel mask showing the orbit of each body

  mask_orbit[name_body_i] = \
    np.array(radius > (d_pluto_body[name_body_i] - d_radius)) & \
    np.array(radius < (d_pluto_body[name_body_i] + d_radius))

    
r_h = d_pluto_body['Hydra']  # Hydra orbital radius

mask_orbit['Hydra x 2'] = \
    np.array(radius > (r_h*2 - d_radius)) & np.array(radius < (r_h*2 + d_radius))
    
mask_orbit['Hydra x 4'] = \
    np.array(radius > (r_h*4 - d_radius)) & np.array(radius < (r_h*4 + d_radius))
Ejemplo n.º 22
0
ddt = 1*minute   # Time offset for my calculation of velocity

width_pix_rad_4x4 = 4 * (0.3*hbt.d2r / 1024)  # LORRI 4x4 pixel size, in radians
width_pix_rad_1x1 =     (0.3*hbt.d2r / 1024)  # LORRI 4x4 pixel size, in radians

et_0 = et_ca + dt

(state_0, lt_0) = sp.spkezr('MU69', et_0,            'J2000', 'LT+S', 'New Horizons')
(state_1, lt_1) = sp.spkezr('MU69', et_0 + ddt, 'J2000', 'LT+S', 'New Horizons')

(junk, ra_0, dec_0) = sp.recrad(state_0[0:3])
(junk, ra_1, dec_1) = sp.recrad(state_1[0:3])

omega_kbo = sp.vsep(state_0[0:3], state_1[0:3]) / ddt  # Radians per second of sky motion that the KBO has, from NH

dist_kbo = sp.vnorm(state_0[0:3])  # Distance to KBO, in km

# Calculate the shadow velocity of the KBO

v_shadow_kbo = omega_kbo * dist_kbo  # km/sec of the shadow

# Calculate the time resolution of the LORRI driftscan.
# That is, how long does it take for LORRI to drift one 4x4 LORRI pixel?
# [A: Basically one sec: 0.681 sec, technically.]

dt_lorri_driftscan = width_pix_rad_4x4 / rate_drift

# Calculate Roche radius 

r_kbo       = 16.5 * u.km
r_roche     = 2.5 * r_kbo
Ejemplo n.º 23
0
def norm2surfpt(semi_axes, inputNormal):

    ### Ellipsoid semi-axes' lengths, per the ellipsoid formula:
    ###
    ###        2           2           2
    ###   / x \       / y \       / z \
    ###  ( --- )  +  ( --- )  +  ( --- ) =  1
    ###   \ a /       \ b /       \ c /

    ### Ensure abc components are positive
    abc = sp.vequ([abs(semi_axis) for semi_axis in semi_axes[:3]])

    ### Get unit vector, n, parallel to input normal
    n = sp.vhat(inputNormal)

    ### Direction of normal, N, at [x,y,z] is [ x/(a*a), y/(b*b), z/(c*c)]
    ### - Vector N is unknown, and is not necessarily a unit vector
    ### - Argument inputNormal is parallel to N, and of arbitrary length
    ### - Unit vector parallel to N is n, calculated above from inputNormal
    ### - Assume length of N is scalar 1/k; k is initially unknown
    ### - Scaling unit normal, n, by k yields N:
    ###
    ###     n/k = N = [x/(a*a), y/(b*b), z/(c*c)]      Eqn. 1
    ###
    ### so
    ###
    ###     nx/k = x/(a*a)                                    Eqn. 2x
    ###     ny/k = y/(b*b)                                    Eqn. 2y
    ###     nz/k = z/(c*c)                                    Eqn. 2z
    ###
    ### and, solving for surface point components, [x,y,z]:
    ###
    ###     x = nx*a*a/k                                      Eqn. 3x
    ###     y = ny*b*b/k                                      Eqn. 3y
    ###     z = nz*c*c/k                                      Eqn. 3z
    ###
    ### Substituting Eqns. 3x, 3y, and 3z for x, y, and z
    ### back into the ellipsoid formula (x^2/a^2 + ... = 1):
    ###
    ###     (nx*a*a/k)^2/(a*a)
    ###   + (ny*b*b/k)^2/(b*b)
    ###   + (nz*c*c/k)^2/(c*c) = 1                            Eqn. 4
    ###
    ### and solving for k:
    ###
    ###     (nx*a)^2 + (ny*b)^2 + (nz*c)^2 = k^2              Eqn. 5
    ###
    ### Since nx, a, ny, b, nz, and c are all known, k
    ### can be calculated directly using Eqn. 5, and the
    ### surface point components, x, y, and z, can then
    ### be calculated using Eqns. 3x, 3y, and 3z.

    ### Calculate two vectors:
    ### - [a*a, b*b, c*c]
    ### - [nx*nx, ny*ny, nz*nz]

    abcXabc = vXv(abc, abc)
    nXn = vXv(n, n)

    ### Solve for k using abcXabc, nXn, and Eqn. 5:

    k2 = sp.vdot(abcXabc, nXn)
    k = math.sqrt(k2)

    ### Use k, abcXabc, n, and Eqns. 3x, 3y, and
    ### 3z to calculate surface point vector xyz

    xyz = sp.vscl(1. / k, vXv(abcXabc, n))

    ### Debug logging:
    if doLog:
        ### Calculate (x/a)^2 + (y/b)^2 + (z/c)^2; it should be = 1
        one = sp.vdot(vXv(xyz, xyz), 1 / abcXabc)
        pprint.pprint(
            dict(n=n,
                 abc=abc,
                 abcXabc=abcXabc,
                 nXn=nXn,
                 nMag=sp.vnorm(n),
                 k=k,
                 k2=k2,
                 xyz=xyz,
                 one=one))

    ### Return surface point

    return xyz
def getData(eqdata):
    import spiceypy as spice
    import numpy as np
    import constants as con
    import math

    #Load the meta kernel file for the determination of the position of the planets.
    metakernel = '/home/jdw/UM2019Autumn/M561/Project/kernels/M561ProjectKernels.txt'
    spice.furnsh(metakernel)

    #Find the number of earthquakes to be analyzed.
    nquake = len(eqdata)

    #Create a numpy array with nquake rows and 49 columns.
    data = np.zeros((nquake, 284), dtype='float64')

    #Get the ephemeris times from the earthquake dictionary keys.
    et = list(eqdata.keys())

    #Get the earthquake data from the earthquake dictionary values.
    eqdata = list(eqdata.values())

    #Get the positions
    #First create a vector of planet names to be fed to the spice
    #function.
    Planets = [
        'Sun', 'Mercury_Barycenter', 'Venus_Barycenter', 'Moon',
        'Mars_Barycenter', 'Jupiter_Barycenter', 'Saturn_Barycenter',
        'Uranus_Barycenter', 'Neptune_Barycenter', 'Pluto_Barycenter'
    ]

    #Create a time offset index.  I want to look not only at the exact earthquake
    #time but also times that are a given number of seconds before and after
    #the earthquake.
    k = [3000, 2400, 1800, 1200, 600, 0, -600]

    for i in range(nquake):
        #First fill in the earthquake data.
        data[i][0] = et[i]
        data[i][1] = eqdata[i][0]  #Earthquake Magnitude
        data[i][2] = eqdata[i][1]  #Earthquake Latitude (degrees)
        data[i][3] = eqdata[i][2]  #Earthquake Longitude (degrees)

        #Now loop through the planets to get the distances.
        for p in range(len(Planets)):
            pindex = p * 28 + 4

            #Now loop through the seven times around the earthquake.
            for t in range(7):

                #Create a time value.  The earthquakes are given in the et
                #vector and I want to look at times before those earthquake
                #times.
                time = et[i] - k[t]

                #Determine the position of a given planet with respect to Earth
                #for a given et in the J2000 coordinate system.
                pos, ltime = spice.spkpos(
                    Planets[p],
                    time,
                    'J2000',
                    'NONE',
                    'Earth',
                )

                #Calculate the distance in meters. Vnorm returns a distance in kilometers
                #so we have to convert.  1km = 1000.0 meters.
                dist = spice.vnorm(pos) * 1000.0

                #Return the range, longitude and latitude of the position vector
                #pointing from Earth to the planet.  These are output in radians.
                ran, lon, lat = spice.reclat(pos)

                #Convert the radians to degrees.
                longitude = math.degrees(lon)
                latitude = math.degrees(lat)

                #Get the gravitational force for the times preceeding and
                #following the earthquake.
                F = getGravForce(p, dist)

                #Fill the data array.
                data[i][pindex + t * 4] = dist
                data[i][pindex + t * 4 + 1] = latitude
                data[i][pindex + t * 4 + 2] = longitude
                data[i][pindex + t * 4 + 3] = F

    return data
Ejemplo n.º 25
0
ceres_df.loc[:, 'UTC_PARSED'] = DATETIME_RANGE.strftime('%Y-%j')

# Convert the date-time to Ephemeris Time
ceres_df.loc[:, 'ET_TIME'] = ceres_df['UTC_TIME'] \
                                 .apply(lambda x: spiceypy.utc2et(str(x)))

#%%

# Compute the distance between Ceres and the Sun and convert the resulting
# distance value given in km to AU
ceres_df.loc[:, 'DIST_SUN_AU'] = \
    ceres_df['ET_TIME'].apply(lambda x: \
        spiceypy.convrt( \
            spiceypy.vnorm( \
                spiceypy.spkgps(targ=CERES_ID, \
                                et=x, \
                                ref='ECLIPJ2000', \
                                obs=10)[0]), \
            'km', 'AU'))

# # Compute the distance between Ceres and the Earth and convert the resulting
# distance value given in km to AU
ceres_df.loc[:, 'DIST_EARTH_AU'] = \
    ceres_df['ET_TIME'].apply(lambda x: \
        spiceypy.convrt( \
            spiceypy.vnorm( \
                spiceypy.spkgps(targ=CERES_ID, \
                                et=x, \
                                ref='ECLIPJ2000', \
                                obs=399)[0]), \
            'km', 'AU'))
def nh_find_simulated_rings_lorri():

# =============================================================================
# Now go thru the synthetic ring images. 
#    Load and stack the synthetic implanted images.
#    Load and stack the original 'raw' frames
#    Difference them, and see if we can find a ring in there.
# =============================================================================

    dir_porter = '/Users/throop/Dropbox/Data/NH_KEM_Hazard/Porter_Sep17/'
    dir_synthetic = '/Users/throop/Dropbox/Data/NH_KEM_Hazard/synthetic/'
    
    do_subpixel = False  # Flag: Do we use sub-pixel shifting when doing the flattening? 
                         # It is slower and in theory better, but in reality makes a trivial difference.

    # Start up SPICE
    
    file_kernel = 'kernels_kem.tm'
    sp.furnsh(file_kernel)
    
    # Load the images into a table
    
    images_raw = image_stack(dir_porter)
    images_syn = image_stack(dir_synthetic, do_force=False)
    
    stretch = astropy.visualization.PercentileInterval(95)
    plt.set_cmap('Greys_r')

    # =============================================================================
    # If desired, do a one-time routine for the synthetic images:
    #  extract the I/F and ring size from the filenames, and append that to the table.
    # This routine should be run after creating new synthetic images (e.g., adding an I/F value) 
    # =============================================================================
    
    DO_APPEND = False
    if (DO_APPEND):

        t_syn = images_syn.t
        num_images_syn = (np.shape(t_syn))[0]

        iof_ring  = np.zeros(num_images_syn, dtype=float)
        size_ring = np.zeros(num_images_syn, dtype='U30')
    
        for i in range(num_images_syn):
            f = t_syn['filename_short'][i]
            m = re.search('ring_(.*)_iof(.*)_K', f)  # Call regexp to parse it.
            iof_ring[i] = eval(m.group(2))
            size_ring[i] = m.group(1)
            
        t_syn['size_ring'] = size_ring
        t_syn['iof_ring']  = iof_ring
        images_syn.t = t_syn
        images_syn.save()           # Save the whole pickle archive (including images and table) back to disk
    
    data_raw = images_raw.data
    data_syn = images_syn.data
    
    t_raw = images_raw.t
    t_syn = images_syn.t
    
    num_images_raw = (np.shape(t_raw))[0]
    num_images_syn = (np.shape(t_syn))[0]

    # Look up the time offset, from the image title. (Would be better to have it stored in table, but this will do.)

    match = re.search('_K(.*)d', t_syn['filename_short'][0])
    
    dt_ca = ((match.group(1)*u.day).to('s'))  # Weird: we don't need .value here. I can't explain it.

    utc_ca = '2019 1 Jan 05:33'
    et_ca  = sp.utc2et(utc_ca)
    et_obs = et_ca + dt_ca
    
    # Set the pixel scale
    
    vec,lt = sp.spkezr('2014 MU69', et_obs, 'J2000', 'LT', 'New Horizons')
    vec_sc_targ = vec[0:3]
    dist_target_km = (sp.vnorm(vec_sc_targ)*u.km).value    
    scale_pix_lorri_1x1_rad = 0.3*hbt.d2r / 1024
    scale_pix_lorri_4x4_rad = scale_pix_lorri_1x1_rad * 4
    scale_pix_km_dict = {'1X1' : scale_pix_lorri_1x1_rad * dist_target_km,
                         '4X4' : scale_pix_lorri_4x4_rad * dist_target_km}  # We are 
    
    # Create a bunch of possible image sets, based on various parameters
    
    # Indices for 'raw' images
    
    indices_sep17_raw = t_raw['et'] > sp.utc2et('15 sep 2017')  # The positon of MU69 has changed a few pixels.
                                                                # We can't blindly co-add between sep and pre-sep
    indices_jan17_raw = t_raw['et'] < sp.utc2et('1 sep 2017')
                                                        
    indices_rot0_raw  = t_raw['angle'] < 180   # One rotation angle
    indices_rot90_raw = t_raw['angle'] > 180   # The other rotation angle
    indices_10sec_raw = np.logical_and( t_raw['exptime'] < 10, t_raw['exptime'] > 5  )
    indices_20sec_raw = np.logical_and( t_raw['exptime'] < 20, t_raw['exptime'] > 10 )
    
    indices_30sec_raw = np.logical_and( t_raw['exptime'] < 30, t_raw['exptime'] > 20 )
    
    indices_1x1_raw = t_raw['naxis1'] == 1024
    indices_4x4_raw = t_raw['naxis1'] == 256
    
    indices_30sec_4x4_raw = np.logical_and(indices_4x4_raw, indices_30sec_raw) # 94
    
    # Indices for synthetic images
    
    indices_ring_small_syn = t_syn['size_ring'] == 'small'
    indices_ring_large_syn = t_syn['size_ring'] == 'large'
    
    indices_iof_1em7_syn = t_syn['iof_ring'] == 1e-7
    indices_iof_3em7_syn = t_syn['iof_ring'] == 3e-7
    indices_iof_1em6_syn = t_syn['iof_ring'] == 1e-6
    indices_iof_1em5_syn = t_syn['iof_ring'] == 1e-5
    indices_iof_1em4_syn = t_syn['iof_ring'] == 1e-4
    
    indices_small_1em7_syn = np.logical_and(indices_iof_1em7_syn, indices_ring_small_syn)
    indices_small_3em7_syn = np.logical_and(indices_iof_3em7_syn, indices_ring_small_syn)
    indices_small_1em6_syn = np.logical_and(indices_iof_1em6_syn, indices_ring_small_syn)
    indices_small_1em5_syn = np.logical_and(indices_iof_1em5_syn, indices_ring_small_syn)
    indices_small_1em4_syn = np.logical_and(indices_iof_1em4_syn, indices_ring_small_syn)
    indices_large_1em7_syn = np.logical_and(indices_iof_1em7_syn, indices_ring_large_syn)
    indices_large_3em7_syn = np.logical_and(indices_iof_3em7_syn, indices_ring_large_syn)
    indices_large_1em6_syn = np.logical_and(indices_iof_1em6_syn, indices_ring_large_syn)
    indices_large_1em5_syn = np.logical_and(indices_iof_1em5_syn, indices_ring_large_syn)
    indices_large_1em4_syn = np.logical_and(indices_iof_1em4_syn, indices_ring_large_syn)

    # Choose which indiex. ** THIS IS WHERE WE SET THE RING TO USE!!
    
    indices_raw = indices_30sec_4x4_raw.copy()   # 94 of 344
    indices_syn = indices_small_1em6_syn.copy()  # 94 of 752

    # Now take the first half of the synthetic indices, and the second half of the raw ones
    # This is to assure that we are using different images for the two stacks! Otherwise, the results are trivial.
    
    frames_max = int(np.sum(indices_raw) / 2)         # Total number of frames (94)
    
    w = np.where(indices_raw)[0]
    indices_raw[w[frames_max]:] = False          # De-activate all frames *below* frames_max
    
    w = np.where(indices_syn)[0]
    indices_syn[:w[frames_max]] = False          # De-activate all frames above frames_max
    
    # Set the indices
    
    images_raw.set_indices(indices_raw)
    images_syn.set_indices(indices_syn)
    
    # Do the flattening
        
    arr_raw = images_raw.flatten(do_subpixel=do_subpixel)
    arr_syn = images_syn.flatten(do_subpixel=do_subpixel)
    
#    arr_raw_sub = images_raw.flatten(do_subpixel=True)    
#    arr_syn_sub = images_syn.flatten(do_subpixel=True)
    
    # Extract various fields from the data table. We can look up from any of the images -- they should be all the same.
    
    t_syn       = images_syn.t  # Get the data table
    
    iof_ring    = t_syn[indices_syn]['iof_ring'][0]
    size_ring   = t_syn[indices_syn]['size_ring'][0]
    exptime     = t_syn[indices_syn]['exptime'][0]
    
    # The two flattened images need some offsetting. Do that.
    
    shift = ird.translation(arr_raw, arr_syn)['tvec']
#    shift = np.round(shift).astype('int')
    
#    arr_syn_shift = np.roll(np.roll(arr_syn, int(round(shift[0])), axis=0), int(round(shift[1])), axis=1)
    arr_syn_shift = scipy.ndimage.shift(arr_syn, shift, order=5)  # This allows sub-pixel shifts, apparently. *NO*!

#    a = arr_syn.copy()
#    a_05_05 = scipy.ndimage.shift(arr_syn, (0.5, 0.5), order=5)  # Ugh. 0.5, 0.5 and 1, 1 are *exactly* the same.
#    a_1_05 = scipy.ndimage.shift(arr_syn, (1, 0.5), order=5)
#    a_1_1 = scipy.ndimage.shift(arr_syn, (1, 1), order=5)
#    a_1_15 = scipy.ndimage.shift(arr_syn, (1, 1.5), order=5)
#    a_1_0 = scipy.ndimage.shift(arr_syn, (1, 0), order=5)
#    a_05_0 = scipy.ndimage.shift(arr_syn, (0.5, 0), order=5)
    
    arr_diff  = arr_syn_shift  - arr_raw
    
    pos = (images_raw.y_pix_mean*4, images_raw.x_pix_mean*4)
    
    # Set the binning width of the radial profiles

    binning_pix = 5
    
    # Extract the radial profiles
    
    (dist_pix_1d, profile_1d_median) = get_radial_profile_circular(arr_diff, pos, method='median', width=binning_pix)
    (dist_pix_1d, profile_1d_mean)   = get_radial_profile_circular(arr_diff, pos, method='mean', width=binning_pix)

    str_title = ('Synthetic ring - raw, I/F = {:.0e}, {}, {} x {:.1f}s'.format(
            iof_ring, size_ring, frames_max, exptime))
    
    plt.imshow(stretch(arr_diff))
    plt.title(str_title)
    plt.plot(pos[1], pos[0], marker='.', color='red')
    plt.show()
    
    # Set the scale for the effective mode of these observations. Many are taken as 4x4, but we've rebinned to 1x1
    
    if (np.shape(arr_raw)[0] == 1024):
        scale_mode = '1X1'
    else:
        scale_mode = '4X4'
    scale_pix_km = scale_pix_km_dict[scale_mode]
    
    # Make a plot of the radial profile. Don't plot the innermost bin. It is useless, since it has so few pixels in it.
    
    hbt.figsize((12,8))
    
    plt.plot(dist_pix_1d[1:] * scale_pix_km, profile_1d_median[1:], label = 'Annulus median', alpha = 0.7)
#    plt.plot(dist_pix_1d[1:] * scale_pix_km, profile_1d_mean[1:],   label = 'Mean',   alpha = 0.2)
    plt.xlabel('Distance [km]')
    plt.ylabel('DN per pixel')
    plt.title(str_title + ', binning = {}'.format(binning_pix))
    plt.xlim((0,30000))
    
    # Set the y axis range. This is really stupid. Can't matplotlib figure this out itself?
    
    ax = plt.gca()
    lims = ax.get_xlim()
    i = np.where( (dist_pix_1d * scale_pix_km > lims[0]) &  (dist_pix_1d*scale_pix_km < lims[1]) )[0]
    ax.set_ylim( profile_1d_median[i].min(), profile_1d_median[i].max() ) 
    
    plt.legend()
    plt.show()
    plt.savefig()
Ejemplo n.º 27
0
    # Add one hour to the date-time stamp and convert it ot ET
    datetime_stamp = datetime_stamp + datetime.timedelta(hours=1)
    et_stamp = spiceypy.datetime2et(datetime_stamp)

    # Compute the state vector of 67P based on the initial orbital elements
    # (Sun-centric in ECLIPJ2000)
    COMET_67P_STATE_ORB = spiceypy.conics(COMET_67P_ORB_ELEM, et_stamp)

    # Compute Jupiter's state vector in as seen from the Sun
    JUPITER_STATE, _ = spiceypy.spkgeo(targ=5, \
                                       et=et_stamp, \
                                       ref='ECLIPJ2000', \
                                       obs=10)

    # Compute the distance between Jupiter and 67P
    comet_jup_dist = spiceypy.vnorm(JUPITER_STATE[:3] -
                                    COMET_67P_STATE_ORB[:3])

#%%

# If the while condition is not fulfilled, 67P crosses Jupiter's SOI! Let's
# take a look when this happened and also let's verify the distance to
# Jupiter:
print(f'67P entering Jupiter\'s SOI: {datetime_stamp.strftime("%Y-%m-%d")}')
print('67P distance to Jupiter at SOI crossing in AU: ' \
      f'{spiceypy.convrt(comet_jup_dist, inunit="km", outunit="AU")}')

#%%

# Transform the state vector of 67P from a Sun-centric system to a Jupiter-
# centric system ...
COMET_67P_STATE_JUP_CNTR = COMET_67P_STATE_ORB - JUPITER_STATE
Ejemplo n.º 28
0
def get_illuminated_shape(probe: str, body: str, time: datetime,
                          angular_unit: str) -> Polygon:
    """ Calculates the shape of sun-illuminated part of SPICE body as viewed from a probe.

    :param probe: Name of probe, e.g. "JUICE"
    :param body: Name of body, e.g. "CALLISTO"
    :param time: Time of observation
    :param angular_unit: Angular unit, one of ["deg", "rad", "arcMin", "arcSec"]
    :return: Polygon marking the illuminated part of body as viewed from probe, centered on the nadir
             point. The x-direction points towards the Sun.
    """
    if angular_unit not in angular_units:
        raise ValueError(
            f"Unknown angular_unit: '{angular_unit}'. Allowed units: {angular_units}"
        )

    et = datetime2et(time)
    ncuts = 20

    # we need to compute our own coordinate system, where +z is the probe->body vector,
    # the Sun lies in the x-z plane, with +x direction towards the Sun
    # this coordinate system is left-handed (from view of probe, +z points into the screen,
    # +x points right, and +y points up)
    sun_position_from_probe = spy.spkpos("SUN", et, f"IAU_{body}", "LT+S",
                                         probe)[0]
    body_position_from_probe = spy.spkpos(body, et, f"IAU_{body}", "LT+S",
                                          probe)[0]

    # we only need to know the orientations os x-z and y-z planes, we don't care about point of origin
    x_z_plane_normal_vector = spy.vcrss(sun_position_from_probe,
                                        body_position_from_probe)
    y_z_plane_normal_vector = spy.vcrss(body_position_from_probe,
                                        x_z_plane_normal_vector)

    # the illuminated side of limb in our coordinate system is always on the right side, so we start
    # with +y direction (x_z_plane_normal_vector), and rotate clockwise for 180 degrees
    step_limb = -np.pi / ncuts
    limb_points = spy.limbpt("TANGENT/ELLIPSOID", body, et, f"IAU_{body}",
                             "LT+S", "CENTER", probe, x_z_plane_normal_vector,
                             step_limb, ncuts, 1.0, 1.0, ncuts)[3]
    assert (len(limb_points == ncuts))

    # if we preserve the upwards direction of y axis, but look at the body from POV of the Sun,
    # the probe will always be on the left side. That means we start slicing again at +y direction,
    # but this time rotate counter-clockwise for 180 degrees, to get terminator points that are
    # actually visible from probe
    step_terminator = np.pi / ncuts
    terminator_points = spy.termpt("UMBRAL/TANGENT/ELLIPSOID", "SUN", body, et,
                                   f"IAU_{body}", "LT+S", "CENTER", probe,
                                   x_z_plane_normal_vector, step_terminator,
                                   ncuts, 1.0, 1.0, ncuts)[3]
    assert (len(terminator_points <= ncuts))

    # reverse the order of terminator points so we go
    # (limb top -> ... -> limb bottom -> terminator bottom -> ... -> terminator top)
    # these vectors emanate from probe towards the body limb and terminator
    points_3d = list(limb_points) + list(reversed(terminator_points))

    # project the points from IAU body-fixed 3d frame, to our probe POV 2d frame
    points_2d = []
    for p in points_3d:
        # x-coordinate of each point is the angle vector with the y-z plane (this also recognizes the sign)
        x_rad = np.arcsin(
            spy.vdot(y_z_plane_normal_vector, p) /
            (spy.vnorm(y_z_plane_normal_vector) * spy.vnorm(p)))
        # similarly, y-coordinate is angle with x-z plane
        y_rad = np.arcsin(
            spy.vdot(x_z_plane_normal_vector, p) /
            (spy.vnorm(x_z_plane_normal_vector) * spy.vnorm(p)))
        # convert the angular coordinate from radians to desired unit, and add to list
        points_2d.append((convertAngleFromTo(x_rad, "rad", angular_unit),
                          convertAngleFromTo(y_rad, "rad", angular_unit)))
    return Polygon(points_2d)
NEO_1997BQ_ORBITAL_ELEMENTS = [NEO_1997BQ_PERIHELION_KM, \
                               NEO_1997BQ_ECC, \
                               NEO_1997BQ_INC_RAD, \
                               NEO_1997BQ_LNODE_RAD, \
                               NEO_1997BQ_ARGP_RAD, \
                               NEO_1997BQ_M0_AT_T0_RAD, \
                               NEO_1997BQ_T0, \
                               GM_SUN]

# Compute the state vector
NEO_1997BQ_STATE_VECTOR = spiceypy.conics(NEO_1997BQ_ORBITAL_ELEMENTS,
                                          DATETIME_ET)

print(f'Current state vector of 1997BQ in km and km/s ({DATETIME_UTC})):\n' \
      f'{NEO_1997BQ_STATE_VECTOR}')
print('\n')

#%%

# Now compute the state vector of the Earth:
EARTH_STATE_VECTOR, _ = spiceypy.spkgeo(targ=399, \
                                        et=DATETIME_ET, \
                                        ref='ECLIPJ2000',
                                        obs=10)

# Compute the current distance of the Earth and the asteroids in LD
EARTH_1997BQ_DIST_KM = spiceypy.vnorm(EARTH_STATE_VECTOR[:3] \
                                      - NEO_1997BQ_STATE_VECTOR[:3])
print(f'Current distance between the Earth and 1997BQ ({DATETIME_UTC}):\n' \
      f'{EARTH_1997BQ_DIST_KM / ONE_LD} LDe')
Ejemplo n.º 30
0
def test_ck(kernels, frames_to_convert, fks, stop_ets, output_ck):
    """
  Text CK against FKs at times at which the FKs are valid

  """

    ### Load base kernels (LSK, new FK, SCLK)
    for kernel in kernels + [output_ck]:
        sp.furnsh(kernel)

    ### Set last ET to None so it will be initialized in loop's first pass
    last_et = None

    ### Create dict of info; keys will be new FK filenames
    dt = dict()

    while fks:

        ### Pop FK and stop ET off of lists
        fk = fks.pop()
        stop_et = stop_ets.pop()

        ### Create dict for this FK
        dt[fk] = dict()

        ### Loop over refernce frames
        for reffrm in frames_to_convert:

            ### Get reffrm ID, SCLK ID; N.B. latter comes from new FK
            reffrm_id = sp.gipool('FRAME_{}'.format(reffrm.upper()), 0, 1)[0]
            sclk_id = sp.gipool('CK_{}_{}'.format(reffrm_id, 'SCLK'), 0, 1)[0]

            ### Set start DP-SCLK of window for this old FK (outer loop)
            ### - Set to zero for first window
            ### - Covnert ET to DP-SCLK for subsequent windows
            if last_et is None: et_lo = sp.sct2e(sclk_id, 0.)
            else: et_lo = last_et

            ### Load old FK, get RELATIVE frame name, get time-invariant
            ### matrix, and unload old FK
            sp.furnsh(fk)
            relative_reffrm = sp.gcpool(
                'TKFRAME_{}_{}'.format(reffrm_id, 'RELATIVE'), 0, 1, 99)[0]
            sp.unload(fk)

            ### Get ETs at which to do the tests:
            ### - 10s after start of window
            ### - 10s before end of window, or 1e6s after start if last window
            if stop_et < -1e30:
                et_test_lo = et_lo + 10.
                et_test_hi = et_lo + 1e6
            else:
                et_delta = min([10., (stop_et - et_lo) / 3.])
                et_test_lo = et_lo + et_delta
                et_test_hi = stop_et - et_delta

            ### Save the relative reffrm, the reffrm, the window, and an empty
            ### dict for this reffrm under this FK
            dt[fk][reffrm] = (relative_reffrm, et_test_lo, et_test_hi, dict())

        ### For next pass
        last_et = stop_et

    ### Clear all kernels, and test
    sp.kclear()
    assert 0 == sp.ktotal('all')

    ### Load base kernels including new FK and new CK
    for kernel in kernels + [output_ck]:
        sp.furnsh(kernel)

    ### Loop over old FKs, reffrms, and ETs
    for fk in dt:

        for reffrm in dt[fk]:

            ### Retrieve relative reffrm, ETs and quat dict
            relative_reffrm, et_test_lo, et_test_hi, dtquat = dt[fk][reffrm]

            for et in (
                    et_test_lo,
                    et_test_hi,
            ):

                ### Lookup CK-based matrix, convrt to and save quat at each ET
                dtquat[et] = sp.m2q(sp.pxform(relative_reffrm, reffrm, et))

    ### Loop over the old FKs again
    for fk in dt:

        ### Clear all kernels, and test
        sp.kclear()
        assert 0 == sp.ktotal('all')

        ### Load only the old FK
        sp.furnsh(fk)

        ### Loop over reffrms, and ETs
        for reffrm in dt[fk]:

            relative_reffrm, et_test_lo, et_test_hi, dtquat = dt[fk][reffrm]

            for et in (
                    et_test_lo,
                    et_test_hi,
            ):

                ### Calculate norm of difference of CK-based and FK-based quats
                quat_error = round(
                    sp.vnorm(dtquat[et] -
                             sp.m2q(sp.pxform(relative_reffrm, reffrm, et))),
                    16)

                ### Output that norm as an error for each case, which norm
                ### should be zero
                print(
                    dict(fk=fk,
                         quat_error=quat_error,
                         relative_reffrm=relative_reffrm,
                         reffrm=reffrm,
                         et='{:015.4f}'.format(et)))
Ejemplo n.º 31
0
    def __Geometry(self, boresight=''):

        #if self.geometry_flag is True and \
        #                self.time.window.all() == self.previous_tw.all():
        #    return

        distance = []
        altitude = []
        boresight_latitude = []
        boresight_longitude = []
        latitude = []
        longitude = []
        subpoint_xyz = []
        subpoint_pgc = []
        subpoint_pcc = []
        zaxis_target_angle = []
        myaxis_target_angle = []
        yaxis_target_angle = []
        xaxis_target_angle = []
        beta_angle = []

        qs, qx, qy, qz = [], [], [] ,[]
        x, y, z = [],[],[]


        tar = self.target
        time = self.time

        for et in time.window:

            try:
                #
                # Compute the distance
                #
                ptarg, lt = spiceypy.spkpos(tar.name, et, tar.frame, time.abcorr,
                                          self.name)
                x.append(ptarg[0])
                y.append(ptarg[1])
                z.append(ptarg[2])

                vout, vmag = spiceypy.unorm(ptarg)
                distance.append(vmag)


                #
                # Compute the geometric sub-observer point.
                #
                if tar.frame == 'MARSIAU':
                    tar_frame = 'IAU_MARS'
                else:
                    tar_frame = tar.frame
                spoint, trgepc, srfvec = spiceypy.subpnt(tar.method, tar.name, et,
                                                       tar_frame, time.abcorr,
                                                       self.name)
                subpoint_xyz.append(spoint)

                #
                # Compute the observer's altitude from SPOINT.
                #
                dist = spiceypy.vnorm(srfvec)
                altitude.append(dist)


                #
                # Convert the sub-observer point's rectangular coordinates to
                # planetographic longitude, latitude and altitude.
                #
                spglon, spglat, spgalt = spiceypy.recpgr(tar.name, spoint,
                                                       tar.radii_equ, tar.flat)

                #
                # Convert radians to degrees.
                #
                spglon *= spiceypy.dpr()
                spglat *= spiceypy.dpr()

                subpoint_pgc.append([spglon, spglat, spgalt])

                #
                #  Convert sub-observer point's rectangular coordinates to
                #  planetocentric radius, longitude, and latitude.
                #
                spcrad, spclon, spclat = spiceypy.reclat(spoint)


                #
                # Convert radians to degrees.
                #
                spclon *= spiceypy.dpr()
                spclat *= spiceypy.dpr()

                subpoint_pcc.append([spclon, spclat, spcrad])
                latitude.append(spclat) #TODO: Remove with list extraction
                longitude.append(spclon)  # TODO: Remove with list extraction

                #
                # Compute the geometric sub-boresight point.
                #
                if tar.frame == 'MARSIAU':
                    tar_frame = 'IAU_MARS'
                else:
                    tar_frame = tar.frame


                if boresight:
                    try:
                        id = spiceypy.bodn2c(boresight)
                        (shape,framen, bsight, n, bounds) = spiceypy.getfov(id, 80)
                        mat = spiceypy.pxform(framen,tar_frame,et)
                    except:
                        framen = boresight
                        bsight = 0,0,1
                else:
                    bsight = self.name

                try:
                    if tar.method == 'INTERCEPT/ELLIPSOID':
                        method = 'ELLIPSOID'
                    else:
                        method = tar.method
                    spoint, trgepc, srfvec = spiceypy.sincpt(method, tar.name, et,
                                                           tar_frame, time.abcorr,
                                                           self.name, framen,
                                                           bsight)

                    #
                    # Convert the sub-observer point's rectangular coordinates to
                    # planetographic longitude, latitude and altitude.
                    #
                    spglon, spglat, spgalt = spiceypy.recpgr(tar.name, spoint,
                                                           tar.radii_equ, tar.flat)

                    #
                    # Convert radians to degrees.
                    #
                    spglon *= spiceypy.dpr()
                    spglat *= spiceypy.dpr()


                    #
                    #  Convert sub-observer point's rectangular coordinates to
                    #  planetocentric radius, longitude, and latitude.
                    #
                    spcrad, spclon, spclat = spiceypy.reclat(spoint)


                    #
                    # Convert radians to degrees.
                    #
                    spclon *= spiceypy.dpr()
                    spclat *= spiceypy.dpr()

                    boresight_latitude.append(spclat)
                    boresight_longitude.append(spclon)

                except:
                    pass

                #
                # Compute the angle between the observer's S/C axis and the
                # geometric sub-observer point
                #
                obs_tar, ltime = spiceypy.spkpos(tar.name, et,
                                                       'J2000', time.abcorr,
                                                       self.name)
                obs_zaxis  = [0,  0, 1]
                obs_myaxis = [0, -1, 0]
                obs_yaxis = [0, 1, 0]
                obs_xaxis = [1, 0, 0]

                #
                # We need to account for when there is no CK attitude available.
                #
                try:
                    matrix = spiceypy.pxform(self.frame, 'J2000', et)

                    z_vecout = spiceypy.mxv(matrix, obs_zaxis)
                    zax_target_angle = spiceypy.vsep(z_vecout, obs_tar)
                    zax_target_angle *= spiceypy.dpr()
                    zaxis_target_angle.append(zax_target_angle)

                    my_vecout = spiceypy.mxv(matrix, obs_myaxis)
                    myax_target_angle = spiceypy.vsep(my_vecout, obs_tar)
                    myax_target_angle *= spiceypy.dpr()
                    myaxis_target_angle.append(myax_target_angle)

                    y_vecout = spiceypy.mxv(matrix, obs_myaxis)
                    yax_target_angle = spiceypy.vsep(y_vecout, obs_tar)
                    yax_target_angle *= spiceypy.dpr()
                    yaxis_target_angle.append(yax_target_angle)

                    x_vecout = spiceypy.mxv(matrix, obs_myaxis)
                    xax_target_angle = spiceypy.vsep(x_vecout, obs_tar)
                    xax_target_angle *= spiceypy.dpr()
                    xaxis_target_angle.append(xax_target_angle)


                    quat = spiceypy.m2q(spiceypy.invert(matrix))
                    qs.append(quat[0])
                    qx.append(-1*quat[1])
                    qy.append(-1*quat[2])
                    qz.append(-1*quat[3])

                except:
                    zaxis_target_angle.append(0.0)
                    myaxis_target_angle.append(0.0)
                    yaxis_target_angle.append(0.0)
                    xaxis_target_angle.append(0.0)
                    qs.append(0.0)
                    qx.append(0.0)
                    qy.append(0.0)
                    qz.append(0.0)

                beta_angle.append(spiops.beta_angle(self.name, self.target.name,
                                                    et))
            except:
                boresight_latitude = 0
                boresight_longitude = 0
                distance = 0
                altitude = 0
                latitude = 0
                longitude = 0
                subpoint_xyz = [0,0,0]
                subpoint_pgc =  [0,0,0]
                subpoint_pcc =  [0,0,0]
                zaxis_target_angle = 0
                myaxis_target_angle = 0
                yaxis_target_angle = 0
                xaxis_target_angle = 0
                beta_angle = 0
                (qx, qy, qz, qs) = 0, 0, 0, 0
                (x, y, z) = 0, 0, 0

        self.boresight_latitude = boresight_latitude
        self.boresight_longitude = boresight_longitude
        self.distance = distance
        self.altitude = altitude
        self.latitude = latitude
        self.longitude = longitude
        self.subpoint_xyz = subpoint_xyz
        self.subpoint_pgc = subpoint_pgc
        self.subpoint_pcc = subpoint_pcc
        self.zaxis_target_angle = zaxis_target_angle
        self.myaxis_target_angle = myaxis_target_angle
        self.yaxis_target_angle = yaxis_target_angle
        self.xaxis_target_angle = xaxis_target_angle
        self.beta_angle = beta_angle
        self.quaternions = [qx, qy, qz, qs]
        self.trajectory = [x,y,z]

        self.geometry_flag = True
        self.previous_tw = self.time.window

        return
#==============================================================================

n_exp = n_footprints * exp_per_footprint # Total number of exposures

index_exp = hbt.frange(0,n_exp-1)        
index_footprint = np.trunc(index_exp/3).astype('int')

# Define 'et' as an array, one per exposure, of the time that exposure is taken

et = et_start + (index_exp * exptime) + (index_footprint * dt_slew)

dist_kbo = np.zeros(n_exp)

for i,et_i in enumerate(et):
    (st, junk) = sp.spkezr(name_target, et_i, 'J2000', 'LT+S', name_observer)
    dist_kbo[i] = sp.vnorm(st[0:2])

dist_kbo = dist_kbo * u.km

width_fov_km = (dist_kbo * width_fov_rad).to('km').value    # Width, in km
height_fov_km = (dist_kbo * height_fov_rad).to('km').value  # Height, in km
      
#==============================================================================
# Make the plot
#==============================================================================
#%%
fig, ax = plt.subplots()

hbt.set_fontsize(20)
hbt.figsize((10,10))
lim_image_km = (-radius_image.to('km').value, radius_image.to('km').value)
Ejemplo n.º 33
0
 def _get_solar_constant(self):
     dist = spice.vnorm(self.center_to_sun)
     # SPICE returns in [km] !!
     return L_sol / (4 * np.pi * (dist * 1e3) ** 2)
Ejemplo n.º 34
0
METAKERNEL_NAME = r"em16_ops_win.tm"
os.chdir(KERNEL_DIRECTORY)
sp.furnsh(KERNEL_DIRECTORY + os.sep + METAKERNEL_NAME)
print(sp.tkvrsn("toolkit"))
os.chdir(BASE_DIRECTORY)

origin = np.asfarray([0.0, 0.0, 0.0])

#vectorOffsetX = 3.0**(-1/2)
#vectorOffsetY = 3.0**(-1/2)
vectorOffsetX = np.arcsin(1.9992 / sp.dpr())  #2 degrees
vectorOffsetY = np.arcsin(15.0 * 88 / 90 / sp.dpr())  #15
vectorOffsetZ = np.sqrt(1.0 - vectorOffsetX**2 - vectorOffsetY**2)

vector = np.asfarray([vectorOffsetX, vectorOffsetY, vectorOffsetZ])
vectorMagnitude = sp.vnorm(vector)  #should be 1

vector2 = np.asfarray([0.0, 0.0, 1.0])

angleSeparation = sp.vsep(vector / vectorMagnitude, vector2) * sp.dpr()

#X to Z
#vectorA = np.asfarray([vector[0], 0.0, vector[2]])/sp.vnorm([vector[0], 0.0, vector[2]])
vectorA = np.asfarray([vector[0], 0.0, np.sqrt(1.0 - vector[0]**2)])
angleSeparationA = sp.vsep(vectorA / sp.vnorm(vectorA), vector2) * sp.dpr()

#Y to Z
#vectorB = np.asfarray([0.0, vector[1], vector[2]])/sp.vnorm([0.0, vector[1], vector[2]])
vectorB = np.asfarray([0.0, vector[1], np.sqrt(1.0 - vector[1]**2)])
angleSeparationB = sp.vsep(vectorB / sp.vnorm(vectorB), vector2) * sp.dpr()
Ejemplo n.º 35
0
 def solar_constant(self):
     "float : With global value L_s, solar constant at coordinates of body center."
     dist = spice.vnorm(self.center_to_sun.value) * u.km
     return (L_sun / (2 * tau * (dist)**2)).to(u.W / u.m / u.m)
Ejemplo n.º 36
0
def compute_backplanes(file, name_target, frame, name_observer, angle1=0, angle2=0, angle3=0):
    
    """
    Returns a set of backplanes for a single specified image. The image must have WCS coords available in its header.
    Backplanes include navigation info for every pixel, including RA, Dec, Eq Lon, Phase, etc.
    
    The results are returned to memory, and not written to a file.
    
    SPICE kernels must be alreaded loaded, and spiceypy running.
    
    Parameters
    ----
    
    file:
        String. Input filename, for FITS file.
    frame:
        String. Reference frame of the target body. 'IAU_JUPITER', 'IAU_MU69', '2014_MU69_SUNFLOWER_ROT', etc.
        This is the frame that the Radius_eq and Longitude_eq are computed in.
    name_target:
        String. Name of the central body. All geometry is referenced relative to this (e.g., radius, azimuth, etc)
    name_observer:
        String. Name of the observer. Must be a SPICE body name (e.g., 'New Horizons')
    
    Optional Parameters
    ----
    
    angle{1,2,3}:
        **NOT REALLY TESTED. THE BETTER WAY TO CHANGE THE ROTATION IS TO USE A DIFFERENT FRAME.**

        Rotation angles which are applied when defining the plane in space that the backplane will be generated for.
        These are applied in the order 1, 2, 3. Angles are in radians. Nominal values are 0.
       
        This allows the simulation of (e.g.) a ring system inclined relative to the nominal body equatorial plane.
        
        For MU69 sunflower rings, the following descriptions are roughly accurate, becuase the +Y axis points
        sunward, which is *almost* toward the observer. But it is better to experiment and find the 
        appropriate angle that way, than rely on this ad hoc description. These are close for starting with.
 
                      - `angle1` = Tilt front-back, from face-on. Or rotation angle, if tilted right-left.
                      - `angle2` = Rotation angle, if tilted front-back. 
                      - `angle3` = Tilt right-left, from face-on.
    
    do_fast:
        Boolean. If set, generate only an abbreviated set of backplanes. **NOT CURRENTLY IMPLEMENTED.**
        
    Output
    ----

    Output is a tuple, consisting of each of the backplanes, and a text description for each one. 
    The size of each of these arrays is the same as the input image.
    
    The position of each of these is the plane defined by the target body, and the normal vector to the observer.
    
    output = (backplanes, descs)
    
        backplanes = (ra,      dec,      radius_eq,      longitude_eq,      phase)
        descs      = (desc_ra, desc_dec, desc_radius_eq, desc_longitude_eq, desc_phase)
    
    Radius_eq:
        Radius, in the ring's equatorial plane, in km
    Longitude_eq:
        Longitude, in the equatorial plane, in radians (0 .. 2pi)
    RA:
        RA of pixel, in radians
    Dec:
        Dec of pixel, in
    dRA:
        Projected offset in RA  direction between center of body (or barycenter) and pixel, in km.
    dDec:
        Projected offset in Dec direction between center of body (or barycenter) and pixel, in km.
        
    Z:  
        Vertical value of the ring system.
        
    With special options selected (TBD), then additional backplanes will be generated -- e.g., a set of planes
    for each of the Jovian satellites in the image, or sunflower orbit, etc.
    
    No new FITS file is written. The only output is the returned tuple.
    
    """

    if not(frame):
        raise ValueError('frame undefined')
    
    if not(name_target):
        raise ValueError('name_target undefined')
        
    if not(name_observer):
        raise ValueError('name_observer undefined')
        
    name_body = name_target  # Sometimes we use one, sometimes the other. Both are identical
    
    fov_lorri = 0.3 * hbt.d2r
   
    abcorr = 'LT'

    do_satellites = False  # Flag: Do we create an additional backplane for each of Jupiter's small sats?
    
    # Open the FITS file
    
    w       = WCS(file) # Warning: I have gotten a segfault here before if passing a FITS file with no WCS info.    
    hdulist = fits.open(file)
    
    et      = float(hdulist[0].header['SPCSCET']) # ET of mid-exposure, on s/c
    n_dx    = int(hdulist[0].header['NAXIS1']) # Pixel dimensions of image. Both LORRI and MVIC have this.
    n_dy    = int(hdulist[0].header['NAXIS2'])
    
    hdulist.close()

    # Setup the output arrays
    
    lon_arr    = np.zeros((n_dy, n_dx))     # Longitude of pixel (defined with recpgr)
    lat_arr    = np.zeros((n_dy, n_dx))     # Latitude of pixel (which is zero, so meaningless)
    radius_arr = np.zeros((n_dy, n_dx))     # Radius, in km
    altitude_arr= np.zeros((n_dy, n_dx))    # Altitude above midplane, in km
    ra_arr     = np.zeros((n_dy, n_dx))     # RA of pixel
    dec_arr    = np.zeros((n_dy, n_dx))     # Dec of pixel
    dra_arr    = np.zeros((n_dy, n_dx))     # dRA  of pixel: Distance in sky plane between pixel and body, in km. 
    ddec_arr   = np.zeros((n_dy, n_dx))     # dDec of pixel: Distance in sky plane between pixel and body, in km.
    phase_arr  = np.zeros((n_dy, n_dx))     # Phase angle    
    x_arr      = np.zeros((n_dy, n_dx))     # Intersection of sky plane: X pos in bdoy coords
    y_arr      = np.zeros((n_dy, n_dx))     # Intersection of sky plane: X pos in bdoy coords
    z_arr      = np.zeros((n_dy, n_dx))     # Intersection of sky plane: X pos in bdoy coords
    
# =============================================================================
#  Do the backplane, in the general case.
#  This is a long routine, because we deal with all the satellites, the J-ring, etc.        
# =============================================================================
    
    if (True):
        
        # Look up body parameters, used for PGRREC().
    
        (num, radii) = sp.bodvrd(name_target, 'RADII', 3)
        
        r_e = radii[0]
        r_p = radii[2]
        flat = (r_e - r_p) / r_e

        # Define a SPICE 'plane' along the plane of the ring.
        # Do this in coordinate frame of the body (IAU_JUPITER, 2014_MU69_SUNFLOWER_ROT, etc).
        
# =============================================================================
# Set up the Jupiter system specifics
# =============================================================================
            
        if (name_target.upper() == 'JUPITER'):
            plane_target_eq = sp.nvp2pl([0,0,1], [0,0,0])    # nvp2pl: Normal Vec + Point to Plane. Jupiter north pole?

            # For Jupiter only, define a few more output arrays for the final backplane set

            ang_metis_arr    = np.zeros((n_dy, n_dx))   # Angle from pixel to body, in radians
            ang_adrastea_arr = ang_metis_arr.copy()
            ang_thebe_arr    = ang_metis_arr.copy()
            ang_amalthea_arr = ang_metis_arr.copy()

# =============================================================================
# Set up the MU69 specifics
# =============================================================================
        
        if ('MU69' in name_target.upper()):
            
        # Define a plane, which is the plane of sunflower rings (ie, X-Z plane in Sunflower frame)
        # If additional angles are passed, then create an Euler matrix which will do additional angles of rotation.
        # This is defined in the 'MU69_SUNFLOWER' frame

            vec_plane = [0, 1, 0]                                 # Use +Y (anti-sun dir), which is normal to XZ.
#            vec_plane = [0, 0, 1]                                 # Use +Y (anti-sun dir), which is normal to XZ.
            plane_target_eq = sp.nvp2pl(vec_plane, [0,0,0]) # "Normal Vec + Point to Plane". 0,0,0 = origin.

           # XXX NB: This plane in in body coords, not J2K coords. This is what we want, because the 
           # target intercept calculation is also done in body coords.
        
# =============================================================================
# Set up the various output planes and arrays necessary for computation
# =============================================================================

        # Get xformation matrix from J2K to target system coords. I can use this for points *or* vectors.
                
        mx_j2k_frame = sp.pxform('J2000', frame, et) # from, to, et
        
        # Get vec from body to s/c, in both body frame, and J2K.
        # NB: The suffix _j2k indicates j2K frame. _frame indicates the frame of target (IAU_JUP, MU69_SUNFLOWER, etc)
               
        (st_target_sc_frame, lt) = sp.spkezr(name_observer, et, frame,   abcorr, name_target)
        (st_sc_target_frame, lt) = sp.spkezr(name_target,   et, frame,   abcorr, name_observer)
        (st_target_sc_j2k, lt)   = sp.spkezr(name_observer, et, 'J2000', abcorr, name_target)     
        (st_sc_target_j2k, lt)   = sp.spkezr(name_target,   et, 'J2000', abcorr, name_observer)
        
        vec_target_sc_frame = st_target_sc_frame[0:3]
        vec_sc_target_frame = st_sc_target_frame[0:3]
        vec_target_sc_j2k   = st_target_sc_j2k[0:3]
        vec_sc_target_j2k   = st_sc_target_j2k[0:3]
        
        dist_target_sc      = sp.vnorm(vec_target_sc_j2k)   # Get target distance, in km
        
        # vec_sc_target_frame = -vec_target_sc_frame # ACTUALLY THIS IS NOT TRUE!! ONLY TRUE IF ABCORR=NONE.
        
        # Name this vector a 'point'. INRYPL requires a point argument.
            
        pt_target_sc_frame = vec_target_sc_frame
    
        # Look up RA and Dec of target (from sc), in J2K 
        
        (_, ra_sc_target, dec_sc_target) = sp.recrad(vec_sc_target_j2k)

        # Get vector from target to sun. We use this later for phase angle.
        
        (st_target_sun_frame, lt) = sp.spkezr('Sun', et, frame, abcorr, name_target) # From body to Sun, in body frame
        vec_target_sun_frame = st_target_sun_frame[0:3]
        
        # Create a 2D array of RA and Dec points
        # These are made by WCS, so they are guaranteed to be right.
        
        xs = range(n_dx)
        ys = range(n_dy)
        (i_x_2d, i_y_2d) = np.meshgrid(xs, ys)
        (ra_arr, dec_arr) = w.wcs_pix2world(i_x_2d, i_y_2d, False) # Returns in degrees
        ra_arr  *= hbt.d2r                                        # Convert to radians
        dec_arr *= hbt.d2r
        
        # Compute the projected distance from MU69, in the sky plane, in km, for each pixel.
        # dist_target_sc is the distance to MU69, and we use this to convert from radians, to km.
        # 16-Oct-2018. I had been computing this erroneously. It should be *cosdec, not /cosdec.
        
        dra_arr  = (ra_arr   - ra_sc_target) * dist_target_sc * np.cos(dec_arr)
        ddec_arr = (dec_arr - dec_sc_target) * dist_target_sc  # Convert to km
    
# =============================================================================
#  Compute position for additional Jupiter bodies, as needed
# =============================================================================
    
        if (name_target.upper() == 'JUPITER'):
            vec_metis_j2k,lt     = sp.spkezr('Metis',    et, 'J2000', abcorr, 'New Horizons')
            vec_adrastea_j2k,lt  = sp.spkezr('Adrastea', et, 'J2000', abcorr, 'New Horizons')
            vec_thebe_j2k,lt     = sp.spkezr('Thebe',    et, 'J2000', abcorr, 'New Horizons')
            vec_amalthea_j2k,lt  = sp.spkezr('Amalthea', et, 'J2000', abcorr, 'New Horizons')
            
            vec_metis_j2k        = np.array(vec_metis_j2k[0:3])
            vec_thebe_j2k        = np.array(vec_thebe_j2k[0:3])
            vec_adrastea_j2k     = np.array(vec_adrastea_j2k[0:3])
            vec_amalthea_j2k     = np.array(vec_amalthea_j2k[0:3])
        
# =============================================================================
# Loop over pixels in the output image
# =============================================================================
        
        for i_x in xs:
            for i_y in ys:
        
                # Look up the vector direction of this single pixel, which is defined by an RA and Dec
                # Vector is thru pixel to ring, in J2K. 
                # RA and Dec grids are made by WCS, so they are guaranteed to be right.
        
                vec_pix_j2k =  sp.radrec(1., ra_arr[i_y, i_x], dec_arr[i_y, i_x]) 
                
                # Convert vector along the pixel direction, from J2K into the target body frame
          
                vec_pix_frame = sp.mxv(mx_j2k_frame, vec_pix_j2k)
        
                # And calculate the intercept point between this vector, and the ring plane.
                # All these are in body coordinates.
                # plane_target_eq is defined as the body's equatorial plane (its XZ for MU69).
                
                # ** Some question as to whether we should shoot this vector at the ring plane, or the sky plane.
                # Ring plane is normally the one we want. But, for the case of edge-on rings, the eq's break down.
                # So, we should use the sky plane instead. Testing shows that for the case of MU69 Eq's break down 
                # for edge-on rings... there is always an ambiguity.

                # ** For testing, try intersecting the sky plane instead of the ring plane.
                # ** Confirmed: Using sky plane gives identical results in case of face-on rings.
                #    And it gives meaningful results in case of edge-on rings, where ring plane did not.
                #    However, for normal rings (e.g., Jupiter), we should continue using the ring plane, not sky plane.
                
                do_sky_plane = True  # For ORT4, where we want to use euler angles, need to set this to False
                
                if do_sky_plane and ('MU69' in name_target):
                    plane_sky_frame = sp.nvp2pl(vec_sc_target_frame, [0,0,0])  # Frame normal to s/c vec, cntrd on MU69
                    (npts, pt_intersect_frame) = sp.inrypl(pt_target_sc_frame, vec_pix_frame, plane_sky_frame) 
                    
                    # pt_intersect_frame is the point where the ray hits the skyplane, in the coordinate frame
                    # of the target body.
    
                else:                         # Calc intersect into equator of target plane (ie, ring plane)
                    (npts, pt_intersect_frame) = sp.inrypl(pt_target_sc_frame, vec_pix_frame, plane_target_eq) 
                                                                                             # pt, vec, plane
                    
                # Swap axes in target frame if needed.                
                # In the case of MU69 (both sunflower and tunacan), the frame is defined s.t. the ring 
                # is in the XZ plane, not XY. This is strange (but correct).
                # I bet MU69 is the only ring like this. Swap it so that Z means 'vertical, out of plane' -- 
                # that is, put it into normal XYZ rectangular coords, so we can use RECLAT etc on it.
                
                if ('MU69' in name_target):  # Was 0 2 1. But this makes tunacan radius look in wrong dir.
                                             # 201 looks same
                                             # 210 similar
                                             # 201 similar
                                             # 102, 120 similar.
                                             # ** None of these change orientation of 'radius' backplane. OK.
                                             
                    pt_intersect_frame = np.array([pt_intersect_frame[0], pt_intersect_frame[2], pt_intersect_frame[1]])
                
                # Get the radius and azimuth of the intersect, in the ring plane
                # Q: Why for the TUNACAN is the radius zero here along horizontal (see plot)?
                # A: Ahh, it is not zero. It is just that the 'projected radius' of a ring that is nearly edge-on
                # can be huge! Basically, if we try to calc the intersection with that plane, it will give screwy
                # answers, because the plane is so close to edge-on that intersection could be a long way 
                # from body itself.
                
                # Instead, I really want to take the tangent sky plane, intersect that, and then calc the 
                # position of that (in xyz, radius, longitude, etc).
                # Since that plane is fixed, I don't see a disadvantage to doing that.
                
                # We want the 'radius' to be the radius in the equatorial plane -- that is, sqrt(x^2 + y^2).
                # We don't want it to be the 'SPICE radius', which is the distance.
                # (For MU69 equatorial plane is nominally XZ, but we have already changed that above to XY.)
                
                _radius_3d, lon, lat = sp.reclat(pt_intersect_frame)
                
                radius_eq = sp.vnorm([pt_intersect_frame[0], pt_intersect_frame[1], 0])  
#                radius_eq = sp.vnorm([pt_intersect_frame[0], pt_intersect_frame[1], pt_intersect_frame[2]])
                
                # Get the vertical position (altitude)
                
                altitude = pt_intersect_frame[2]

                # Calculate the phase angle: angle between s/c-to-ring, and ring-to-sun
        
                vec_ring_sun_frame = -pt_intersect_frame + vec_target_sun_frame
                
                angle_phase = sp.vsep(-vec_pix_frame, vec_ring_sun_frame)
                
                # Save various derived quantities
                         
                radius_arr[i_y, i_x] = radius_eq
                lon_arr[i_y, i_x]    = lon
                phase_arr[i_y, i_x]  = angle_phase
                altitude_arr[i_y, i_x] = altitude
                
                # Save these just for debugging
                
                x_arr[i_y, i_x] = pt_intersect_frame[0]
                y_arr[i_y, i_x] = pt_intersect_frame[1]
                z_arr[i_y, i_x] = pt_intersect_frame[2]
                
                # Now calc angular separation between this pixel, and the satellites in our list
                # Since these are huge arrays, cast into floats to make sure they are not doubles.
                
                if (name_body.upper() == 'JUPITER'):
                    ang_thebe_arr[i_y, i_x]    = sp.vsep(vec_pix_j2k, vec_thebe_j2k)
                    ang_adrastea_arr[i_y, i_x] = sp.vsep(vec_pix_j2k, vec_adrastea_j2k)
                    ang_metis_arr[i_y, i_x]    = sp.vsep(vec_pix_j2k, vec_metis_j2k)
                    ang_amalthea_arr[i_y, i_x] = sp.vsep(vec_pix_j2k, vec_amalthea_j2k) 

        # Now, fix a bug. The issue is that SP.INRYPL uses the actual location of the bodies (no aberration),
        # while their position is calculated (as it should be) with abcorr=LT. This causes a small error in the 
        # positions based on the INRYPL calculation. This should probably be fixed above, but it was not 
        # obvious how. So, instead, I am fixing it here, by doing a small manual offset.
        
        # Calculate the shift required, by measuring the position of MU69 with abcorr=NONE, and comparing it to 
        # the existing calculation, that uses abcorr=LT. This is brute force, but it works. For MU69 approach, 
        # it is 0.75 LORRI 4x4 pixels (ie, 3 1X1 pixels). This is bafflingly huge (I mean, we are headed
        # straight toward MU69, and it takes a month to move a pixel, and RTLT is only a few minutes). But I have
        # confirmed the math and the magnitude, and it works.
        
        (st_sc_target_j2k_nolt, _)                 = sp.spkezr(name_target,   et, 'J2000', 'NONE', name_observer)
        vec_sc_target_j2k_nolt                     = st_sc_target_j2k_nolt[0:3]
        (_, ra_sc_target_nolt, dec_sc_target_nolt) = sp.recrad(vec_sc_target_j2k_nolt)

        (x0,y0) = w.wcs_world2pix(ra_sc_target_nolt*hbt.r2d, dec_sc_target_nolt*hbt.r2d, 1)
        (x1,y1) = w.wcs_world2pix(ra_sc_target     *hbt.r2d, dec_sc_target     *hbt.r2d, 1)
        dx = x1-x0
        dy = y1-y0
        
        print(f'Compute backplanes: INRYPL pixel shift = {dx}, {dy}')

        dx_int = int(round(dx))
        dy_int = int(round(dy))
        
        do_roll = True
        
        if do_roll:
            print(f'compute_backplanes: Rolling by {dx_int}, {dy_int} due to INRYPL')
             
            # Now shift all of the planes that need fixing. The dRA_km and dDec_km are calculated before INRYPL()
            # is applied, so they do not need to be shifted. I have validated that by plotting them.
            # 
            # XXX NP.ROLL() is really not ideal. I should use a function that introduces NaN at the edge, not roll it.
            
            radius_arr   = np.roll(np.roll(radius_arr,   dy_int, axis=0), dx_int, axis=1)
            lon_arr      = np.roll(np.roll(lon_arr,      dy_int, axis=0), dx_int, axis=1)
            phase_arr    = np.roll(np.roll(phase_arr,    dy_int, axis=0), dx_int, axis=1)
            altitude_arr = np.roll(np.roll(altitude_arr, dy_int, axis=0), dx_int, axis=1)
        else:
            print(f'compute_backplanes: Skipping roll due to INRYPL, based on do_roll={do_roll}')
            
        # Assemble the results into a backplane
    
        backplane = {
             'RA'           : ra_arr.astype(float),  # return radians
             'Dec'          : dec_arr.astype(float), # return radians 
             'dRA_km'       : dra_arr.astype(float),
             'dDec_km'      : ddec_arr.astype(float),
             'Radius_eq'    : radius_arr.astype(float),
             'Longitude_eq' : lon_arr.astype(float), 
             'Phase'        : phase_arr.astype(float),
             'Altitude_eq'  : altitude_arr.astype(float),
#             'x'            : x_arr.astype(float),
#             'y'            : y_arr.astype(float),
#             'z'            : z_arr.astype(float),
#             
             }
        
        # Assemble a bunch of descriptors, to be put into the FITS headers
        
        desc = {
                'RA of pixel, radians',
                'Dec of pixel, radians',
                'Offset from target in target plane, RA direction, km',
                'Offset from target in target plane, Dec direction, km',
                'Projected equatorial radius, km',
                'Projected equatorial longitude, km',
                'Sun-target-observer phase angle, radians',
                'Altitude above midplane, km',    
#                'X position of sky plane intercept',
#                'Y position of sky plane intercept',
#                'Z position of sky plane intercept'
                }
                
        # In the case of Jupiter, add a few extra fields
        
        if (name_body.upper() == 'JUPITER'):
            backplane['Ang_Thebe']    = ang_thebe_arr.astype(float)   # Angle to Thebe, in radians
            backplane['Ang_Metis']    = ang_metis_arr.astype(float)
            backplane['Ang_Amalthea'] = ang_amalthea_arr.astype(float)
            backplane['Ang_Adrastea'] = ang_adrastea_arr.astype(float)
    
        # If distance to any of the small sats is < 0.3 deg, then delete that entry in the dictionary
        
            if (np.amin(ang_thebe_arr) > fov_lorri):
                del backplane['Ang_Thebe']
            else:
                print("Keeping Thebe".format(np.min(ang_thebe_arr) * hbt.r2d))
        
            if (np.amin(ang_metis_arr) > fov_lorri):
                del backplane['Ang_Metis']
            else:
                print("Keeping Metis, min = {} deg".format(np.min(ang_metis_arr) * hbt.r2d))
                
            if (np.amin(ang_amalthea_arr) > fov_lorri):
                del backplane['Ang_Amalthea']
            else:
                print("Keeping Amalthea, min = {} deg".format(np.amin(ang_amalthea_arr) * hbt.r2d))
        
            if (np.amin(ang_adrastea_arr) > fov_lorri):
                del backplane['Ang_Adrastea']
            else:
                print("Keeping Adrastea".format(np.min(ang_adrastea_arr) * hbt.r2d))
        
    # And return the backplane set
                 
    return (backplane, desc)
Ejemplo n.º 37
0
def get_vector(date_time, reference_frame):
    # print("SUN", date_time, reference_frame, SPICE_ABERRATION_CORRECTION, SPICE_OBSERVER)
    obs2SunVector = sp.spkpos("SUN", date_time, reference_frame, SPICE_ABERRATION_CORRECTION, SPICE_OBSERVER)[0]
    obs2SunUnitVector = obs2SunVector / sp.vnorm(obs2SunVector)
    return -1 * obs2SunUnitVector #-1 is there to switch the directions to be like in cosmographia
Ejemplo n.º 38
0
def geometry(et, bsight, target, frame, sensor, observer=''):

    if not observer:
        observer = sensor

    # Time tag [UTC]
    # pixel id [(x,y)]
    # corner id [(x,y)]

    # Requested geometry

    # lat lon intersection (planetocentric)
    # lat lon subspacecraft
    # lat lon subsolar
    # target distance intersection
    # target angular diameter
    # local solar time intersection
    # phase angle intersection
    # emission angle intersection
    # incidence angle intersection

    #
    # We retrieve the camera information using GETFOV. More info available:
    #
    #   https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/getfov_c.html
    #
    sensor_id = spiceypy.bodn2c(sensor)
    (shape, sensor_frame, ibsight, vectors,
     bounds) = spiceypy.getfov(sensor_id, 100)

    visible = spiceypy.fovtrg(sensor, target, 'ELLIPSOID', frame, 'LT+S',
                              observer, et)

    if not visible:
        return 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

    tarid = spiceypy.bodn2c(target)

    n, radii = spiceypy.bodvrd(target, 'RADII', 3)
    re = radii[0]
    rp = radii[2]
    f = (re - rp) / re

    try:
        #
        # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/sincpt_c.html
        #
        # For each pixel we compute the possible intersection with the target, if
        # the target is intersected we then compute the illumination angles. We
        # use the following SPICE APIs: SINCPT and ILLUMF
        #
        #   https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/sincpt_c.html
        #   https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/illumf_c.html
        #
        (spoint, trgepc, srfvec) = \
            spiceypy.sincpt('ELLIPSOID', target, et, frame, 'LT+S', observer, sensor_frame, bsight)

        (tarlon, tarlat, taralt) = spiceypy.recgeo(spoint, re, f)
        tardis = spiceypy.vnorm(srfvec)

        #
        # Angular diameter
        #
        tarang = np.degrees(
            2 * np.arctan(max(radii) / spiceypy.vnorm(spoint + srfvec)))

        #
        # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/illumf_c.html
        #
        (trgenpc, srfvec, phase, incdnc, emissn, visiblef, iluminatedf) = \
             spiceypy.illumf('ELLIPSOID', target, 'SUN', et, frame, 'LT+S', observer, spoint)

        phase *= spiceypy.dpr()
        incdnc *= spiceypy.dpr()
        emissn *= spiceypy.dpr()

        #
        # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/et2lst_c.html
        #
        #    VARIABLE  I/O  DESCRIPTION
        #    --------  ---  --------------------------------------------------
        #    et         I   Epoch in seconds past J2000 epoch.
        #    body       I   ID-code of the body of interest.
        #    lon        I   Longitude of surface point (RADIANS).
        #    type       I   Type of longitude "PLANETOCENTRIC", etc.
        #    timlen     I   Available room in output time string.
        #    ampmlen    I   Available room in output `ampm' string.
        #    hr         O   Local hour on a "24 hour" clock.
        #    mn         O   Minutes past the hour.
        #    sc         O   Seconds past the minute.
        #    time       O   String giving local time on 24 hour clock.
        #    ampm       O   String giving time on A.M./ P.M. scale.
        (hr, mn, sc, ltime, ampm) = \
            spiceypy.et2lst(et, tarid, tarlon, 'PLANETOCENTRIC', 80, 80)

        #
        # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/subpnt_c.html
        #
        #    Variable  I/O  Description
        #    --------  ---  --------------------------------------------------
        #    method     I   Computation method.
        #    target     I   Name of target body.
        #    et         I   Epoch in TDB seconds past J2000 TDB.
        #    fixref     I   Body-fixed, body-centered target body frame.
        #    abcorr     I   Aberration correction flag.
        #    obsrvr     I   Name of observing body.
        #    spoint     O   Sub-observer point on the target body.
        #    trgepc     O   Sub-observer point epoch.
        #    srfvec     O   Vector from observer to sub-observer point
        #
        (spoint, trgepc, srfev) = \
            spiceypy.subpnt('INTERCEPT/ELLIPSOID', target, et, frame, 'LT+S', observer)

        (sublon, sublat, subalt) = spiceypy.recgeo(spoint, re, f)

        #
        # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/subslr_c.html
        #
        #    Variable  I/O  Description
        #    --------  ---  --------------------------------------------------
        #    method     I   Computation method.
        #    target     I   Name of target body.
        #    et         I   Epoch in ephemeris seconds past J2000 TDB.
        #    fixref     I   Body-fixed, body-centered target body frame.
        #    abcorr     I   Aberration correction.
        #    obsrvr     I   Name of observing body.
        #    spoint     O   Sub-solar point on the target body.
        #    trgepc     O   Sub-solar point epoch.
        #    srfvec     O   Vector from observer to sub-solar point.
        #
        (spoint, trgepc, srfev) = \
            spiceypy.subslr('INTERCEPT/ELLIPSOID', target, et, frame, 'LT+S', observer)

        (sunlon, sunlat, sunalt) = spiceypy.recgeo(spoint, re, f)

        return tarlon, tarlat, sublon, sublat, sunlon, sunlat, tardis, tarang, ltime, phase, emissn, incdnc

    except:
        return 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
Ejemplo n.º 39
0
def compute_backplanes(file,
                       name_target,
                       frame,
                       name_observer,
                       angle1=0,
                       angle2=0,
                       angle3=0):
    """
    Returns a set of backplanes for a single specified image. The image must have WCS coords available in its header.
    Backplanes include navigation info for every pixel, including RA, Dec, Eq Lon, Phase, etc.
    
    The results are returned to memory, and not written to a file.
    
    SPICE kernels must be alreaded loaded, and spiceypy running.
    
    Parameters
    ----
    
    file:
        String. Input filename, for FITS file.
    frame:
        String. Reference frame of the target body. 'IAU_JUPITER', 'IAU_MU69', '2014_MU69_SUNFLOWER_ROT', etc.
        This is the frame that the Radius_eq and Longitude_eq are computed in.
    name_target:
        String. Name of the central body. All geometry is referenced relative to this (e.g., radius, azimuth, etc)
    name_observer:
        String. Name of the observer. Must be a SPICE body name (e.g., 'New Horizons')
    
    Optional Parameters
    ----
    
    angle{1,2,3}:
        **NOT REALLY TESTED. THE BETTER WAY TO CHANGE THE ROTATION IS TO USE A DIFFERENT FRAME.**

        Rotation angles which are applied when defining the plane in space that the backplane will be generated for.
        These are applied in the order 1, 2, 3. Angles are in radians. Nominal values are 0.
       
        This allows the simulation of (e.g.) a ring system inclined relative to the nominal body equatorial plane.
        
        For MU69 sunflower rings, the following descriptions are roughly accurate, becuase the +Y axis points
        sunward, which is *almost* toward the observer. But it is better to experiment and find the 
        appropriate angle that way, than rely on this ad hoc description. These are close for starting with.
 
                      - `angle1` = Tilt front-back, from face-on. Or rotation angle, if tilted right-left.
                      - `angle2` = Rotation angle, if tilted front-back. 
                      - `angle3` = Tilt right-left, from face-on.
    
    do_fast:
        Boolean. If set, generate only an abbreviated set of backplanes. **NOT CURRENTLY IMPLEMENTED.**
        
    Output
    ----

    Output is a tuple, consisting of each of the backplanes, and a text description for each one. 
    The size of each of these arrays is the same as the input image.
    
    The position of each of these is the plane defined by the target body, and the normal vector to the observer.
    
    output = (backplanes, descs)
    
        backplanes = (ra,      dec,      radius_eq,      longitude_eq,      phase)
        descs      = (desc_ra, desc_dec, desc_radius_eq, desc_longitude_eq, desc_phase)
    
    Radius_eq:
        Radius, in the ring's equatorial plane, in km
    Longitude_eq:
        Longitude, in the equatorial plane, in radians (0 .. 2pi)
    RA:
        RA of pixel, in radians
    Dec:
        Dec of pixel, in
    dRA:
        Projected offset in RA  direction between center of body (or barycenter) and pixel, in km.
    dDec:
        Projected offset in Dec direction between center of body (or barycenter) and pixel, in km.
        
    Z:  
        Vertical value of the ring system.
        
    With special options selected (TBD), then additional backplanes will be generated -- e.g., a set of planes
    for each of the Jovian satellites in the image, or sunflower orbit, etc.
    
    No new FITS file is written. The only output is the returned tuple.
    
    """

    if not (frame):
        raise ValueError('frame undefined')

    if not (name_target):
        raise ValueError('name_target undefined')

    if not (name_observer):
        raise ValueError('name_observer undefined')

    name_body = name_target  # Sometimes we use one, sometimes the other. Both are identical

    fov_lorri = 0.3 * hbt.d2r

    abcorr = 'LT'

    do_satellites = False  # Flag: Do we create an additional backplane for each of Jupiter's small sats?

    # Open the FITS file

    w = WCS(
        file
    )  # Warning: I have gotten a segfault here before if passing a FITS file with no WCS info.
    hdulist = fits.open(file)

    et = float(hdulist[0].header['SPCSCET'])  # ET of mid-exposure, on s/c
    n_dx = int(hdulist[0].header['NAXIS1']
               )  # Pixel dimensions of image. Both LORRI and MVIC have this.
    n_dy = int(hdulist[0].header['NAXIS2'])

    hdulist.close()

    # Setup the output arrays

    lon_arr = np.zeros(
        (n_dy, n_dx))  # Longitude of pixel (defined with recpgr)
    lat_arr = np.zeros(
        (n_dy, n_dx))  # Latitude of pixel (which is zero, so meaningless)
    radius_arr = np.zeros((n_dy, n_dx))  # Radius, in km
    altitude_arr = np.zeros((n_dy, n_dx))  # Altitude above midplane, in km
    ra_arr = np.zeros((n_dy, n_dx))  # RA of pixel
    dec_arr = np.zeros((n_dy, n_dx))  # Dec of pixel
    dra_arr = np.zeros(
        (n_dy, n_dx)
    )  # dRA  of pixel: Distance in sky plane between pixel and body, in km.
    ddec_arr = np.zeros(
        (n_dy, n_dx)
    )  # dDec of pixel: Distance in sky plane between pixel and body, in km.
    phase_arr = np.zeros((n_dy, n_dx))  # Phase angle
    x_arr = np.zeros(
        (n_dy, n_dx))  # Intersection of sky plane: X pos in bdoy coords
    y_arr = np.zeros(
        (n_dy, n_dx))  # Intersection of sky plane: X pos in bdoy coords
    z_arr = np.zeros(
        (n_dy, n_dx))  # Intersection of sky plane: X pos in bdoy coords

    # =============================================================================
    #  Do the backplane, in the general case.
    #  This is a long routine, because we deal with all the satellites, the J-ring, etc.
    # =============================================================================

    if (True):

        # Look up body parameters, used for PGRREC().

        (num, radii) = sp.bodvrd(name_target, 'RADII', 3)

        r_e = radii[0]
        r_p = radii[2]
        flat = (r_e - r_p) / r_e

        # Define a SPICE 'plane' along the plane of the ring.
        # Do this in coordinate frame of the body (IAU_JUPITER, 2014_MU69_SUNFLOWER_ROT, etc).

        # =============================================================================
        # Set up the Jupiter system specifics
        # =============================================================================

        if (name_target.upper() == 'JUPITER'):
            plane_target_eq = sp.nvp2pl(
                [0, 0, 1],
                [0, 0, 0
                 ])  # nvp2pl: Normal Vec + Point to Plane. Jupiter north pole?

            # For Jupiter only, define a few more output arrays for the final backplane set

            ang_metis_arr = np.zeros(
                (n_dy, n_dx))  # Angle from pixel to body, in radians
            ang_adrastea_arr = ang_metis_arr.copy()
            ang_thebe_arr = ang_metis_arr.copy()
            ang_amalthea_arr = ang_metis_arr.copy()

# =============================================================================
# Set up the MU69 specifics
# =============================================================================

        if ('MU69' in name_target.upper()):

            # Define a plane, which is the plane of sunflower rings (ie, X-Z plane in Sunflower frame)
            # If additional angles are passed, then create an Euler matrix which will do additional angles of rotation.
            # This is defined in the 'MU69_SUNFLOWER' frame

            vec_plane = [0, 1,
                         0]  # Use +Y (anti-sun dir), which is normal to XZ.
            #            vec_plane = [0, 0, 1]                                 # Use +Y (anti-sun dir), which is normal to XZ.
            plane_target_eq = sp.nvp2pl(
                vec_plane,
                [0, 0, 0])  # "Normal Vec + Point to Plane". 0,0,0 = origin.

        # XXX NB: This plane in in body coords, not J2K coords. This is what we want, because the
        # target intercept calculation is also done in body coords.

# =============================================================================
# Set up the various output planes and arrays necessary for computation
# =============================================================================

# Get xformation matrix from J2K to target system coords. I can use this for points *or* vectors.

        mx_j2k_frame = sp.pxform('J2000', frame, et)  # from, to, et

        # Get vec from body to s/c, in both body frame, and J2K.
        # NB: The suffix _j2k indicates j2K frame. _frame indicates the frame of target (IAU_JUP, MU69_SUNFLOWER, etc)

        (st_target_sc_frame, lt) = sp.spkezr(name_observer, et, frame, abcorr,
                                             name_target)
        (st_sc_target_frame, lt) = sp.spkezr(name_target, et, frame, abcorr,
                                             name_observer)
        (st_target_sc_j2k, lt) = sp.spkezr(name_observer, et, 'J2000', abcorr,
                                           name_target)
        (st_sc_target_j2k, lt) = sp.spkezr(name_target, et, 'J2000', abcorr,
                                           name_observer)

        vec_target_sc_frame = st_target_sc_frame[0:3]
        vec_sc_target_frame = st_sc_target_frame[0:3]
        vec_target_sc_j2k = st_target_sc_j2k[0:3]
        vec_sc_target_j2k = st_sc_target_j2k[0:3]

        dist_target_sc = sp.vnorm(
            vec_target_sc_j2k)  # Get target distance, in km

        # vec_sc_target_frame = -vec_target_sc_frame # ACTUALLY THIS IS NOT TRUE!! ONLY TRUE IF ABCORR=NONE.

        # Name this vector a 'point'. INRYPL requires a point argument.

        pt_target_sc_frame = vec_target_sc_frame

        # Look up RA and Dec of target (from sc), in J2K

        (_, ra_sc_target, dec_sc_target) = sp.recrad(vec_sc_target_j2k)

        # Get vector from target to sun. We use this later for phase angle.

        (st_target_sun_frame,
         lt) = sp.spkezr('Sun', et, frame, abcorr,
                         name_target)  # From body to Sun, in body frame
        vec_target_sun_frame = st_target_sun_frame[0:3]

        # Create a 2D array of RA and Dec points
        # These are made by WCS, so they are guaranteed to be right.

        xs = range(n_dx)
        ys = range(n_dy)
        (i_x_2d, i_y_2d) = np.meshgrid(xs, ys)
        (ra_arr, dec_arr) = w.wcs_pix2world(i_x_2d, i_y_2d,
                                            False)  # Returns in degrees
        ra_arr *= hbt.d2r  # Convert to radians
        dec_arr *= hbt.d2r

        # Compute the projected distance from MU69, in the sky plane, in km, for each pixel.
        # dist_target_sc is the distance to MU69, and we use this to convert from radians, to km.
        # 16-Oct-2018. I had been computing this erroneously. It should be *cosdec, not /cosdec.

        dra_arr = (ra_arr - ra_sc_target) * dist_target_sc * np.cos(dec_arr)
        ddec_arr = (dec_arr - dec_sc_target) * dist_target_sc  # Convert to km

        # =============================================================================
        #  Compute position for additional Jupiter bodies, as needed
        # =============================================================================

        if (name_target.upper() == 'JUPITER'):
            vec_metis_j2k, lt = sp.spkezr('Metis', et, 'J2000', abcorr,
                                          'New Horizons')
            vec_adrastea_j2k, lt = sp.spkezr('Adrastea', et, 'J2000', abcorr,
                                             'New Horizons')
            vec_thebe_j2k, lt = sp.spkezr('Thebe', et, 'J2000', abcorr,
                                          'New Horizons')
            vec_amalthea_j2k, lt = sp.spkezr('Amalthea', et, 'J2000', abcorr,
                                             'New Horizons')

            vec_metis_j2k = np.array(vec_metis_j2k[0:3])
            vec_thebe_j2k = np.array(vec_thebe_j2k[0:3])
            vec_adrastea_j2k = np.array(vec_adrastea_j2k[0:3])
            vec_amalthea_j2k = np.array(vec_amalthea_j2k[0:3])

# =============================================================================
# Loop over pixels in the output image
# =============================================================================

        for i_x in xs:
            for i_y in ys:

                # Look up the vector direction of this single pixel, which is defined by an RA and Dec
                # Vector is thru pixel to ring, in J2K.
                # RA and Dec grids are made by WCS, so they are guaranteed to be right.

                vec_pix_j2k = sp.radrec(1., ra_arr[i_y, i_x], dec_arr[i_y,
                                                                      i_x])

                # Convert vector along the pixel direction, from J2K into the target body frame

                vec_pix_frame = sp.mxv(mx_j2k_frame, vec_pix_j2k)

                # And calculate the intercept point between this vector, and the ring plane.
                # All these are in body coordinates.
                # plane_target_eq is defined as the body's equatorial plane (its XZ for MU69).

                # ** Some question as to whether we should shoot this vector at the ring plane, or the sky plane.
                # Ring plane is normally the one we want. But, for the case of edge-on rings, the eq's break down.
                # So, we should use the sky plane instead. Testing shows that for the case of MU69 Eq's break down
                # for edge-on rings... there is always an ambiguity.

                # ** For testing, try intersecting the sky plane instead of the ring plane.
                # ** Confirmed: Using sky plane gives identical results in case of face-on rings.
                #    And it gives meaningful results in case of edge-on rings, where ring plane did not.
                #    However, for normal rings (e.g., Jupiter), we should continue using the ring plane, not sky plane.

                do_sky_plane = True  # For ORT4, where we want to use euler angles, need to set this to False

                if do_sky_plane and ('MU69' in name_target):
                    plane_sky_frame = sp.nvp2pl(
                        vec_sc_target_frame,
                        [0, 0, 0])  # Frame normal to s/c vec, cntrd on MU69
                    (npts,
                     pt_intersect_frame) = sp.inrypl(pt_target_sc_frame,
                                                     vec_pix_frame,
                                                     plane_sky_frame)

                    # pt_intersect_frame is the point where the ray hits the skyplane, in the coordinate frame
                    # of the target body.

                else:  # Calc intersect into equator of target plane (ie, ring plane)
                    (npts,
                     pt_intersect_frame) = sp.inrypl(pt_target_sc_frame,
                                                     vec_pix_frame,
                                                     plane_target_eq)
                    # pt, vec, plane

                # Swap axes in target frame if needed.
                # In the case of MU69 (both sunflower and tunacan), the frame is defined s.t. the ring
                # is in the XZ plane, not XY. This is strange (but correct).
                # I bet MU69 is the only ring like this. Swap it so that Z means 'vertical, out of plane' --
                # that is, put it into normal XYZ rectangular coords, so we can use RECLAT etc on it.

                if (
                        'MU69' in name_target
                ):  # Was 0 2 1. But this makes tunacan radius look in wrong dir.
                    # 201 looks same
                    # 210 similar
                    # 201 similar
                    # 102, 120 similar.
                    # ** None of these change orientation of 'radius' backplane. OK.

                    pt_intersect_frame = np.array([
                        pt_intersect_frame[0], pt_intersect_frame[2],
                        pt_intersect_frame[1]
                    ])

                # Get the radius and azimuth of the intersect, in the ring plane
                # Q: Why for the TUNACAN is the radius zero here along horizontal (see plot)?
                # A: Ahh, it is not zero. It is just that the 'projected radius' of a ring that is nearly edge-on
                # can be huge! Basically, if we try to calc the intersection with that plane, it will give screwy
                # answers, because the plane is so close to edge-on that intersection could be a long way
                # from body itself.

                # Instead, I really want to take the tangent sky plane, intersect that, and then calc the
                # position of that (in xyz, radius, longitude, etc).
                # Since that plane is fixed, I don't see a disadvantage to doing that.

                # We want the 'radius' to be the radius in the equatorial plane -- that is, sqrt(x^2 + y^2).
                # We don't want it to be the 'SPICE radius', which is the distance.
                # (For MU69 equatorial plane is nominally XZ, but we have already changed that above to XY.)

                _radius_3d, lon, lat = sp.reclat(pt_intersect_frame)

                radius_eq = sp.vnorm(
                    [pt_intersect_frame[0], pt_intersect_frame[1], 0])
                #                radius_eq = sp.vnorm([pt_intersect_frame[0], pt_intersect_frame[1], pt_intersect_frame[2]])

                # Get the vertical position (altitude)

                altitude = pt_intersect_frame[2]

                # Calculate the phase angle: angle between s/c-to-ring, and ring-to-sun

                vec_ring_sun_frame = -pt_intersect_frame + vec_target_sun_frame

                angle_phase = sp.vsep(-vec_pix_frame, vec_ring_sun_frame)

                # Save various derived quantities

                radius_arr[i_y, i_x] = radius_eq
                lon_arr[i_y, i_x] = lon
                phase_arr[i_y, i_x] = angle_phase
                altitude_arr[i_y, i_x] = altitude

                # Save these just for debugging

                x_arr[i_y, i_x] = pt_intersect_frame[0]
                y_arr[i_y, i_x] = pt_intersect_frame[1]
                z_arr[i_y, i_x] = pt_intersect_frame[2]

                # Now calc angular separation between this pixel, and the satellites in our list
                # Since these are huge arrays, cast into floats to make sure they are not doubles.

                if (name_body.upper() == 'JUPITER'):
                    ang_thebe_arr[i_y, i_x] = sp.vsep(vec_pix_j2k,
                                                      vec_thebe_j2k)
                    ang_adrastea_arr[i_y,
                                     i_x] = sp.vsep(vec_pix_j2k,
                                                    vec_adrastea_j2k)
                    ang_metis_arr[i_y, i_x] = sp.vsep(vec_pix_j2k,
                                                      vec_metis_j2k)
                    ang_amalthea_arr[i_y,
                                     i_x] = sp.vsep(vec_pix_j2k,
                                                    vec_amalthea_j2k)

        # Now, fix a bug. The issue is that SP.INRYPL uses the actual location of the bodies (no aberration),
        # while their position is calculated (as it should be) with abcorr=LT. This causes a small error in the
        # positions based on the INRYPL calculation. This should probably be fixed above, but it was not
        # obvious how. So, instead, I am fixing it here, by doing a small manual offset.

        # Calculate the shift required, by measuring the position of MU69 with abcorr=NONE, and comparing it to
        # the existing calculation, that uses abcorr=LT. This is brute force, but it works. For MU69 approach,
        # it is 0.75 LORRI 4x4 pixels (ie, 3 1X1 pixels). This is bafflingly huge (I mean, we are headed
        # straight toward MU69, and it takes a month to move a pixel, and RTLT is only a few minutes). But I have
        # confirmed the math and the magnitude, and it works.

        (st_sc_target_j2k_nolt, _) = sp.spkezr(name_target, et, 'J2000',
                                               'NONE', name_observer)
        vec_sc_target_j2k_nolt = st_sc_target_j2k_nolt[0:3]
        (_, ra_sc_target_nolt,
         dec_sc_target_nolt) = sp.recrad(vec_sc_target_j2k_nolt)

        (x0, y0) = w.wcs_world2pix(ra_sc_target_nolt * hbt.r2d,
                                   dec_sc_target_nolt * hbt.r2d, 1)
        (x1, y1) = w.wcs_world2pix(ra_sc_target * hbt.r2d,
                                   dec_sc_target * hbt.r2d, 1)
        dx = x1 - x0
        dy = y1 - y0

        print(f'Compute backplanes: INRYPL pixel shift = {dx}, {dy}')

        dx_int = int(round(dx))
        dy_int = int(round(dy))

        do_roll = True

        if do_roll:
            print(
                f'compute_backplanes: Rolling by {dx_int}, {dy_int} due to INRYPL'
            )

            # Now shift all of the planes that need fixing. The dRA_km and dDec_km are calculated before INRYPL()
            # is applied, so they do not need to be shifted. I have validated that by plotting them.
            #
            # XXX NP.ROLL() is really not ideal. I should use a function that introduces NaN at the edge, not roll it.

            radius_arr = np.roll(np.roll(radius_arr, dy_int, axis=0),
                                 dx_int,
                                 axis=1)
            lon_arr = np.roll(np.roll(lon_arr, dy_int, axis=0), dx_int, axis=1)
            phase_arr = np.roll(np.roll(phase_arr, dy_int, axis=0),
                                dx_int,
                                axis=1)
            altitude_arr = np.roll(np.roll(altitude_arr, dy_int, axis=0),
                                   dx_int,
                                   axis=1)
        else:
            print(
                f'compute_backplanes: Skipping roll due to INRYPL, based on do_roll={do_roll}'
            )

        # Assemble the results into a backplane

        backplane = {
            'RA': ra_arr.astype(float),  # return radians
            'Dec': dec_arr.astype(float),  # return radians 
            'dRA_km': dra_arr.astype(float),
            'dDec_km': ddec_arr.astype(float),
            'Radius_eq': radius_arr.astype(float),
            'Longitude_eq': lon_arr.astype(float),
            'Phase': phase_arr.astype(float),
            'Altitude_eq': altitude_arr.astype(float),
            #             'x'            : x_arr.astype(float),
            #             'y'            : y_arr.astype(float),
            #             'z'            : z_arr.astype(float),
            #
        }

        # Assemble a bunch of descriptors, to be put into the FITS headers

        desc = {
            'RA of pixel, radians',
            'Dec of pixel, radians',
            'Offset from target in target plane, RA direction, km',
            'Offset from target in target plane, Dec direction, km',
            'Projected equatorial radius, km',
            'Projected equatorial longitude, km',
            'Sun-target-observer phase angle, radians',
            'Altitude above midplane, km',
            #                'X position of sky plane intercept',
            #                'Y position of sky plane intercept',
            #                'Z position of sky plane intercept'
        }

        # In the case of Jupiter, add a few extra fields

        if (name_body.upper() == 'JUPITER'):
            backplane['Ang_Thebe'] = ang_thebe_arr.astype(
                float)  # Angle to Thebe, in radians
            backplane['Ang_Metis'] = ang_metis_arr.astype(float)
            backplane['Ang_Amalthea'] = ang_amalthea_arr.astype(float)
            backplane['Ang_Adrastea'] = ang_adrastea_arr.astype(float)

            # If distance to any of the small sats is < 0.3 deg, then delete that entry in the dictionary

            if (np.amin(ang_thebe_arr) > fov_lorri):
                del backplane['Ang_Thebe']
            else:
                print("Keeping Thebe".format(np.min(ang_thebe_arr) * hbt.r2d))

            if (np.amin(ang_metis_arr) > fov_lorri):
                del backplane['Ang_Metis']
            else:
                print("Keeping Metis, min = {} deg".format(
                    np.min(ang_metis_arr) * hbt.r2d))

            if (np.amin(ang_amalthea_arr) > fov_lorri):
                del backplane['Ang_Amalthea']
            else:
                print("Keeping Amalthea, min = {} deg".format(
                    np.amin(ang_amalthea_arr) * hbt.r2d))

            if (np.amin(ang_adrastea_arr) > fov_lorri):
                del backplane['Ang_Adrastea']
            else:
                print("Keeping Adrastea".format(
                    np.min(ang_adrastea_arr) * hbt.r2d))

    # And return the backplane set

    return (backplane, desc)
Ejemplo n.º 40
0
def get_planet_magnitude(object_id, pos_planet, pos_basis):
    """ Planet and Moon magnitudes """

    planet_abs_mag = {
        199: -0.42,  # OBJECT_ID_MERCURY
        299: -4.40,  # OBJECT_ID_VENUS
        399: -2.96,  # OBJECT_ID_EARTH
        499: -1.52,  # OBJECT_ID_MARS
        599: -9.40,  # OBJECT_ID_JUPITER
        699: -8.68,  # OBJECT_ID_SATURN
        799: -7.19,  # OBJECT_ID_URANUS
        899: -6.87,  # OBJECT_ID_NEPTUNE
        999: -1.00,  # OBJECT_ID_PLUTO
    }

    # distance between each objects in AU
    d_planet_basis = spice.vdist(pos_planet, pos_basis)
    d_planet_basis = spice.convrt(d_planet_basis, "km", "AU")
    d_planet_sun = spice.vnorm(pos_planet)
    d_planet_sun = spice.convrt(d_planet_sun, "km", "AU")
    d_sun_basis = spice.vnorm(pos_basis)
    d_sun_basis = spice.convrt(d_sun_basis, "km", "AU")

    if object_id == OBJECT_ID_SUN:
        return -27.3 + 5.0 * np.log10(d_sun_basis)
    elif object_id == OBJECT_ID_MOON:
        return 0.38
    elif object_id in [
            OBJECT_ID_MERCURY,
            OBJECT_ID_VENUS,
            OBJECT_ID_EARTH,
            OBJECT_ID_MARS,
            OBJECT_ID_JUPITER,
            OBJECT_ID_SATURN,
            OBJECT_ID_URANUS,
            OBJECT_ID_NEPTUNE,
            OBJECT_ID_PLUTO,
    ]:
        pass
    else:
        return None

    mag = planet_abs_mag[object_id]
    mag += 5.0 * np.log10(d_planet_basis * d_planet_sun)

    if object_id <= OBJECT_ID_JUPITER:
        pa = 0.0
        tpa = (d_planet_sun**2 + d_planet_basis**2 -
               d_sun_basis) / (2.0 * d_planet_sun * d_planet_basis)
        tpa = np.clip(tpa, -1.0, 1.0)
        pa = np.degrees(np.arccos(tpa))

        if object_id == OBJECT_ID_MERCURY:
            mag += (0.0380 - 0.000273 * pa + 0.000002 * pa * pa) * pa
        elif object_id == OBJECT_ID_VENUS:
            mag += (0.0009 + 0.000239 * pa - 0.00000065 * pa * pa) * pa
        elif object_id == OBJECT_ID_MARS:
            mag += 0.016 * pa
        elif object_id == OBJECT_ID_JUPITER:
            mag += 0.005 * pa
    elif object_id == OBJECT_ID_SATURN:
        mag -= 1.1 * 0.3
    return mag
Ejemplo n.º 41
0
           ,indent=2
           )

  import matplotlib.pyplot as plt
  plt.axhline(0,color='k',linewidth=0.5)
  plt.axvline(0,color='k',linewidth=0.5)
  plt.plot(ets,dvs)
  plt.xlabel('Time, s past TCA')
  plt.ylabel('Differential dRange/dt, km/s')
  plt.title('dRange/dt difference\n[Target wrt Instr] - [Target wrt S/C]')
  plt.show()
  
  plt.axhline(8,color='k',linewidth=0.5)
  plt.axhline(10,color='k',linewidth=0.5)
  plt.axvline(0,color='k',linewidth=0.5)
  plt.plot(ets,[sp.vnorm(porx) for porx in porxs],'r',label='Bennu wrt S/C')
  plt.plot(ets,[sp.vnorm(porxinstr) for porxinstr in porxinstrs],'g',label='Bennu wrt Instr',linewidth=0.65)
  plt.xlabel('Time, s past TCA')
  plt.ylabel('Range, km')
  plt.title('Time vs. Range to target:  wrt Instr; wrt S/C')
  plt.legend(loc='best')
  plt.show()
  
  plt.axhline(8,color='k',linewidth=0.5)
  plt.axhline(10,color='k',linewidth=0.5)
  plt.axvline(6,color='k',linewidth=0.5)
  plt.axvline(0,color='k',linewidth=0.5)
  plt.plot([porx[0] for porx in porxs]
          ,[sp.vnorm(porx) for porx in porxs]
          ,'r',label='Bennu wrt S/C')
  plt.plot([porxinstr[0] for porxinstr in porxinstrs]