Example #1
0
def relative_position(obj, reference, ettemp, frame='J2000'):
    kernels = load_kernels()

    if isinstance(ettemp, Time):
        et = spice.str2et(ettemp.isot)
    elif isinstance(ettemp, float):
        et = ettemp
    else:
        et = [
            spice.str2et(e.isot) if type(e) == type(Time.now()) else e
            for e in ettemp
        ]

    abcor = 'LT+S'

    if isinstance(et, float):
        posvel, lt = spice.spkezr(obj, et, frame, abcor, reference)
        pos, vel = posvel[:3], posvel[3:]
    else:
        pos = np.zeros((len(et), 3))
        vel = np.zeros((len(et), 3))
        for i, t in enumerate(et):
            posvel, lt = spice.spkezr(obj, t, frame, abcor, reference)
            pos[i, :] = np.array(posvel[:3])
            vel[i, :] = np.array(posvel[3:])
    pos *= u.km
    vel *= u.km / u.s

    for k in kernels:
        spice.unload(k)

    return pos, vel
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
Example #3
0
def Calculate_SCoords(run):

    #setup
    import spiceypy as spice
    import numpy as np
    import math
    spice.furnsh("./MetDat/MoonMetdat.txt")

    #import ~_setup.txt and SPK (~.bsp) file
    slines = []
    with open(run + '_setup.txt') as f:
        slines = f.read().splitlines()
    spice.furnsh(run + '.bsp')

    #get TLE_SPK_OBJ_ID and et (time, seconds past J2000) from TLE File
    obj_id = slines[5].split('=')[1]
    et = float(slines[28].split('=')[1])
    print ''
    #read out date as yyyy mmm dd hr:min:sec.millisecond
    print '\n', spice.et2utc(et, 'C', 3)

    #Calculate sub-observer point and distance
    state = spice.spkezr(obj_id, et, "MOON_PA", "LT+S", "Moon")
    s_obs = spice.reclat(state[0][0:3])
    print '\nSub-Observer Point:'
    print ' Sat-Moon distance: ', s_obs[0], 'km'
    print ' Satellite sub-long: ', s_obs[1] * 180 / math.pi, 'deg'
    print ' Satellite sub-lat:  ', s_obs[2] * 180 / math.pi, 'deg'

    #Calculate sub-Earth point and distance
    state = spice.spkezr("Earth", et, "MOON_PA", "LT+S", "Moon")
    s_eat = spice.reclat(state[0][0:3])
    print '\nSub-Earth Point:'
    print ' Earth-Moon distance: ', s_eat[0], 'km'
    print ' Earth sub-long: ', s_eat[1] * 180 / math.pi, 'deg'
    print ' Earth sub-lat:  ', s_eat[2] * 180 / math.pi, 'deg'

    #Calculate sub-Sun point and distance
    state = spice.spkezr("Sun", et, "MOON_PA", "LT+S", "Moon")
    s_sun = spice.reclat(state[0][0:3])
    print '\nSub-Sun Point:'
    print ' Sun-Moon distance: ', s_sun[0], 'km'
    print ' Sun sub-long: ', 90 - s_sun[1] * 180 / math.pi, 'deg'
    print ' Sun sub-lat:  ', s_sun[2] * 180 / math.pi, 'deg\n'

    #Writes selenographic coordiantes to a file named 'run'+_spoints.txt
    with open(run + '_spoints.txt', 'w') as f:
        f.write('' + '\n#Sub-Observer Point:' + '\n\t Sat-Moon distance: ' +
                str(s_obs[0]) + '\n\t slong: ' +
                str(s_obs[1] * 180 / math.pi) + '\n\t slat: ' +
                str(s_obs[2] * 180 / math.pi) + '\n\n#Sub-Earth Point:' +
                '\n\t Earth-Moon distance: ' + str(s_eat[0]) + '\n\t slong: ' +
                str(s_eat[1] * 180 / math.pi) + '\n\t slat: ' +
                str(s_eat[2] * 180 / math.pi) + '\n\n#Sub-Sun Point:' +
                '\n\t Sun-Moon distance: ' + str(s_sun[0]) + '\n\t slong: ' +
                str(90 - s_sun[1] * 180 / math.pi) + '\n\t slat: ' +
                str(s_sun[2] * 180 / math.pi))

    return [slines[21].split('=')[1][1:], et, s_obs, s_eat, s_sun]
Example #4
0
 def _get_body_barycentric_acc(self, body, t):
     #Uses numerical differentiation to estimate body acceleration
     interval = 86400
     v1 = spice.spkezr(body, t - interval, 'J2000', 'NONE',
                       'solar system barycenter')[0][3:6] * 1000
     v2 = spice.spkezr(body, t + interval, 'J2000', 'NONE',
                       'solar system barycenter')[0][3:6] * 1000
     return (v2 - v1) / (2 * interval)
Example #5
0
    def PlotJourneyCentralBodyOrbits(self, JourneyAxes, PlotOptions):
        if PlotOptions.PlotCentralBody != 'Journey central body':
            import spiceypy
            journey_central_body = self.central_body
                            
            if journey_central_body in ['Mars','Jupiter','Uranus','Neptune','Pluto']:
                journey_central_body = journey_central_body + ' Barycenter'

            if journey_central_body != PlotOptions.PlotCentralBody:
                body_state, LT = spiceypy.spkezr(journey_central_body, (self.missionevents[0].JulianDate - 2451545.0) * 86400.0, 'J2000', "None", PlotOptions.PlotCentralBody)
                
                
                r = np.linalg.norm(body_state[0:3])
                v = np.linalg.norm(body_state[3:6])

                mu = {'Mercury':                   22031.780000,
                      'Venus':                    324858.592000,
                      'Earth':                    398600.435436,
                      'Mars':                      42828.375214,
                      'Jupiter Barycenter':    126712764.800000,
                      'Saturn Barycenter':      37940585.200000,
                      'Uranus Barycenter':       5794548.600000,
                      'Neptune Barycenter':      6836527.100580,
                      'Pluto Barycenter':            977.000000,
                      'Sun':                132712440041.939400,
                      'Moon':                       4902.800066,
                      'Earth-Moon Barycenter':    403503.235502}

                elements = spiceypy.oscltx(body_state, (self.missionevents[0].JulianDate - 2451545.0) * 86400.0, mu[PlotOptions.PlotCentralBody])
                a = elements[-2]
                #a = r / (2.0 - r*v*v)
                if a > 0.0:
                    T = 2*math.pi  *math.sqrt(a**3 / mu[PlotOptions.PlotCentralBody]) 
                else:
                    T = 2*math.pi * self.TU

                t0 = (self.missionevents[0].JulianDate - 2451545.0) * 86400.0
                tf = t0 + T
                X = []
                Y = []
                Z = []
                for epoch in np.linspace(t0, tf, num=100):
                    body_state, LT = spiceypy.spkezr(journey_central_body, epoch, 'J2000', "None", PlotOptions.PlotCentralBody)
                            
                    X.append(body_state[0])
                    Y.append(body_state[1])
                    Z.append(body_state[2])

                JourneyAxes.plot(X, Y, Z, lw=1, c='0.75')
Example #6
0
def tess_earth_vector(time):
    """Return the cartesian position of TESS relative to the Earth."""
    # Load the SPICE data
    ephemFiles = glob.glob(path + "TESS_EPH_PRE_LONG_2018*.bsp")
    tlsFile = path + "tess2018338154046-41240_naif0012.tls"
    solarSysFile = path + "tess2018338154429-41241_de430.bsp"
    for ephFil in ephemFiles:
        spice.furnsh(ephFil)
    spice.furnsh(tlsFile)
    spice.furnsh(solarSysFile)

    # JD time range
    allTJD = time + TJD0
    nT = len(allTJD)
    allET = np.zeros((nT, ), dtype=np.float)
    for i, t in enumerate(allTJD):
        allET[i] = spice.unitim(t, "JDTDB", "ET")

    # Calculate positions of TESS, the Earth, and the Sun
    # Note that our reference frame is that of `starry`,
    # where `y` points *north*.
    # This is just the rotation {x, y, z} --> {z, x, y}
    # relative to the J200 mean equatorial coordinates.
    tess = np.zeros((3, len(allET)))
    for i, et in enumerate(allET):
        outTuple = spice.spkezr("Mgs Simulation", et, "J2000", "NONE", "Earth")
        tess[0, i] = outTuple[0][1] * REARTH
        tess[1, i] = outTuple[0][2] * REARTH
        tess[2, i] = outTuple[0][0] * REARTH
    return tess
    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
def Location(et, ingress, sv, when):
    Coords = np.ones(3)
    [tgopos, _] = spice.spkpos(sv.front, et - when, sv.fframe, 'NONE',
                               sv.target)
    [mexpos, _] = spice.spkpos(sv.front, et - when, sv.fframe, 'NONE', sv.obs)
    [states, _] = spice.spkezr(sv.target, et - when, sv.fframe, 'NONE', sv.obs)
    sc2scvector = states[0:3]
    velocity = states[3:6]
    relativespeed = np.linalg.norm(velocity)
    # e9 because we are converting from km to m (SPICE outputs km, but constants in m)
    veldopp = (relativespeed / constants.c) * 437.1e9
    displacement = np.linalg.norm(sc2scvector)
    sc2scunitvector = np.true_divide(sc2scvector, displacement)
    # Extract the triaxial dimensions of Mars
    marsrad = spice.bodvrd(sv.front, 'RADII', 3)
    # For the ray that connects MEX and TGO, find the point on this ray that is closest to the Martian surface
    [nearestpoint, alt] = spice.npedln(marsrad[1][0], marsrad[1][1],
                                       marsrad[1][2], tgopos, sc2scunitvector)
    # THERE IS MORE SETTINGS ON THIS
    [radius, lon, lat] = spice.reclat(nearestpoint)
    # Rad -> Deg , frame inversion required (hence the negative 180)
    lon = 180 - (lon * (-180 / math.pi))
    lat = lat * (-180 / math.pi)

    MexNadirTGOAngle = spice.vsep(-mexpos, -sc2scvector)
    MexNadirTGOAngle = MexNadirTGOAngle * (180 / math.pi)

    # produce a string of the date and time, because an ephemeris time is not human-readable
    date_time = spice.timout(et, 'MM-DD HR:MN:SC')
    ingress_date_time = spice.timout(ingress, 'MM-DD HR:MN:SC')
    return lon, lat, displacement, nearestpoint, alt, relativespeed, date_time, ingress_date_time, veldopp, MexNadirTGOAngle
Example #9
0
def bodyPosition(body,et):
    import spiceypy as sp

    x,tl=sp.spkezr(body,et,"J2000","NONE","EARTH")
    R,alpha,dec=sp.recrad(x[:3])

    return R,alpha,dec
Example #10
0
    def spacecraft_direction(self):
        """
        Returns the x axis of the first velocity vector relative to the
        spacecraft. This indicates of the craft is moving forwards or backwards.

        From LROC Frame Kernel: lro_frames_2014049_v01.tf
        "+X axis is in the direction of the velocity vector half the year. The
        other half of the year, the +X axis is opposite the velocity vector"

        Hence we rotate the first velocity vector into the sensor reference
        frame, but the X component of that vector is inverted compared to the
        spacecraft so a +X indicates backwards and -X indicates forwards

        The returned velocity is also slightly off from the spacecraft velocity
        due to the sensor being attached to the craft with wax.

        Returns
        -------
        direction : double
                    X value of the first velocity relative to the sensor
        """
        frame_chain = self.frame_chain
        lro_bus_id = spice.bods2c('LRO_SC_BUS')
        time = self.ephemeris_start_time
        state, _ = spice.spkezr(self.spacecraft_name, time, 'J2000', 'None',
                                self.target_name)
        position = state[:3]
        velocity = state[3:]
        rotation = frame_chain.compute_rotation(1, lro_bus_id)
        rotated_velocity = spice.mxv(rotation._rots.as_matrix()[0], velocity)
        return rotated_velocity[0]
    def get_target_xyz(t):
        """
        Returns the vectors of the Horizons body at a certain time t.
        
        Arguments:
        
        t: days
        
        Julian date of observation
        
        Returns:
    
        xyz: numpy array
        
        A position vector of the observed object
    
        uvw: numpy array
        
        An instantaneous velocity vector of the observed object
        
        radec: numpy array
        
        The right ascension and declination of the observed object
        """

        state, lighttime = spice.spkezr(name, t, 'J2000', cor, loc)
        pos, lighttime = spice.spkpos(name, t, 'J2000', cor, loc)
        range, ra, dec = spice.recrad(pos)
        xyz = np.array([state[0], state[1], state[2]
                        ]) / 149597870.7  #6.68459e-9
        uvw = np.array([state[3], state[4], state[5]
                        ]) / 149597870.7 * 24. * 3600.  #*6.68459e-9
        radec = np.array([ra, dec])
        return xyz, uvw, radec * 180 / np.pi
Example #12
0
    def nightlystates(self,timerange):
        
        """ Generate asteroid state at every midnight within the input time range
        
        Expects integer MJD dates (midnight). Generates geocentric coordinates
        (range, RA, DEC, rangerate, dRA, dDec) for the asteroid at each midnight
        between, and including, start and end.

        Parameters
        ----------
            timerange: 2 element float numpy array
                [Starting time, Ending time] (MJD)
                
        """

        # Converting from MJD to JD and computing number of days including endpoints
        start=int(timerange[0])+shared.mjd2jd
        stop =int(timerange[-1])+shared.mjd2jd
        ndays=int(stop-start)+1

        # Loading SPK
        try:
            sp.furnsh(self.spkname)
        except:
            sys.exit("Cannot load %s" %(self.spkname))

        # Initializing state variables
        self.nightlyt=np.zeros(ndays)
        self.nightlyr=np.zeros(ndays)
        self.nightlydr=np.zeros(ndays)
        self.nightlyra=np.zeros(ndays)
        self.nightlydra=np.zeros(ndays)
        self.nightlydec=np.zeros(ndays)
        self.nightlyddec=np.zeros(ndays)
 
        counter = -1

        for i in np.linspace(start,stop,num=ndays):

            counter += 1

            # Converting from UTC to ET
            timeet=sp.str2et('JD '+repr(i))

            # Finding direction of asteroids from geocenter
            # Finding states wrt station adds 4 days for 1e6 objects

            asteroidsvec=sp.spkezr(str(self.spiceid),timeet,"J2000","LT","Earth") # Units are km and km/s
            
            # Converting from Rectangular to Latitudinal coordinates.
            # tmp is a 6 element vector (R, Long, Lat, Dr, Dlong, Dlat) Units are radians and radians/s
            tmp=sp.xfmsta(asteroidsvec[0][0:6],"RECTANGULAR","LATITUDINAL"," ")

            self.nightlyt[counter]   = i
            self.nightlyr[counter]   = tmp[0]
            self.nightlydr[counter]  = tmp[3]
            self.nightlyra[counter]  = tmp[1]
            self.nightlydra[counter] = tmp[4]
            self.nightlydec[counter] = tmp[2]
            self.nightlyddec[counter]= tmp[5]
Example #13
0
    def sun_position(self):
        """
        Returns a tuple with information detailing the sun position at the time
        of the image. Expects center_ephemeris_time to be defined. This must be
        a floating point number containing the average of the start and end ephemeris time.
        Expects reference frame to be defined. This must be a sring containing the name of
        the target reference frame. Expects target_name to be defined. This must be
        a string containing the name of the target body.

        Returns
        -------
        : (sun_positions, sun_velocities)
          a tuple containing a list of sun positions, a list of sun velocities
        """
        times = [self.center_ephemeris_time]
        positions = []
        velocities = []

        for time in times:
            sun_state, _ = spice.spkezr("SUN", time, self.reference_frame,
                                        'LT+S', self.target_name)
            positions.append(sun_state[:3])
            velocities.append(sun_state[3:6])
        positions = 1000 * np.asarray(positions)
        velocities = 1000 * np.asarray(velocities)

        return positions, velocities, times
Example #14
0
 def _compute_ephemerides(self):
     """
     Helper function to pull position and velocity in one pass
     so that the results can then be cached in the associated
     properties.
     """
     eph = np.empty((self.number_of_ephemerides, 3))
     eph_rates = np.empty(eph.shape)
     current_et = self.starting_ephemeris_time
     for i in range(self.number_of_ephemerides):
         state, _ = spice.spkezr(
             self.spacecraft_name,
             current_et,
             self.reference_frame,
             'NONE',
             self.target_name,
         )  # If this is the sensor, insufficient, if this is the spacecraft, it works? Huh?
         eph[i] = state[:3]
         eph_rates[i] = state[3:]
         current_et += getattr(self, 'dt_ephemeris', 0)
     # By default, spice works in km
     eph *= 1000
     eph_rates *= 1000
     self._sensor_velocity = eph_rates
     self._sensor_position = eph
Example #15
0
    def position_velocity(self, jd, of='TESS', relative_to='EARTH'):
        """
		Returns position and velocity of TESS for the given timestamps as geocentric
		XYZ-coordinates in kilometers.

		Parameters:
			jd (ndarray): Time in Julian Days where position of TESS should be calculated.
			of (string, optional): Object for which to calculate position for. Default='TESS'.
			relative_to (string, optional): Object for which to calculate position relative to.
				Default='EARTH'.

		Returns:
			ndarray: Position and velocity of TESS as geocentric XYZ-coordinates in kilometers.

		.. codeauthor:: Rasmus Handberg <*****@*****.**>
		"""

        # Convert JD to Ephemeris Time:
        jd = np.atleast_1d(jd)
        times = [spiceypy.unitim(j, 'JDTDB', 'ET') for j in jd]

        # Get state of spacecraft (position and velocity):
        try:
            pos_vel, lt = spiceypy.spkezr(of, times, 'J2000', 'NONE',
                                          relative_to)
            pos_vel = np.asarray(pos_vel)
        except SpiceyError as e:
            if 'SPICE(SPKINSUFFDATA)' in e.value:
                raise InadequateSpiceException(
                    "Inadequate SPICE kernels available")
            else:
                raise

        return pos_vel
Example #16
0
def velocityUlysses(date, RF="HCI"):
    """
    Get Ulysses' eigen-velocity from SPICE and transform it to RTN-coordinates

    :param date: datetime object
    :param RF: etspice.ReferenceFrame
    :return: eigen-velocity in RTN-coordinates
    """
    from spiceypy import spkezr, datetime2et
    [x, y, z, vx, vy, vz] = spkezr('ULYSSES', datetime2et(date), RF, 'None',
                                   'SUN')[0]  # cart in km(/s)
    r_sph = cart2spher(np.array([x, y, z]))
    v_sph = cart2spher(np.array(
        [vx, vy,
         vz]))  #does not really make sense but is needed for the rtn-function
    if RF in ["HCI", "HCI_B", "HCI_J"]:  # solar equatorial coord. sys.
        pass
    elif RF in ["ECLIPJ2000", "ECLIPB1950"]:  # ecliptic coord. sys.
        t_epoch = datetime.datetime(
            2000, 1, 1, 12) if RF == "ECLIPJ2000" else datetime.datetime(
                2000, 1, 1, 12) + datetime.timedelta(days=(2433282.42345905 -
                                                           2451545.0))
        r_sph = hc_to_hg(r_sph, ang_ascnode=calc_delta(t_epoch))
        v_sph = hc_to_hg(v_sph, ang_ascnode=calc_delta(t_epoch))
    vR, vT, vN = hg_to_rtn(v_sph, r_sph)
    return (np.array([vR, vT, vN]))
Example #17
0
    def sensor_position(self):
        """
        Returns a tuple with information detailing the position of the sensor at the time
        of the image. Expects ephemeris_time to be defined. This must be a floating point number
        containing the ephemeris time. Expects spacecraft_name to be defined. This must be a
        string containing the name of the spacecraft containing the sensor. Expects
        reference_frame to be defined. This must be a sring containing the name of
        the target reference frame. Expects target_name to be defined. This must be
        a string containing the name of the target body.

        Returns
        -------
        : (positions, velocities, times)
          a tuple containing a list of positions, a list of velocities, and a list of times
        """
        if not hasattr(self, '_position'):
            ephem = self.ephemeris_time
            pos = []
            vel = []
            for time in ephem:
                state, _ = spice.spkezr(
                    self.spacecraft_name,
                    time,
                    self.reference_frame,
                    'NONE',
                    self.target_name,
                )
                pos.append(state[:3])
                vel.append(state[3:])
            # By default, spice works in km
            self._position = [p * 1000 for p in pos]
            self._velocity = [v * 1000 for v in vel]
        return self._position, self._velocity, self.ephemeris_time
Example #18
0
def force_function(t, y):

    # Get the derivative vector
    dy = np.zeros(y.shape)
    # Populate the first elements of dy with the last elements of y
    dy[:3] = y[3:]
    # Get the force on the s/c from Mercury
    dy[3:] -= MERCURY_MU / MERCURY_MU * y[:3] / (np.linalg.norm(y[:3])**3)
    # Get the force on the s/c d/t the Sun
    sun_vector = get_sun_vector(634798080 + t * time_scale)
    sun_vector /= MERCURY_RADIUS
    dy[3:] -= SUN_MU / MERCURY_MU * sun_vector[:3] / (np.linalg.norm(
        sun_vector[:3])**3)
    # Difference vector
    diff_vec = y - sun_vector
    dy[3:] -= SUN_MU / MERCURY_MU * diff_vec[:3] / (np.linalg.norm(
        diff_vec[:3])**3)

    for pname in ("Venus", "Jupiter barycenter", "Saturn barycenter"):
        p_vector, _ = spice.spkezr(pname, 634798080 + t * time_scale, "J2000",
                                   "NONE", "Mercury")
        p_vector /= MERCURY_RADIUS
        diff_vec = y - p_vector
        dy[3:] -= P_MUS[pname] / MERCURY_MU * (
            p_vector[:3] / (np.linalg.norm(p_vector[:3])**3) + diff_vec[:3] /
            (np.linalg.norm(diff_vec[:3])**3))

    return dy
Example #19
0
    def n_body_equation(self, t, y):
        """
        n体问题微分方程 y_dot = f(t, y)
        见英文版教材P117
        :self.PARAM t: 对应tdb时刻
        :self.PARAM y: 对应时刻人造卫星的状态向量[x, y, z, vx, vy, vz]
        :return: y_dot
        """
        y_dot = np.empty((6, ))
        y_dot[:3] = y[3:]

        r_mex = y[:3]
        r_central = spice.spkezr(self.PARAM["Central_Body"], t,
                                 self.PARAM["Ref_Frame"], 'None',
                                 self.PARAM["Ref_Body"])[0][:3]
        r_central_mex = r_mex - r_central
        GM_central = spice.bodvrd(self.PARAM["Central_Body"], "GM", 1)[1][0]
        a_central = -GM_central * r_central_mex / norm(r_central_mex)**3
        perturbations = sum([
            self.perturbation(name, r_mex, t)
            for name in self.PARAM["Perturbation_Bodies"]
        ])
        y_dot[3:] = a_central + perturbations

        if self.PARAM["Radiation"]:
            radiation_pressure = self.solar_radiation_pressure(t, y)
            y_dot[3:] += radiation_pressure

        return y_dot  # v, a
Example #20
0
    def solar_radiation_pressure(self, t, y):
        """
        计算太阳辐射光压 见英文版教材P79 3.75
        :self.PARAM t: TDB时刻
        :self.PARAM y: 对应时刻人造卫星的状态向量[x, y, z, vx, vy, vz]
        :return: 太阳辐射光压加速度
        """
        r_mex = y[:3]
        r_sun = spice.spkezr("Sun", t, self.PARAM["Ref_Frame"], 'None',
                             self.PARAM["Ref_Body"])[0][:3]
        r_sun_mex = r_mex - r_sun

        AU_km = spice.gdpool("AU", 0, 1)[0]
        v = min([
            self.body_shadow_function(r_mex, name, t)
            for name in self.PARAM["Occulting_Bodies"]
        ])
        CR, A, m = [
            self.PARAM[i] for i in ["CR", "Surface_Area", "Satellite_Mass"]
        ]
        a = v * self.P_Solar * CR * A / m * AU_km**2 * r_sun_mex / norm(
            r_sun_mex)**3

        return a * 1e-3  # km * s^-2
        pass
Example #21
0
    def sun_position(self):
        sun_state, _ = spice.spkezr("SUN", self.center_ephemeris_time,
                                    self.reference_frame, 'NONE',
                                    self.target_name)

        return [sun_state[:4].tolist()], [sun_state[3:6].tolist()
                                          ], [self.center_ephemeris_time]
def cassini_titan_altlatlon(tempdatetime):
    et = spice.datetime2et(tempdatetime)

    state, ltime = spice.spkezr('CASSINI', et, 'IAU_TITAN', 'NONE', 'TITAN')
    lon, lat, alt = spice.recpgr('TITAN', state[:3], spice.bodvrd('TITAN', 'RADII', 3)[1][0], 2.64e-4)

    return alt, lat * spice.dpr(), lon * spice.dpr()
Example #23
0
    def sun_velocity(self):
        sun_state, lt = spice.spkezr("SUN",
                                     self.center_ephemeris_time,
                                     self.reference_frame,
                                     'NONE',
                                     self.label['TARGET_NAME'])

        return [sun_state[3:6].tolist()]
Example #24
0
    def sun_position(self):
        sun_state, _ = spice.spkezr("SUN",
                                     self.center_ephemeris_time,
                                     self.reference_frame,
                                     'NONE',
                                     self.label['TARGET_NAME'])

        return [sun_state[:3].tolist()]
Example #25
0
 def __state_from_spice(self, x):
     '''
     Routine for actually getting the state vector from spice.
     '''
     spiceid, epoch = x
     ephemeris_time = self.__compute_ephemeris_time(epoch)
     state, _ = spice.spkezr(spiceid, ephemeris_time, self.frame, 'none',
                             self.origin)
     return state
Example #26
0
def test():
    print("which kernels would you like to load?")
    meta = input()
    KERNELS_TO_LOAD = ('meta.tm')
    print(spice.tkvrsn('TOOLKIT'))
    #help(spice.furnsh)
    spice.furnsh(KERNELS_TO_LOAD)
    print("Load successful")
    print("Enter time:")
    time = input()
    et = spice.str2et(time)
    #et=spice.str2et( '2012-12-03T12:23:52' )
    #help(spice.spkcpo)
    target0 = 'DAWN'
    frame0 = 'DAWN_SPACECRAFT'
    corrtn0 = 'NONE'
    observ0 = 'DAWN'
    state0 = [6]
    state0, ltime0 = spice.spkezr(target0, et, frame0, corrtn0, observ0)
    print("target:" + target0)
    print("positions:" + "\nx: " + str(state0[0]))
    print("y: " + str(state0[1]))
    print("z: " + str(state0[2]))
    target1 = 'VESTA'
    frame1 = 'DAWN_SPACECRAFT'
    corrtn1 = 'NONE'
    observ1 = 'DAWN'
    state1, ltime1 = spice.spkezr(target1, et, frame1, corrtn1, observ1)
    print("target:" + target1)
    print("positions:" + "\nx: " + str(state1[0]))
    print("y: " + str(state1[1]))
    print("z: " + str(state1[2]))

    mat = spice.pxform('DAWN_SPACECRAFT', 'IAU_VESTA', et)
    print("orientation")
    print(mat)
    fig = plt.figure(figsize=(9, 9))
    ax = fig.add_subplot(111, projection='3d')
    ax.text(state0[0], state0[1], state0[2], target0)
    ax.text(state1[0], state1[1], state1[2], target1)
    ax.set_xlabel('X axis')
    ax.set_ylabel('Y axis')
    ax.set_zlabel('Z axis')
    plt.show()
Example #27
0
def perturbation(name, r_mex, t):
    """
    计算摄动天体加速度
    :param name: 摄动天体名称
    :param r_mex: 人造卫星(MEX)的位置向量
    :param t: TDB时刻
    :return: 对应天体摄动加速度加速度[ax, ay, az]
    """
    r_body = spice.spkezr(name, t, PARAM["Ref_Frame"], 'None',
                          PARAM["Ref_Body"])[0][:3]
    r_cent = spice.spkezr(PARAM["Central_Body"], t, PARAM["Ref_Frame"], 'None',
                          PARAM["Ref_Body"])[0][:3]

    GM_body = spice.bodvrd(name, "GM", 1)[1][0]

    r_body_mex = r_mex - r_body
    r_cent_body = r_body - r_cent

    a = -GM_body * (r_cent_body / norm(r_cent_body)**3 +
                    r_body_mex / norm(r_body_mex)**3)
    return a
Example #28
0
def getsta(target, TIME, mode="LT+S", observer="SOLAR SYSTEM BARYCENTER"):
    # https://spiceypy.readthedocs.io/en/master/remote_sensing.html
    #
    # Local parameters
    #
    METAKR = 'getsta.tm'

    #
    # Load the kernels that this program requires.  We
    # will need a leapseconds kernel to convert input
    # UTC time strings into ET.  We also will need the
    # necessary SPK files with coverage for the bodies
    # in which we are interested.
    #
    spiceypy.furnsh(METAKR)

    #
    #Prompt the user for the input time string.
    #
    utctim = parseDate(TIME)

    #print( 'Converting UTC Time: {:s}'.format(utctim)  )

    #
    #Convert utctim to ET.
    #
    et = spiceypy.str2et(utctim)

    #print( '   ET seconds past J2000: {:16.3f}'.format(et) )

    #
    # Compute the apparent state of target as seem from
    # observer in the J2000 frame.  All of the ephemeris
    # readers return states in units of kilometers and
    # kilometers per second.
    #
    [state, ltime] = spiceypy.spkezr(target, et, 'J2000', mode, observer)

    #print( '   Apparent state of MARS BARYCENTER (4) as seen '
    #'from SSB (0) in the J2000\n'
    #'      frame (km, km/s):'              )

    #print( '      X = {:16.3f}'.format(state[0])       )
    #print( '      Y = {:16.3f}'.format(state[1])       )
    #print( '      Z = {:16.3f}'.format(state[2])       )
    #print( '     VX = {:16.3f}'.format(state[3])       )
    #print( '     VY = {:16.3f}'.format(state[4])       )
    #print( '     VZ = {:16.3f}'.format(state[5])       )

    spiceypy.unload(METAKR)

    return state
Example #29
0
    def body_shadow_function(self, r_mex, name, t):
        """
        计算阴影函数(shadow function)见英文教材P81 3.4.2
        :self.PARAM r_mex: 人造卫星(MEX)的位置向量
        :self.PARAM name: 遮挡天体的名称
        :self.PARAM t: TDB时刻
        :return: 阴影函数v
        """
        r_body = spice.spkezr(name, t, self.PARAM["Ref_Frame"], 'None',
                              self.PARAM["Ref_Body"])[0][:3]
        r_sun = spice.spkezr("Sun", t, self.PARAM["Ref_Frame"], 'None',
                             self.PARAM["Ref_Body"])[0][:3]
        r_body_mex = r_mex - r_body
        r_mex_sun = r_sun - r_mex

        R_body = spice.bodvrd(name, "RADII", 3)[1][0]
        RS = spice.bodvrd("Sun", "RADII", 3)[1][0]
        a = asin(RS / norm(r_mex_sun))
        b = asin(R_body / norm(r_body_mex))
        c = self.agl_between(-1 * r_body_mex, r_mex_sun)
        v = self.shadow_function(a, b, c)
        return v
Example #30
0
def ChgFrame(state, fromFrame, toFrame, dt):
    '''
    :param: state vector, as array of floats or numpy array.
    :param: fromFrame: spice frame name, as string
    :param: toFrame: spice frame name, as string
    :param: dt: float representing the date and time in J2000.
    :return: a numpy array
    '''
    _load_kernels_()
    if isinstance(dt, str):
        dt = spice.str2et(dt)

    dcm = DCM(fromFrame, toFrame, dt)
    position = dcm.dot(state[:3])
    velocity = dcm.dot(state[3:])
    stateRotd = np.array(list(position) + list(velocity))
    # Find the target body name
    if fromFrame.startswith('IAU_'):
        # From planetocentric to heliocentric
        target = fromFrame[4:]
        if target.lower() in BARYCENTER_FRAMES:
            # Switch to barycenter, as per https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/aareadme_de430-de431.txt
            target += '_barycenter'
        origin = spice.spkezr(target, dt, toFrame, 'None', 'Sun')[0]
        return stateRotd + origin

    elif fromFrame.endswith('J2000'):
        # From heliocentric to planetocentric
        # Works for EclipJ2000 and J2000
        target = toFrame[4:]
        if target.lower() in BARYCENTER_FRAMES:
            # Switch to barycenter, as per https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/aareadme_de430-de431.txt
            target += '_barycenter'
        origin = spice.spkezr(target, dt, fromFrame, 'None', 'Sun')[0]
        # Note that we perform the rotation because we asked for the origin in the *fromFrame* not in the *toFrame* like above.
        position = position - dcm.dot(origin[:3])
        velocity = velocity - dcm.dot(origin[3:])
        return np.array(list(position) + list(velocity))
Example #31
0
    def generate_positions(self, times, observing_body, frame,
                           abcorr=None):
        """
        Generate positions from a spice kernel.

        Parameters
        ----------
        times : time like
            An object that can be parsed by `~astropy.time.Time`.
        observing_body : str or int
            The observing body. Output position vectors are given relative to
            the position of this body. See
            https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/req/naif_ids.html
            for a list of bodies.
        frame : str
            The coordinate system to return the positions in. See
            https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/req/frames.html
            for a list of frames.
        abcorr : str, optional
            By default no aberration correciton is performed.
            See the documentaiton at
            https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/spkezr_c.html
            for allowable values and their effects.
        """
        times = time.Time(times)
        # Spice needs a funny set of times
        fmt = '%Y %b %d, %H:%M:%S'
        spice_times = [sp.str2et(time.strftime(fmt)) for time in times]
        self._abcorr = str(abcorr)

        # Do the calculation
        pos_vel, lightTimes = sp.spkezr(
            self.target.name, spice_times, frame, self._abcorr,
            observing_body)

        positions = np.array(pos_vel)[:, :3] * u.km
        velocities = np.array(pos_vel)[:, 3:] * u.km / u.s
        self.light_times=np.array(lightTimes)

        self._frame = frame
        self._times = time.Time(times)
        self._velocities = velocities
        self._x = positions[:, 0]
        self._y = positions[:, 1]
        self._z = positions[:, 2]
        self._vx = velocities[:, 0]
        self._vy = velocities[:, 1]
        self._vz = velocities[:, 2]
        self._generated = True
        self._observing_body = Body(observing_body)
Example #32
0
def calc_ephemeris(target, ets, frame, observer):
    '''
	Convenience wrapper for spkezr and spkgeo
	'''

    if type(target) == str:
        return array(spice.spkezr(target, ets, frame, 'NONE', observer)[0])

    else:
        n_states = len(ets)
        states = zeros((n_states, 6))
        for n in range(n_states):
            states[n] = spice.spkgeo(target, ets[n], frame, observer)[0]
        return states
Example #33
0
def ram_angles(time):
    """Return the angle between SC-Y and the ram direction (0. = Y to ram)"""
    p = spiceypy.spkezr('MAVEN', time, 'IAU_MARS', 'NONE', 'MARS')[0][3:]
    r = spiceypy.pxform('IAU_MARS', 'MAVEN_SPACECRAFT', time)
    a = spiceypy.mxv(r, p)

    e = np.arctan( np.sqrt(a[1]**2. + a[2]**2.) / a[0]) * 180./np.pi
    f = np.arctan( np.sqrt(a[0]**2. + a[2]**2.) / a[1]) * 180./np.pi
    g = np.arctan( np.sqrt(a[0]**2. + a[1]**2.) / a[2]) * 180./np.pi
    if e < 0.: e = e + 180.
    if f < 0.: f = f + 180.
    if g < 0.: g = g + 180.

    return np.array((e,f,g))
    def fetch_state(self, target, epoch):

        target = self.fromName(target)
        try:
            state, lt = spiceypy.spkezr(str(target), self.get_et(epoch),
                                        self.frame, self.correction,
                                        str(self.center))
        except spiceypy.utils.exceptions.SpiceSPKINSUFFDATA:
            print(
                f"Insufficient SPICE data exception was raised for {target} at {epoch}",
                flush=True)
            state = [None] * 6

        return {k: v for k, v in zip(SpiceProvider.STATE_COLUMNS, state)}
Example #35
0
sp.furnsh('kernels_kem_ats.tm')

utc_start = "2019 1 Jan 06:40:00"
utc_end   = "2019 1 Jan 07:20:00"

et_start = sp.utc2et(utc_start)
et_end   = sp.utc2et(utc_end)

et_mid = (et_start + et_end)/2

num_dt = 500

et = hbt.frange(et_start, et_end, num_dt)

name_target = 'MU69'
name_observer = 'New Horizons'

dist   = np.zeros(num_dt)
phase  = np.zeros(num_dt)

for i,et_i in enumerate(et):
    (state,_) = sp.spkezr(name_target, et_i, 'J2000', 'LT+S', name_observer)
    dist[i] = np.sqrt(np.sum(state[0:3]**2))
    
plt.plot(et - et_mid, dist)
plt.xlabel('Dist [km]')
plt.ylabel('Seconds from c/a')
plt.show()    
    
print("dist[0] = {}".format(dist))
            print(f'{i} {stack.t["filename"][i]}')
            # plt.imshow(stretch(stack.image_single(i)), origin='lower')
            # plt.title(f"i={i}, et = {stack.t['et'][i]}")
        plt.show()
#%%%
        
    # Align the stack.
    # The values for the shifts (in pixels) are calculated based 
    # on putting MU69's ra/dec in the center of each frame.
    # This assumes that MU69 does not move within the stack itself.
    
    # ra_mu69  = 274.73344   # Value for 2-Nov-2018. Matches plot_img_wcs.py
    # dec_mu69 = -20.86170
    
    et = stack.t['et'][0]
    (st, _) = sp.spkezr('MU69', et, 'J2000', 'LT', 'New Horizons')
    (_, ra, dec) = sp.recrad(st[0:3])
    radec_mu69 = (ra, dec)
    
    stack.align(method = 'WCS', center = radec_mu69)  # Pass position in radians

    
#%%%    
    # Flatten the stack
    
    stack.padding = 65
    zoom = 4
    
    self = stack
    
    # Do the flattening
def nh_ort_find_ring_pole():
    
    file_superstack = '/Users/throop/Data/ORT4/superstack_ORT4_z4_mean_wcs_sm_hbt.fits'
    
    file_tm = 'kernels_kem_prime.tm'
    sp.unload(file_tm)
    sp.furnsh(file_tm)
    
    f = fits.open(file_superstack)
    
    img = f[0].data
    
#    plt.imshow(stretch(img))
#    plt.show()
    
    wcs = WCS(file) 
    
    num_pts = 200
    
    ra_pole   = 275 * hbt.d2r
#    dec_pole  = -56 * hbt.d2r
    dec_pole  = 13 * hbt.d2r

    radius_ring = 9000  # Radius in km

    vec_pole_j2k = sp.radrec(1, ra_pole, dec_pole)
    
    et = float(f[0].header['SPCSCET'])
    utc = sp.et2utc(et, 'C', 0)
    
    # Get position from NH to UT
    
    (st, lt) = sp.spkezr('MU69', et, 'J2000', 'LT', 'New Horizons')

    vec_nh_ut = st[0:3]
 
     # Get position from Sun, to NH
    
    (st, lt) = sp.spkezr('New Horizons', et, 'J2000', 'LT', 'Sun')
    
    vec_sun_nh = st[0:3]

    vec_sun_ut = vec_sun_nh + vec_nh_ut
    
    # Define a 'ring plane', based on a pole vector, and a point
    # This ring plane should be in J2K space -- that is, centered on Sun.
    
    plane_ring = sp.nvp2pl(vec_pole_j2k, vec_sun_ut)  # Pole position is variable. Point is UT in J2K.
 
    # Get the point and spanning vectors that define this plane
    
    # XXX for some reason, these values from pl2psv do not depend on value of vec_pol_j2k
    
    (pt_pl, vec1_pl, vec2_pl) = sp.pl2psv(plane_ring)

    # Now take a bunch of linear combinations of these spanning vectors
    
    # Plot UT's position on the plot
    
    (st, lt) = sp.spkezr('MU69', et, 'J2000', 'LT', 'New Horizons')

    (_, ra, dec) = sp.recrad(vec_nh_ut)
        
    (x, y) = wcs.wcs_world2pix(ra*hbt.r2d, dec*hbt.r2d, 0)
       
#    plt.plot(x, y, marker = 'o', ms = 10, alpha=0.3, color='purple')
    
    # Set an offset to the WCS values, in case UT is not in the right position (ie, not centered properly)
    
    dy = 0 # Large value moves up
    dx = 0

    # Draw the ring image
    
    plt.imshow(stretch(img), origin='lower')
    
    # Calculate and draw all of the ring points
        
    for i in range(num_pts):
        
        angle_azimuth = 2*math.pi * (i / num_pts)   # Put in range 0 .. 2 pi
        vec_i = vec1_pl * math.sin(angle_azimuth) + vec2_pl * math.cos(angle_azimuth)
        vec_i = vec_i * radius_ring
        
        # Now get the point in space, J2K
        
        pt_ring_i_j2k = vec_i + vec_sun_ut
        
        vec_sun_ring_i = pt_ring_i_j2k
        
        vec_nh_ring_i = vec_sun_ring_i- vec_sun_nh
        
        # 
        (_, ra_i, dec_i) = sp.recrad(vec_nh_ring_i)
        
        (x, y) = wcs.wcs_world2pix(ra_i*hbt.r2d, dec_i*hbt.r2d, 0)
        
        plt.plot(x+dx, y+dy, marker = 'o', ms = 1, color='red', alpha = 0.15)
#        print(f'{i}, {ra_i*hbt.r2d}, {dec_i*hbt.r2d}, {x}, {y}')
    
    plt.title(f'ORT4 Superstack, Ring Pole = ({ra_pole*hbt.r2d},{dec_pole*hbt.r2d}) deg')    
    plt.show()
    
    return
Example #38
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()
Example #39
0
    def align(self, method = 'wcs', center = None, body = None):
        """
        Take a loaded stack, and set the shift amounts for each image.
        
        Does not actually perform the shifts, but updates internal shift settings
        for each image. The shifting only happens when image is flattened.
        
        Updates the internal shift_x_pix, shift_y_pix, accessible thru self.t[<number>]['shift_pix_x'].
        These pixel shifts are raw, and do not reflect any zoom.
        
        Optional arguments
        ----
        method:
            'wcs' : Center based on full WCS object. This should usually be used.

            'wcs_simple' : Center based on RA / Dec positions in WCS. Does not use full WCS -- just CRVAL.
            
            'body'  : Center based on a given body name. Not yet implemented.
            
        center:
            Tuple or string specifying the center to use. For instance, center = (ra_radians, dec_radians)
            
        """
        
        if (method.upper() == 'WCS_SIMPLE'):  # Do not use this!
            
            # If 'radec' is passed, then set the shifts s.t. the right amount is applied to put 
            # specified RA/Dec in the center.
            
            # Extract the ra and dec in radians. These specify the desired RA and dec.
            
            (ra, dec) = center
                        
            shift_x_pix = self.t['shift_x_pix']
            shift_y_pix = self.t['shift_y_pix']
            
            ra_center   = self.t['ra_center']   # RA of each individual image
            dec_center  = self.t['dec_center']
            
            num_images = self.size[0]
            
            for i in range(num_images):
                shift_x_pix[i] = (ra_center[i]  -  ra)/self.pixscale_x
                shift_y_pix[i] = (dec_center[i] - dec)/self.pixscale_y
            
            # Save these values back to the table, where they live
            
            self.t['shift_pix_x'] = shift_x_pix
            self.t['shift_pix_y'] = shift_y_pix
       
        if (method.upper() == 'WCS'):  # This is the normal method
            
            # If 'WCS' is passed, do the same as 'radec', but use the whole WCS object explicitly.
            # This should give very nearly the same results.
            
            (ra, dec) = center
            
            num_images = self.size[0]
            
            shift_x_pix = self.t['shift_x_pix']
            shift_y_pix = self.t['shift_y_pix']
            
            for i in range(num_images):
                wcs = self.t['wcs'][i]  # Get WCS for this image
                                
                (pos_pix_x, pos_pix_y) = wcs.wcs_world2pix(ra*hbt.r2d, dec*hbt.r2d, 0)
                
                shift_x_pix[i] = self.dx_pix/2 - pos_pix_x
                shift_y_pix[i] = self.dy_pix/2 - pos_pix_y
           
            # Save these values back to the table, where they live
            
            self.t['shift_pix_x'] = shift_x_pix
            self.t['shift_pix_y'] = shift_y_pix

        if (method.upper() == 'BODY'):    
            
            num_images = self.size[0]

            shift_x_pix = self.t['shift_x_pix']
            shift_y_pix = self.t['shift_y_pix']

            for i in range(num_images):
                
                hdu = fits.open(self.t['filename'][i])
                et_i = hdu['PRIMARY'].header['SPCSCET']
                hdu.close()
                self.t['et'][i] = et_i  # This was badly populated -- here we fix it
                abcorr = 'LT'
                frame = 'J2000'
                name_observer = 'New Horizons'
                (st,lt) = sp.spkezr(body, et_i, frame, abcorr, name_observer)
                vec_obs_targ = st[0:3]
                (_, ra, dec) = sp.recrad(vec_obs_targ)  # get ra dec in radians
                # print(f'For image {}')
            
                wcs = self.t['wcs'][i]  # Get WCS for this image
                (pos_pix_x, pos_pix_y) = wcs.wcs_world2pix(ra*hbt.r2d, dec*hbt.r2d, 0)  # degrees → pixels
                
                shift_x_pix[i] = self.dx_pix/2 - pos_pix_x
                shift_y_pix[i] = self.dy_pix/2 - pos_pix_y

            # Save these values back to the table, where they live
            
            self.t['shift_pix_x'] = shift_x_pix
            self.t['shift_pix_y'] = shift_y_pix
# Set up the times for each exposure
#==============================================================================

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))
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))
    
fwhm = 4   # Works well for MVIC framing

hdulist = fits.open(file)

im = hdulist['PRIMARY'].data
w = WCS(file)                  # Look up the WCS coordinates for this frame
center  = w.wcs.crval  # degrees

et = hdulist['PRIMARY'].header['SPCSCET']

file_tm = '/Users/throop/gv/dev/gv_kernels_new_horizons.txt'

sp.furnsh(file_tm)

(st_sun,lt) = sp.spkezr('Sun', et, 'J2000', 'LT', 'New Horizons')
(st_pluto,lt) = sp.spkezr('Pluto', et, 'J2000', 'LT', 'New Horizons')

ang_sep = sp.vsep(st_sun[0:3], st_pluto[0:3])
ang_phase_deg = (math.pi - ang_sep)*hbt.r2d

# Generate the pickle filename

file_stars_pkl = dir_out + sequence + '_stars_deg' + repr(radius) + '.pkl'

#==============================================================================
# Get stars from a star catalog
#==============================================================================

try:
    lun = open(file_stars_pkl, 'rb')
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()
def get_fits_info_from_files_lorri(path,
                            file_tm = "/Users/throop/gv/dev/gv_kernels_new_horizons.txt", pattern=''):
    "Populate an astropy table with info from the headers of a list of LORRI files."
    import numpy as np
    import spiceypy as sp
    import glob
    import astropy
    from astropy.io import fits
    from astropy.table import Table
    import astropy.table
    import math
    import hbt
    

# For testing:
# file = '/Users/throop/Data/NH_Jring/data/jupiter/level2/lor/all/lor_0035020322_0x630_sci_1.fit' # 119 deg phase as per gv
# file = '/Users/throop/Data/NH_Jring/data/jupiter/level2/lor/all/lor_0034599122_0x630_sci_1.fit' # 7 deg phase, inbound

# t = hbt.get_fits_info_from_files_lorri(file)

# Flags: Do we do all of the files? Or just a truncated subset of them, for testing purposes?
    
    DO_TRUNCATED = False
    NUM_TRUNC = 100

# We should work to standardize this, perhaps allowing different versions of this function 
# for different instruments.

    d2r = np.pi /180.
    r2d = 1. / d2r

    sp.furnsh(file_tm)

# *** If path ends with .fit or .fits, then it is a file not a path. Don't expand it, but read it as a single file.

    if (('.fits' in path) or ('.fit' in path)):
        file_list = path
        files = [file_list]

    else:
        
        dir_data = path          
    #dir_data = '/Users/throop/data/NH_Jring/data/jupiter/level2/lor/all'
    # Start up SPICE
    
    
    # Get the full list of files
    # List only the files that match an (optional) user-supplied pattern, such as '_opnav'
    
        file_list = glob.glob(dir_data + '/*' + pattern + '.fit')
        files = np.array(file_list)
        indices = np.argsort(file_list)
        files = files[indices]

# Read the JD from each file. Then sort the files based on JD.

    jd = []
    for file in files:
        hdulist = fits.open(file)
        jd.append(hdulist[0].header['MET'])
        hdulist.close()
         
    fits_met     = [] # new list (same as array) 
    fits_startmet= [] 
    fits_stopmet = []
    fits_exptime = [] # starting time of exposure
    fits_target  = [] 
    fits_reqdesc = []     
    fits_reqcomm = [] # New 9-Oct-2018
    fits_reqid   = [] # New 9-Oct-2018
    fits_spcinst0= [] 
    fits_spcutcjd= []   
    fits_naxis1=   [] 
    fits_naxis2 =  []
    fits_sformat = [] # Data format -- '1x1' or '4x4'
    fits_spctscx = [] # sc - target, dx 
    fits_spctscy = [] # dy
    fits_spctscz = [] # dz
    fits_spctcb  = [] # target name
    fits_spctnaz = [] # Pole angle between target and instrument (i.e., boresight rotation angle)
    fits_rsolar  = [] # (DN/s)/(erg/cm^2/s/Ang/sr), Solar spectrum. Use for resolved sources.
    
    if (DO_TRUNCATED):
        files = files[0:NUM_TRUNC]
        
#files_short = np.array(files)
#for i in range(files.size):
#    files_short = files[i].split('/')[-1]  # Get just the filename itself

# Set up one iteration variable so we don't need to create it over and over
    num_obs = np.size(files)
    i_obs = np.arange(num_obs)
    
    print("Read " + repr(np.size(files)) + " files.")
    
    for file in files:
        print("Reading file " + file)
    
        hdulist = fits.open(file)
        header = hdulist[0].header
        
        keys = header.keys()
    
        fits_met.append(header['MET'])
        fits_exptime.append(header['EXPTIME'])
        fits_startmet.append(header['STARTMET'])
        fits_stopmet.append(header['STOPMET'])
        fits_target.append(header['TARGET'])
        fits_reqdesc.append(header['REQDESC'])
        fits_reqcomm.append(header['REQCOMM'])
        fits_reqid.append(header['REQID'])
        fits_spcinst0.append(header['SPCINST0'])
        fits_spcutcjd.append( (header['SPCUTCJD'])[3:]) # Remove the 'JD ' from before number
        fits_naxis1.append(header['NAXIS1'])
        fits_naxis2.append(header['NAXIS2'])
        fits_spctscx.append(header['SPCTSCX'])
        fits_spctscy.append(header['SPCTSCY'])
        fits_spctscz.append(header['SPCTSCZ'])    
        fits_spctnaz.append(header['SPCTNAZ'])    
        fits_sformat.append(header['SFORMAT'])
        fits_rsolar.append(header['RSOLAR'])   # NB: This will be in the level-2 FITS, but not level 1
                                             
        hdulist.close() # Close the FITS file

#print object
#print "done"

# Calculate distance to Jupiter in each of these
# Calc phase angle (to Jupiter)
# Eventually build backplanes: phase, RA/Dec, etc.
# Eventually Superimpose a ring on top of these
#  ** Not too hard. I already have a routine to create RA/Dec of ring borders.
# Eventually overlay stars 
#   Q: Will there be enough there?
# Eventually repoint based on stars
#  ** Before I allow repointing, I should search a star catalog and plot them.

# Convert some things to numpy arrays. Is there any disadvantage to this?

    met        = np.array(fits_met)
    jd         = np.array(fits_spcutcjd, dtype='d') # 'f' was rounding to one decimal place...
    naxis1     = np.array(fits_naxis1)
    naxis2     = np.array(fits_naxis2)
    target     = np.array(fits_target) # np.array can use string arrays as easily as float arrays
    instrument = np.array(fits_spcinst0)
    dx_targ    = np.array(fits_spctscx)
    dy_targ    = np.array(fits_spctscy)
    dz_targ    = np.array(fits_spctscz)
    desc       = np.array(fits_reqdesc)
    reqid      = np.array(fits_reqid)
    reqcomm    = np.array(fits_reqcomm)
    met0       = np.array(fits_startmet)
    met1       = np.array(fits_stopmet)
    exptime    = np.array(fits_exptime)
    rotation   = np.array(fits_spctnaz)
    sformat    = np.array(fits_sformat)
    rotation   = np.rint(rotation).astype(int)  # Turn rotation into integer. I only want this to be 0, 90, 180, 270... 
    rsolar     = np.array(fits_rsolar)
    
    files_short = np.zeros(num_obs, dtype = 'U60')

# Now do some geometric calculations and create new values for a few fields

    dist_targ = np.sqrt(dx_targ**2 + dy_targ**2 + dz_targ**2)

    phase = np.zeros(num_obs)
    utc = np.zeros(num_obs, dtype = 'U30')
    et = np.zeros(num_obs)
    subsclat = np.zeros(num_obs) # Sub-sc latitude
    subsclon = np.zeros(num_obs) # Sub-sc longitude
    
    name_observer = 'New Horizons'
    frame = 'J2000'
    abcorr = 'LT+S'
#         Note that using light time corrections alone ("LT") is 
#         generally not a good way to obtain an approximation to an 
#         apparent target vector:  since light time and stellar 
#         aberration corrections often partially cancel each other, 
#         it may be more accurate to use no correction at all than to 
#         use light time alone. 

# Fix the MET. The 'MET' field in fits header is actually not the midtime, but the time of the first packet.
# I am going to replace it with the midtime.
# *** No, don't do that. The actual MET field is used for timestamping -- keep it as integer.

#    met = (met0 + met1) / 2.

# Loop over all images

    for i in i_obs:
    
# Get the ET and UTC, from the JD. These are all times *on s/c*, which is what we want
    
      et[i] = sp.utc2et('JD ' + repr(jd[i]))
      utc[i] = sp.et2utc(et[i], 'C', 2)
    
# Calculate Sun-Jupiter-NH phase angle for each image 
    
      (st_jup_sc, ltime) = sp.spkezr('Jupiter', et[i], frame, abcorr, 'New Horizons') #obs, targ
      (st_sun_jup, ltime) = sp.spkezr('Sun', et[i], frame, abcorr, 'Jupiter')
      ang_scat = sp.vsep(st_sun_jup[0:3], st_jup_sc[0:3])
      phase[i] = math.pi - ang_scat
#      phase[i] = ang_scat
      files_short[i] = files[i].split('/')[-1]
# Calc sub-sc lon/lat
      
      mx = sp.pxform(frame,'IAU_JUPITER', et[i])
      st_jup_sc_iau_jup = sp.mxv(mx, st_jup_sc[0:3])
      
      (radius,subsclon[i],subsclat[i]) = sp.reclat(st_jup_sc[0:3])  # Radians
      (radius,subsclon[i],subsclat[i]) = sp.reclat(st_jup_sc_iau_jup)  # Radians

# Stuff all of these into a Table

    t = Table([i_obs, met, utc, et, jd, files, files_short, naxis1, naxis2, target, instrument, 
               dx_targ, dy_targ, dz_targ, reqid, 
               met0, met1, exptime, phase, subsclat, subsclon, naxis1, 
               naxis2, rotation, sformat, rsolar, desc, reqcomm], 
               
               names = ('#', 'MET', 'UTC', 'ET', 'JD', 'Filename', 'Shortname', 'N1', 'N2', 'Target', 'Inst', 
                        'dx', 'dy', 'dz', 'ReqID',
                        'MET Start', 'MET End', 'Exptime', 'Phase', 'Sub-SC Lat', 'Sub-SC Lon', 'dx_pix', 
                        'dy_pix', 'Rotation', 'Format', 'RSolar', 'Desc', 'Comment'))
    
# Define units for a few of the columns
                        
    t['Exptime'].unit = 's'
    t['Sub-SC Lat'].unit = 'degrees'

# Create a dxyz_targ column, from dx dy dz. Easy!

    t['dxyz'] = np.sqrt(t['dx']**2 + t['dy']**2 + t['dz']**2)  # Distance, in km

    return t
EPHEMERIDES_FILE = "data/de432s.bsp"


if __name__ == "__main__":
    # Script expects first argument to be a UTC timestamp
    if len(sys.argv) > 1:
        input_utc = sys.argv[1]
    else:
        input_utc = "2000-01-01T00:00:00"  # default for testing
    # Load the necessary data files
    sp.furnsh([LEAP_SECOND_FILE, EPHEMERIDES_FILE])
    # Convert UTC to ephemeris time
    epoch = sp.utc2et(input_utc)
    # State (position and velocity in cartesian coordinates)
    # of EARTH as seen from SUN in the ecliptic J2000 coordinate frame.
    state, lt, = sp.spkezr("EARTH", epoch, "ECLIPJ2000", "NONE", "SUN")
    # Show the output
    print("Input time = {}".format(input_utc))
    print("")
    print("# Position of the Earth in the heliocentric ecliptic (J2000) frame")
    print("X = {} km".format(state[0]))
    print("Y = {} km".format(state[1]))
    print("Z = {} km".format(state[2]))
    print("")
    print("# Velocity of the Earth in the same frame")
    print("dX = {} km/s".format(state[3]))
    print("dY = {} km/s".format(state[4]))
    print("dZ = {} km/s".format(state[5]))
    # As a sanity check, print the speed
    speed = (state[3]**2 + state[4]**2 + state[5]**2)**.5
    print("")
def plot_backplanes_fits(file):

    """
    This file takes an image, and plots all of the backplanes for it.
    """

    # Start up SPICE
    
    file_kernel = '/Users/throop/git/NH_rings/kernels_kem.tm'
    sp.furnsh(file_kernel)

    hdulist = fits.open(file)

    # Loop over all of the planes, and plot each one
    
    i=1
    fig = plt.subplots()
    for hdu in hdulist:
        plt.subplot(3,4,i)
        plt.imshow(hdu.data)
        plt.title("{} / {}".format(i-1, hdu.name))
        i+=1

    plt.show()

    stretch_percent = 90    
    stretch = astropy.visualization.PercentileInterval(stretch_percent)
        
    # Look up position of MU69 in pixels.

    et = hdu[0].header['SPCSCET']
    utc = sp.et2utc(et, 'C', 0)
    abcorr = 'LT'
    frame = 'J2000'
    name_target = 'MU69'
    name_observer = 'New Horizons'
    w = WCS(file_new)
    
    (st,lt) = sp.spkezr(name_target, et, frame, abcorr, name_observer)
    vec_obs_mu69 = st[0:3]
    (_, ra, dec) = sp.recrad(vec_obs_mu69)
    (pos_pix_x, pos_pix_y) = w.wcs_world2pix(ra*hbt.r2d, dec*hbt.r2d, 0)
    
#    Plot the image itself
 
    hbt.figsize((10,10)) 
    plt.imshow(stretch(hdu[0].data))

    # Plot one of the planes

    plt.imshow(stretch(hdu['Longitude_eq'].data), alpha=0.5, cmap=plt.cm.Reds_r)

    # Plot the ring
 
    radius_ring = 100_000  # This needs to be adjusted for different distances.
    radius_arr = hdu['Radius_eq'].data
    radius_good = np.logical_and(radius_arr > radius_ring*0.95, radius_arr < radius_ring*1.05)
    plt.imshow(radius_good, alpha=0.3)
    
    # Plot MU69
    
    plt.plot(pos_pix_x, pos_pix_y, ms=10, marker = 'o', color='green')    
    plt.title("{}, {}".format(os.path.basename(file_new), utc))
    plt.show()  

    # Close the file
    
    hdu.close()
Example #47
0
def plot_img_wcs(img, wcs, ra=274.73344, dec=-20.86170, markersize=5, alpha=0.5, 
                 color='red', title=None, do_zoom_center = False, do_show=True,
                 width=None, name_observer='New Horizons', name_target = 'MU69', 
                 et = None, do_stretch=True,
                 do_inhibit_axes=False,
                 do_colorbar=False,
                 do_plot_fiducial=True,
                 label_axis=None,
                 # vmin=None, vmax=None,
                 scale_km=None,
                 **kwargs):
 

    # Default RA, Dec above is for MU69 on approach
    # (274.73344, -20.86170) = 2-Nov-2018. But it changes v slowly, so should be OK.
    
    import matplotlib.pyplot as plt # pyplot
    import astropy.visualization
    import numpy as np

    """
    Just plots an image, and uses WCS to plot a specific RA/Dec on it.
    By default, the RA/Dec is the inbound asymptote for NH MU69. 
    This RA/Dec should be good to within 1 pixel for 15-Aug .. 15-Dec 2018.
    
    This is a one-off func; not in a class.
    
    **kwargs are passed to imshow(), not to plot().
    
    Parameters
    -----
    
    do_zoom_center:
        Flag. Zoom in on just the center region, or show the entire plot.
        
    width:
        If set, crop the plot to the specified width in pixels, centered at the plot center.
        Plot is assumed to be square (height=width).
        If not set, the entire plot is shown.
    
    do_show:
        If False, suppress the final plt.show(). This allows other planes to be plotted on top later if requested.
    
    do_stretch:
        [Default True]. Boolean. If set, apply a stretch to the outuput image before plotting.
    
    do_inhibit_axes:
        Boolean. If set, do not print the x and y values on the respective axes.
    
    do_plot_fiducial:
        Boolean. If set, plot little red dots on the edges, showing the original image center location.
        
    ra: 
        RA to center on [degrees]
        
    dec:
        Declination to center on [degrees]
        
    name_target:
    name_observer:
    et:
        If *all* of these parameters are supplied, then the RA and Dec are taken from a call to SPKEZR,
        rather than from the supplied RA/Dec.
    
    scale_km:
        pixel scale in km/pix. If this is passed, then xy axes are labeled in km from center.
        If this is 'None', then xy axes are labeled in pixels.

    label_axis:
        Text to label the axes with (e.g., 'pixels' or 'km from MU69'). If None, then no label is plotted.
        
    """

    stretch_percent = 90    
    stretch = astropy.visualization.PercentileInterval(stretch_percent) # PI(90) scales to 5th..95th %ile.


    
    # Set the width and height appropriately
    
    if not width:                        # If no width passed, then just use the default.
        width = np.shape(img)[0]
        
    hw = int(width/2)
    ycenter = int((np.shape(img)[0])/2)  # Vertical
    xcenter = int((np.shape(img)[0])/2)  # Horizontal
    
    x0 = xcenter-hw  # center positions, in pixels
    x1 = xcenter+hw
    y0 = ycenter-hw
    y1 = ycenter+hw

    # Crop the image if needed

    img_crop = img[x0:x1, y0:y1]

    ycenter_crop = int((np.shape(img_crop)[0])/2)  # Vertical
    xcenter_crop = int((np.shape(img_crop)[0])/2)  # Horizontal
    
    # Stretch the image vertically. Must do this after the crop, not before!
    
    vmax = np.percentile(img_crop, 95)
    vmin = np.percentile(img_crop,  5)    

    img_crop_stretch = np.clip(img_crop, a_min=vmin, a_max=vmax)

    # If requested, calculate the x and y axes, in km
    
    extent = None # Set ehe default value
    
    size = np.shape(img_crop_stretch)

    if scale_km is not None:
        extent = [-size[0]/2 * scale_km, size[0]/2 * scale_km,
                  -size[1]/2 * scale_km, size[1]/2 * scale_km]    
        # print(f'extent = {extent}')
        
    else:
       extent = [0, size[0], 0, size[1] ]

    # Do the actual imshow()
    
    if not(do_stretch):    
        fig = plt.imshow(img_crop, origin='lower', extent=extent, **kwargs)
    else:
        fig = plt.imshow(img_crop_stretch, origin='lower', extent=extent, **kwargs)
    
    # If requested, inhibit printing values on the x and y axes
    
    if do_inhibit_axes:
        plt.gca().get_xaxis().set_visible(False)
        plt.gca().get_yaxis().set_visible(False)
        
    # If requested, use SPICE to determine the position to plot
    
    if name_target and name_observer and et:
        abcorr = 'LT'
        frame = 'J2000'
        (st, lt) = sp.spkezr(name_target, et, frame, abcorr, name_observer)
        vec = st[0:3]
        (_, ra, dec) = sp.recrad(vec)
        ra *= hbt.r2d
        dec *= hbt.r2d
        
    (x, y) = wcs.wcs_world2pix(ra, dec, 0)    # The '0' is the 'origin': 1 for FITS, where [1,1] is origin.
                                              #                          0 for numpy, where [0,0] is origin.
                                              # (Though if I go into DS9, it says that LL pixel is 0,0.)
                                              # I assume that I should use 0 here

# Definition of 'origin', which is third argument of wcs_world2pix:  
#  "Here, origin is the coordinate in the upper left corner of the image. In FITS and Fortran standards, 
#   this is 1. In Numpy and C standards this is 0." 

    # print(f'Position xy: {x}, {y}')
    
    
    # plt.xlim((xcenter-hw, xcenter+hw))
    # plt.ylim((ycenter-hw, ycenter+hw))
    
    # Make some markers on the edge, so we can tell if MU69 is actually properly centered, or not.
    
    if do_plot_fiducial:
        if scale_km is not None:
            # Mark center
            # plt.plot(x, y, marker = 'o', markersize = markersize, alpha=alpha, color=color)  
            # Mark four edges
            plt.plot([0*scale_km, 0*scale_km, hw*scale_km, -hw*scale_km], [-hw*scale_km, hw*scale_km, 0,0], 
                 marker = 'o', color='red', ls='None')
        else:
            # Mark center
            # Mark four edges
            # plt.plot(0, 0, marker = 'o', markersize = markersize, alpha=alpha, color=color)              
            plt.plot([ycenter_crop, ycenter_crop, ycenter_crop+hw, ycenter_crop-hw], 
                     [xcenter_crop-hw, xcenter_crop+hw, xcenter_crop, xcenter_crop], 
                 marker = 'o', color='red', ls='None')

    # Label axes, if requested
    if label_axis is not None:
        # if scale_km is not None:        
            # text = 'km'
        # else:
            # text = 'pix'    
        plt.xlabel(label_axis)
        plt.ylabel(label_axis)
            
    plt.title(title)

    # Plot a colorbar, if requested
    
    if do_colorbar:
        plt.colorbar(fig, format='%.0e')
        
    # Display the whole thing
    
    if do_show:
        plt.show()
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)
Example #49
0
# ######################################################################
# SPICE RELATED ROUTINES
# ######################################################################
# Ephemeris time: et=sp.str2et("01/01/2015 00:00:00.000 UTC")
etini=sp.str2et("01/01/%s 00:00:00.000 UTC"%yini)
jdini=et2jd(etini)
etend=sp.str2et("01/01/%s 00:00:00.000 UTC"%yend)
print "Number of days:",(etend-etini)/DAY
det=1.0*DAY/6
det=1*HOUR

astronomy=[]
et=etini
while True:
    # STATE VECTOR MOON AND SUN
    rmoon=sp.spkezr("MOON",et,"J2000","NONE","399")
    rsun=sp.spkezr("SUN",et,"J2000","NONE","399")
    Rm,vm=normX(rmoon)
    Rs,vs=normX(rsun)
    V=Dmoon*(Rmoon/Rm)**3+Dsun*(Rsun/Rs)**3
    # JULIAN DAY
    jd=et2jd(et)
    astronomy+=[[jd,Rm/Rmoon,Rs/Rsun,V]]
    if et>etend:break
    et+=det

astronomy=numpy.array(astronomy)
numpy.savetxt("astronomy-cycles-%s_%s.data"%(yini,yend),astronomy)

jds=astronomy[:,0]
Rms=astronomy[:,1]
#pos = (300, 700)
pos = (100, 200)  # y, x in normal imshow() coordinates.
#dist_target = 0.01*u.au
dist_solar  = 43.2*u.au  # MU69 dist at encounter: 43.2 AU, from KEM Wiki page 
do_psf = True            # Flag: Do we convolve result with NH LORRI PSF?26.7 + 0.65

dt_obs = -22*u.day        # Time relative to MU69 C/A

utc_ca = '2019 1 Jan 05:33:00'
et_ca  = sp.utc2et(utc_ca)
et_obs = et_ca + dt_obs.to('s').value
 
utc_obs = sp.et2utc(et_obs, 'C', 0)
utc_obs_human = 'K{:+}d'.format(dt_obs.to('day').value)

vec,lt = sp.spkezr('2014 MU69', et_obs, 'J2000', 'LT', 'New Horizons')
vec_sc_targ = vec[0:3]
dist_target = np.sqrt(np.sum(vec_sc_targ**2))*u.km.to('AU')*u.au
            
arr = nh_make_simulated_image_lorri(do_ring=True, 
                                    dist_ring_smoothing = 1000*u.km, 
                                    iof_ring = iof_ring,
                                    a_ring = (5000*u.km, 10000*u.km), 
                                    exptime = exptime, 
                                    mode = mode, 
                                    pos = pos,
                                    dist_solar = dist_solar, 
                                    dist_target = dist_target,
                                    do_mu69 = False,
                                    do_psf = True)
 
     # Get the RA / Dec for the four corner points
     
     if t_i['Format'] == '1X1':
         radec_corners = w.wcs_pix2world([[0,0],[0,1024],[1023,1023],[1023,0],[0,0]],0) * hbt.d2r  # Radians
         radec_center  = w.wcs_pix2world([[511,511]],0) * hbt.d2r                       # Radians
         
         arr *= 16  # For vertical scaling, all of the 1x1's must be scaled up by ~16x to match 4x4's.
         
     if t_i['Format'] == '4X4':
         radec_corners = w.wcs_pix2world([[0,0],[0,256],[256,256],[256,0],[0,0]],0) * hbt.d2r  # Radians
         radec_center  = w.wcs_pix2world([[128,128]],0) * hbt.d2r                       # Radians
 
     # Get RA / Dec for Jupiter
     
     (vec,lt) = sp.spkezr('Jupiter', t_i['ET'], 'J2000', abcorr, 'New Horizons')
     (_, ra_jup, dec_jup) = sp.recrad(vec[0:3])  # Radians
     vec_nh_jup = vec[0:3]
     
     # Get vectors Sun-Jup, and Sun-NH
     
     (vec,lt) = sp.spkezr('Jupiter', t_i['ET'], 'J2000', abcorr, 'Sun')
     vec_sun_jup = vec[0:3]  # Radians
     
     vec_sun_nh = vec_sun_jup - vec_nh_jup
     
     # Plot the boxes
     
     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
             
# Calculate the ring location
#==============================================================================

# Get an array of points along the ring

ra_ring1, dec_ring1 = hbt.get_pos_ring(et, name_body='Jupiter', radius=122000, units='radec', wcs=w)
ra_ring2, dec_ring2 = hbt.get_pos_ring(et, name_body='Jupiter', radius=129000, units='radec', wcs=w)

# Return as radians

x_ring1, y_ring1    = w.wcs_world2pix(ra_ring1*r2d, dec_ring1*r2d, 0) # Convert to pixels
x_ring2, y_ring2    = w.wcs_world2pix(ra_ring2*r2d, dec_ring2*r2d, 0) # Convert to pixels

# Get position of Metis, in pixels

(vec6,lt) = sp.spkezr('Metis', et, 'J2000', 'LT+S', 'New Horizons')
(junk, ra_metis, dec_metis) = sp.recrad(vec6[0:3])

# Look up velocity of NH, for stellar aberration

abcorr = 'LT+S'
frame = 'J2000'
st,ltime = sp.spkezr('New Horizons', et, frame, abcorr, 'Sun') # Get velocity of NH 
vel_sun_nh_j2k = st[3:6]

# Correct stellar RA/Dec for stellar aberration

radec_stars_cat        = np.transpose(np.array((ra_stars_cat, dec_stars_cat)))
radec_stars_cat_abcorr = hbt.correct_stellab(radec_stars_cat, vel_sun_nh_j2k) # Store as radians

# Convert ring RA/Dec for stellar aberration
#planes = pickle.load(lun)
#lun.close()

#radius = planes['Radius_eq']

radius    = hdulist['Radius_eq'].data
longitude = hdulist['Longitude_eq'].data

et = hdulist['PRIMARY'].header['SPCSCET'] # Get the ET from the file
utc = sp.et2utc(et, 'C', 0)

w = WCS(file)

# Calculate the sub-observer latitude (ie, ring tilt angle)

(vec, lt) = sp.spkezr('New Horizons', et, 'IAU_PLUTO', 'LT', 'Pluto')
vec = vec[0:3]
(junk, lon_obs, lat_obs) = sp.reclat(vec) # Get latitude, in radians

#==============================================================================
# Make a plot of the image + backplanes
#==============================================================================

plt.set_cmap('Greys_r')

hbt.figsize((9,6)) # This gets ignored sometimes here. I'm not sure why??
plt.subplot(1,3,1)
plt.imshow(stretch(im))
plt.title(sequence)
plt.gca().get_xaxis().set_visible(False)
Example #54
0
et_limits_arr = []

for utc in utc_limits_arr:
    et_limits_arr.append(sp.utc2et(utc))

num_et = 100


et_arr = hbt.frange(et_limits_arr[0], et_limits_arr[1], num_et)

phase_arr = []
utc_arr   = []

for et in et_arr:
    (st, lt) = sp.spkezr('MU69', et, 'J2000', 'LT+S', 'New Horizons')
    vec_sc_mu69 = st[0:3]

    (st, lt) = sp.spkezr('MU69', et, 'J2000', 'LT+S', 'Sun')
    vec_sun_mu69 = st[0:3]
    
    ang_phase = sp.vsep(-vec_sc_mu69, -vec_sun_mu69)
    
    phase_arr.append(ang_phase)
    
    utc_arr.append(sp.et2utc(et, 'C', 0))
    
    print(f'Phase angle = {ang_phase*hbt.r2d} deg at {utc_arr[-1]}')

phase_arr = np.array(phase_arr)
et_arr = np.array(et_arr)
et_ca = sp.utc2et(utc_ca)

hour = 3600
minute = 60

dt = -12 * hour   # What is the offset of our observation, from KBO C/A. K-1h, K+2d, etc.

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?
encounter_phase = 'Inbound' if (et_start < et_ca) else 'Outbound'

# Set up the ET array

if DO_TIMES_OPNAV:
    et = et_opnav
    
else:    
    et = hbt.frange(et_start, et_end, num_dt)

index_asymptote = 0 if (encounter_phase == 'Outbound') else 1

# We make one call to SPICE to look up the asymptote position
# Most SPICE calls are later, but we need to do this one now, ahead of order.

(st,lt) = sp.spkezr('MU69', et[index_asymptote], 'J2000', 'LT', 'New Horizons')
(junk, ra_asymptote, dec_asymptote) = sp.recrad(st[0:3])   
crval = np.array([ra_asymptote, dec_asymptote]) * hbt.r2d
                  
radius_search = 0.15 # degrees # We only use these catalog for very fine searches, so narrow is OK.

DO_PLOT_GSC = False # Goes to about v=12

if DO_PLOT_GSC:                 
    name_cat = u'The HST Guide Star Catalog, Version 1.1 (Lasker+ 1992) 1' # works, but 1' errors; investigating
    stars = conesearch.conesearch(crval, radius_search, cache=False, catalog_db = name_cat)
    ra_stars  = np.array(stars.array['RAJ2000'])*hbt.d2r # Convert to radians
    dec_stars = np.array(stars.array['DEJ2000'])*hbt.d2r # Convert to radians
    mag_stars = np.array(stars.array['Pmag'])

if DO_PLOT_USNO: