Exemplo n.º 1
0
def calculate_orientations(mid_time, interframe_delay, frame_number=0):
    et = mid_time
    et = et + (frame_number * interframe_delay)
    jupiter_state, lt = spice.spkpos('JUPITER', et, 'IAU_SUN', 'NONE', 'SUN')
    jupiter_state = np.array(jupiter_state)

    spacecraft_state, lt = spice.spkpos('JUNO_SPACECRAFT', et, 'IAU_JUPITER',
                                        'NONE', 'JUPITER')
    spacecraft_state = np.array(spacecraft_state)

    m = spice.pxform("IAU_JUPITER", "J2000", et)
    jupiter_rotation = np.array(
        ((m[0][0], m[0][1], m[0][2], 0.0), (m[1][0], m[1][1], m[1][2], 0.0),
         (m[2][0], m[2][1], m[2][2], 0.0), (0.0, 0.0, 0.0, 1.0)))

    m = spice.pxform("JUNO_SPACECRAFT", "IAU_JUPITER", et)
    spacecraft_orientation = np.array(
        ((m[0][0], m[0][1], m[0][2], 0.0), (m[1][0], m[1][1], m[1][2], 0.0),
         (m[2][0], m[2][1], m[2][2], 0.0), (0.0, 0.0, 0.0, 1.0)))

    m = spice.pxform("JUNO_JUNOCAM_CUBE", "JUNO_SPACECRAFT", et)
    instrument_cube_orientation = np.array(
        ((m[0][0], m[0][1], m[0][2], 0.0), (m[1][0], m[1][1], m[1][2], 0.0),
         (m[2][0], m[2][1], m[2][2], 0.0), (0.0, 0.0, 0.0, 1.0)))

    m = spice.pxform("JUNO_JUNOCAM", "IAU_JUPITER", et)
    instrument_orientation = np.array(
        ((m[0][0], m[0][1], m[0][2], 0.0), (m[1][0], m[1][1], m[1][2], 0.0),
         (m[2][0], m[2][1], m[2][2], 0.0), (0.0, 0.0, 0.0, 1.0)))

    return spacecraft_orientation, jupiter_state, spacecraft_state, jupiter_rotation, instrument_cube_orientation, instrument_orientation
Exemplo n.º 2
0
def check(mk, time=False, report=False):

    frames_to_check = False
    if not time:
        today = datetime.datetime.now()
        time = today.strftime("%Y-%m-%dT%H:%M:%S")

    frame_dict = gen_frame_dict(mk, report=report)

    spiceypy.furnsh(mk)
    et = spiceypy.utc2et(time)

    for key in frame_dict.keys():
        if frame_dict[key]['class'] == 4:
            fname = frame_dict[key]['name']
            try:
                spiceypy.pxform(fname, 'J2000', et)
            except SpiceyError:
                frames_to_check = True
                print( f'Frame {fname} not properly defined at {time}\n' +
                       f'   NAME:     {frame_dict[key]["name"]}\n' +
                       f'   CLASS:    {frame_dict[key]["class"]}\n' +
                       f'   ID:       {frame_dict[key]["id"]}\n' +
                       f'   CENTER:   {frame_dict[key]["center"]}\n' +
                       f'   RELATIVE: {frame_dict[key]["relative"]}\n'
                     )
                pass

    spiceypy.kclear()

    if not frames_to_check:
        print(f'All {len(frame_dict)} frames are correct @ {time}')

    return
Exemplo n.º 3
0
def rotation_matrix(et):
    axis_x,axis_y,axis_z = selframe_axis(et)
    coord_topo = np.array([axis_x,axis_y,axis_z]).T  
    R_topo2self = np.linalg.inv(coord_topo)
    R_topo2j2000 = spice.pxform('GROUNDSTATION_TOPO','J2000',et)
    R_j20002me = spice.pxform('j2000','moon_me',et)
    
    return R_topo2self,R_topo2j2000,R_j20002me
Exemplo n.º 4
0
 def test_msi_frame_transpose(self):
     spice.kclear()
     spice.furnsh(self.near.metakernel)
     utc = 'Feb 2, 2001 14:20:10 UTC'
     et = spice.str2et(utc)
     Rb2c = spice.pxform(self.near.near_body_frame, 
             self.near.near_msi_frame, et)
     Rc2b = spice.pxform(self.near.near_msi_frame,
             self.near.near_body_frame, et)
     np.testing.assert_array_almost_equal_nulp(Rb2c, Rc2b.T)
     spice.kclear()
Exemplo n.º 5
0
    def test_msi_frame_transformation_fixed(self):
        spice.kclear()
        spice.furnsh(self.near.metakernel)
        etone = spice.str2et('Jan 1, 2001 10:04:02.2 UTC')
        ettwo = spice.str2et('Feb 12, 2001 18:03:02.6 UTC')

        Rone = spice.pxform(self.near.near_body_frame, 
                self.near.near_msi_frame, etone)
        Rtwo = spice.pxform(self.near.near_body_frame,
                self.near.near_msi_frame, ettwo)
        np.testing.assert_array_almost_equal(Rone, Rtwo)
        spice.kclear()
Exemplo n.º 6
0
def transform_lonlat(t, lons, lats, frame_from, frame_to):
    """Transform longitude/latitude direction from one reference frame to another.

    Parameters
    ----------
    t : datetime.datetime
        Evaluation datetimes.
    lons : np.ndarray
        Longitudes array.
    lats : np.ndarray
        Latitudes array.

    Returns
    -------
    (np.ndarray, np.ndarray)
        Transformed longitude/latitude's.

    Raises
    ------
    ValueError
        If source and target frame are equal.
    """
    if frame_from == frame_to:
        raise ValueError("source frame and target frame are equal")

    lons_rad = 2 * np.pi * lons / 360
    lats_rad = 2 * np.pi * lats / 360

    vecs = np.array([
        [np.cos(lon), np.sin(lon), np.sin(lat)]
        for (lon, lat) in list(zip(lons_rad, lats_rad))
    ])

    if isinstance(t, datetime.datetime):
        for i in range(0, len(vecs)):
            vecs[i] = spiceypy.mxv(spiceypy.pxform(frame_from, frame_to,
                                   spiceypy.datetime2et(t)),
                                   vecs[i])
    elif isinstance(t, np.ndarray) or isinstance(t, list):
        for i in range(0, len(t)):
            vecs[i] = spiceypy.mxv(spiceypy.pxform(frame_from, frame_to,
                                   spiceypy.datetime2et(t[i])),
                                   vecs[i])
    else:
        raise TypeError("variable t is not a datetime or array/list")

    res = np.array([
        [360 * np.arccos(v[0]) / 2 / np.pi, 360 * np.arcsin(v[2]) / 2 / np.pi]
        for v in vecs
    ])

    return res
Exemplo n.º 7
0
    def frame_chain(self):
        """
        Return the root node of the rotation frame tree/chain.

        The root node is the J2000 reference frame. The other nodes in the
        tree can be accessed via the methods in the FrameNode class.

        This property expects the ephemeris_time property/attribute to be defined.
        It should be a list of the ephemeris seconds past the J2000 epoch for each
        exposure in the image.

        Returns
        -------
        FrameNode
            The root node of the frame tree. This will always be the J2000 reference frame.
        """
        if not hasattr(self, '_root_frame'):
            j2000_id = 1  #J2000 is our root reference frame
            self._root_frame = FrameNode(j2000_id)

            sensor_quats = np.zeros((len(self.ephemeris_time), 4))
            sensor_times = np.array(self.ephemeris_time)
            body_quats = np.zeros((len(self.ephemeris_time), 4))
            body_times = np.array(self.ephemeris_time)
            for i, time in enumerate(self.ephemeris_time):
                sensor2j2000 = spice.pxform(spice.frmnam(self.sensor_frame_id),
                                            spice.frmnam(j2000_id), time)
                q_sensor = spice.m2q(sensor2j2000)
                sensor_quats[i, :3] = q_sensor[1:]
                sensor_quats[i, 3] = q_sensor[0]

                body2j2000 = spice.pxform(spice.frmnam(self.target_frame_id),
                                          spice.frmnam(j2000_id), time)
                q_body = spice.m2q(body2j2000)
                body_quats[i, :3] = q_body[1:]
                body_quats[i, 3] = q_body[0]

            sensor2j2000_rot = TimeDependentRotation(sensor_quats,
                                                     sensor_times,
                                                     self.sensor_frame_id,
                                                     j2000_id)
            sensor_node = FrameNode(self.sensor_frame_id,
                                    parent=self._root_frame,
                                    rotation=sensor2j2000_rot)

            body2j2000_rot = TimeDependentRotation(body_quats, body_times,
                                                   self.target_frame_id,
                                                   j2000_id)
            body_node = FrameNode(self.target_frame_id,
                                  parent=self._root_frame,
                                  rotation=body2j2000_rot)
        return self._root_frame
Exemplo n.º 8
0
    def sidereal_time(self, kind, longitude=None, model=None):
        # Currently returns the zenith RA as the LST.
        if self.location is None or self.location is EarthLocation:
            return super().sidereal_time(kind,
                                         longitude=longitude,
                                         model=model)

        if model is not None:
            raise ValueError("The 'model' keyword is not supported"
                             "'for MoonLocation sidereal_times.")

        # From here on, proceed assuming longitude or location refer
        # to the selenodetic coordinate system. "self.location" must
        # be defined in order to get here.

        et = (self - Time('J2000')).sec
        et = np.atleast_1d(et)
        mats = np.array([spice.pxform('MOON_ME', 'J2000', t) for t in et])

        # Zenith vector
        zvec = self.location.mcmf.cartesian.xyz
        uzvec = zvec / np.linalg.norm(zvec)

        newvec = np.dot(mats, uzvec)
        return Longitude(
            np.arctan2(newvec[..., 1], newvec[..., 0]).squeeze(), 'rad')
Exemplo n.º 9
0
 def test_spicepy_state_transformation(self):
     spice.furnsh(self.cass.metakernel)
     T = spice.sxform('IAU_EARTH', 'IAU_SATURN', self.etOne)
     R = spice.pxform('IAU_EARTH', 'IAU_SATURN', self.etOne)
     (Rout, wout) = spice.xf2rav(T)
     np.testing.assert_array_almost_equal(Rout, R)
     spice.kclear()
Exemplo n.º 10
0
def worker_transform_frame(args):
    """Worker function for transforming 3D coordinates.

    Parameters
    ----------
    args: (np.ndarray, np.ndarray, str, str, np.ndarray, np.ndarray, list)
        Function arguments as tuple.

    Returns
    -------
    np.ndarray
        Transformed 3D coordinates.
    """
    (t, data, frame_from, frame_to, frames, frame_indices, kernels) = args

    if len(heliosat._spice["kernels_loaded"]) == 0:
        heliosat.spice.spice_init()
        heliosat.spice.spice_reload(kernels)

    if frames:
        for i in range(0, len(t)):
            data[i] = spiceypy.mxv(frames[int(frame_indices[i])], data[i])
    else:
        for i in range(0, len(t)):
            data[i] = spiceypy.mxv(spiceypy.pxform(frame_from, frame_to,
                                   spiceypy.datetime2et(t[i])),
                                   data[i])
    return data
def getNacCenterAndRotationAtTime(imname):
    '''
    Extract the rotation of the spacecraft and its position for the given picture
    Notice that WAV and NAC can be considered aligned on the same axis and centred in the spacecraft
    so it can be used the same way independently by the camera
    :param imname: the input picture filename
    :return: the centre and the rotation matrix of the spacecraft
    '''
    os.chdir(spice_kernels_dir)  # or spyceypy will not work
    spiceypy.furnsh(spice_kernels_dir + os.path.sep +
                    furnsh_file)  #load the kernels from furnsh

    tstr = extractTimeString(
        imname)  # get only the time string from the filename
    t = spiceypy.str2et(tstr)  # transform the string to a time

    # get the position of Rosetta spacecraft in 67p refence frame, centred in 67P + light time corr (not really needed)
    pos = spiceypy.spkpos("ROSETTA", t, "67P/C-G_CK", "LT+S", "67P/C-G")[0]
    # pos = spiceypy.spkpos("ROSETTA", t, "ROS_OSIRIS_NAC", "LT+S", "67P/C-G")[0]

    print("Found position: {}".format(pos))

    #and the rotation - we use NAC but it is the same for the WAC
    # R = spiceypy.pxform("67P/C-G_CK", "ROS_OSIRIS_NAC", t);
    # rotation matrix of the nac camera, in respect to 67P reference frame
    R = spiceypy.pxform("ROS_OSIRIS_NAC", "67P/C-G_CK", t)
    return np.array(pos), np.array(R)
Exemplo n.º 12
0
    def _sensor_orientation(self):
        """
        Returns quaternions describing the orientation of the sensor.
        Expects ephemeris_time to be defined. This should be a list containing
        ephemeris times during which the image was taken.
        Expects instrument_id to be defined in the Pds3Label mixin. This should be
        a string of the form TC1 or TC2self.
        Expects reference_frame to be defined. This should be a string containing
        the name of the reference_frame.

        Returns
        -------
        : list
          Quaternions describing the orentiation of the sensor
        """
        if not hasattr(self, '_orientation'):
            ephem = self.ephemeris_time

            qua = np.empty((len(ephem), 4))
            for i, time in enumerate(ephem):
                instrument = super().instrument_id
                # Find the rotation matrix
                camera2bodyfixed = spice.pxform(
                    "LISM_{}_HEAD".format(instrument), self.reference_frame,
                    time)
                q = spice.m2q(camera2bodyfixed)
                qua[i, :3] = q[1:]
                qua[i, 3] = q[0]
            self._orientation = qua
        return self._orientation.tolist()
Exemplo n.º 13
0
    def sensor_orientation(self):
        """
        Returns quaternions describing the sensor orientation. Expects ephemeris_time
        to be defined. This must be a floating point number containing the
        ephemeris time. Expects instrument_id to be defined. This must be a string
        containing the short name of the instrument. Expects reference frame to be defined.
        This must be a sring containing the name of the target reference frame.

        Returns
        -------
        : list
          Quaternions describing the orientation of the sensor
        """
        if not hasattr(self, '_orientation'):
            ephem = self.ephemeris_time

            qua = np.empty((len(ephem), 4))
            for i, time in enumerate(ephem):
                # Find the rotation matrix
                camera2bodyfixed = spice.pxform(self.instrument_id,
                                                self.reference_frame, time)
                q = spice.m2q(camera2bodyfixed)
                qua[i, :3] = q[1:]
                qua[i, 3] = q[0]
            self._orientation = qua
        return self._orientation.tolist()
Exemplo n.º 14
0
def icrs_to_mcmf_mat(time):
    # Rotation matrix from ICRS to MOON_ME
    # time = single astropy Time object.

    # Ephemeris time = seconds since J2000
    et = (time - Time('J2000')).sec
    mat = spice.pxform('J2000', 'MOON_ME', et)

    return mat
Exemplo n.º 15
0
def lunartopo_to_mcmf(topo_coo, mcmf_frame):

    _spice_setup(topo_coo.location.lat.deg, topo_coo.location.lon.deg)

    mat = spice.pxform('LUNAR-TOPO', 'MOON_ME', 0)   # Not time-dependent

    newrepr = topo_coo.cartesian.transform(mat)

    return mcmf_frame.realize_frame(newrepr)
Exemplo n.º 16
0
    def from_spice(cls,
                   *args,
                   sensor_frame,
                   target_frame,
                   center_ephemeris_time,
                   ephemeris_times=[],
                   **kwargs):
        frame_chain = cls()

        times = np.array(ephemeris_times)

        sensor_time_dependent_frames, sensor_constant_frames = cls.frame_trace(
            sensor_frame, center_ephemeris_time)
        target_time_dependent_frames, target_constant_frames = cls.frame_trace(
            target_frame, center_ephemeris_time)

        time_dependent_frames = list(
            zip(sensor_time_dependent_frames[:-1],
                sensor_time_dependent_frames[1:]))
        constant_frames = list(
            zip(sensor_constant_frames[:-1], sensor_constant_frames[1:]))
        target_time_dependent_frames = list(
            zip(target_time_dependent_frames[:-1],
                target_time_dependent_frames[1:]))
        target_constant_frames = list(
            zip(target_constant_frames[:-1], target_constant_frames[1:]))

        time_dependent_frames.extend(target_time_dependent_frames)
        constant_frames.extend(target_constant_frames)

        for s, d in time_dependent_frames:
            quats = np.zeros((len(times), 4))
            avs = np.zeros((len(times), 3))
            for j, time in enumerate(times):
                state_matrix = spice.sxform(spice.frmnam(s), spice.frmnam(d),
                                            time)
                rotation_matrix, avs[j] = spice.xf2rav(state_matrix)
                quat_from_rotation = spice.m2q(rotation_matrix)
                quats[j, :3] = quat_from_rotation[1:]
                quats[j, 3] = quat_from_rotation[0]

            rotation = TimeDependentRotation(quats, times, s, d, av=avs)
            frame_chain.add_edge(rotation=rotation)

        for s, d in constant_frames:
            quats = np.zeros(4)
            rotation_matrix = spice.pxform(spice.frmnam(s), spice.frmnam(d),
                                           times[0])
            quat_from_rotation = spice.m2q(rotation_matrix)
            quats[:3] = quat_from_rotation[1:]
            quats[3] = quat_from_rotation[0]

            rotation = ConstantRotation(quats, s, d)

            frame_chain.add_edge(rotation=rotation)

        return frame_chain
Exemplo n.º 17
0
def DCM(fromFrame, toFrame, dt):
    '''
    :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: the Cartesian DCM as 3x3 np matrix
    '''
    _load_kernels_()
    return spice.pxform(fromFrame, toFrame, dt)
Exemplo n.º 18
0
def icrs_to_lunartopo(icrs_coo, topo_frame):

    _spice_setup(topo_frame.location.lat.deg, topo_frame.location.lon.deg)

    obstime = topo_frame.obstime
    et = (obstime - _J2000).sec
    mat = spice.pxform('J2000', 'LUNAR-TOPO', et)
    newrepr = icrs_coo.cartesian.transform(mat)

    return topo_frame.realize_frame(newrepr)
Exemplo n.º 19
0
 def test_msi_boresight_vector(self):
     spice.kclear()
     spice.furnsh(self.near.metakernel)
     bs_near_body = np.array([.9999988429, -.0004838414, .0014422523])
     bs_msi_frame = np.array([1, 0, 0])
     et = spice.str2et('Feb 12, 2001 UTC')
     Rc2b = spice.pxform(self.near.near_msi_frame,
             self.near.near_body_frame, et)
     np.testing.assert_array_almost_equal(Rc2b.dot(bs_msi_frame),
             bs_near_body)
     spice.kclear()
Exemplo n.º 20
0
def get_orientation_matrix(obsinfo, target, object_id):
    iau_frame = f"IAU_{target}"
    frcode = spice.namfrm(iau_frame)
    if frcode == 0:
        frame_name = f"FRAME_{object_id}_NAME"
        _n, iau_frame = spice.gcpool(frame_name, 0, 1)
        frcode = spice.namfrm(iau_frame)
    mtx = spice.pxform(iau_frame, obsinfo.fov.frame, obsinfo.et)
    # rot = get_boresight_rotation_matrix(obsinfo['boresight'])
    # return spice.mxm(rot, mtx)
    return mtx
Exemplo n.º 21
0
def spice_rot2inert(y, t):
    frame_from = 'IAU_JUPITER'
    frame_to = 'JUICE_JUPITER_IF_J2000'  # 'JUICE_JSM'
    et = t

    rot_mat = spice.pxform(frame_from, frame_to,
                           et)  # rotation matrix from IAU_JUPITER frame to JIF
    w = np.array([0., 0., w_jup])
    transfo_mat = spice.rav2xf(rot_mat, -w)
    yout = np.dot(transfo_mat, y)
    return yout
    def calculate_orientations(self, frame_number=0):
        observationStartEt = self.start_time

        # https://github.com/USGS-Astrogeology/ISIS3/pull/165
        # https://naif.jpl.nasa.gov/pub/naif/JUNO/kernels/ik/juno_junocam_v02.ti
        startTimeBias = 0.06188
        interFrameDelayBias = 0.001
        et = observationStartEt + startTimeBias + (frame_number - 1.0) * (self.interframe_delay + interFrameDelayBias);

        jupiter_state, lt = spice.spkpos('JUPITER', et, 'IAU_SUN', 'NONE', 'SUN')
        jupiter_state = np.array(jupiter_state)

        spacecraft_state, lt = spice.spkpos('JUNO_SPACECRAFT', et, 'IAU_JUPITER', 'NONE', 'JUPITER')
        spacecraft_state = np.array(spacecraft_state)

        m = spice.pxform("IAU_JUPITER", "J2000", et)
        jupiter_rotation = np.array(
            ((m[0][0], m[0][1], m[0][2], 0.0),
             (m[1][0], m[1][1], m[1][2], 0.0),
             (m[2][0], m[2][1], m[2][2], 0.0),
             (0.0, 0.0, 0.0, 1.0))
        )

        m = spice.pxform("JUNO_SPACECRAFT", "IAU_JUPITER", et)
        spacecraft_orientation = np.array(
            ((m[0][0], m[0][1], m[0][2], 0.0),
             (m[1][0], m[1][1], m[1][2], 0.0),
             (m[2][0], m[2][1], m[2][2], 0.0),
             (0.0, 0.0, 0.0, 1.0))
        )

        m = spice.pxform("JUNO_JUNOCAM", "IAU_JUPITER", et)
        instrument_orientation = np.array(
            ((m[0][0], m[0][1], m[0][2], 0.0),
             (m[1][0], m[1][1], m[1][2], 0.0),
             (m[2][0], m[2][1], m[2][2], 0.0),
             (0.0, 0.0, 0.0, 1.0))
        )

        return spacecraft_orientation, jupiter_state, spacecraft_state, jupiter_rotation, instrument_orientation
Exemplo n.º 23
0
    def sc_pos(self, timestr):

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

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

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

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

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

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

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

        self.i2p = spice.pxform(self.instrument, self.framestring, self.et)
        
        return self.data
Exemplo n.º 24
0
def xform_spice(xfunc, tais, **kwargs):
    res = {}
    try:
        for tai in tais:
            et = sp.unitim(tai, 'tai', 'et')
            mat = sp.pxform(kwargs['from_ref'], kwargs['to_ref'], et)
            utc = sp.et2utc(et, 'isoc', 3)

            res[utc] = xfunc(mat)
    except sp.stypes.SpiceyError as ex:
        return ex.value
    else:
        return res
Exemplo n.º 25
0
    def sensor_orientation(self):
        if not hasattr(self, '_orientation'):
            ephem = self.ephemeris_time

            qua = np.empty((len(ephem), 4))
            for i, time in enumerate(ephem):
                # Find the rotation matrix
                camera2bodyfixed = spice.pxform(self.instrument_id,
                                                self.reference_frame, time)
                q = spice.m2q(camera2bodyfixed)
                qua[i, :3] = q[1:]
                qua[i, 3] = q[0]
            self._orientation = qua
        return self._orientation.tolist()
Exemplo n.º 26
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))
Exemplo n.º 27
0
def HAEtoHCI(Date, ut, xi, yi, zi):
    '''
	Convert HAE to HCI coordinates
	
	'''

    #create some arrays
    n = np.size(xi)

    if np.size(ut) == 1:
        ut = np.zeros(n, dtype='float32') + ut
    if np.size(xi) == 1:
        xi = np.array([xi])
        yi = np.array([yi])
        zi = np.array([zi])
    et = np.zeros((n, ), dtype='float64')
    x = np.zeros(n, dtype='float64')
    y = np.zeros(n, dtype='float64')
    z = np.zeros(n, dtype='float64')

    #load kernels
    sp.furnsh(lsk_path)
    sp.furnsh(spk_kernel)
    sp.furnsh(pck_kernel)
    sp.furnsh(hci_kernel)

    #do each unique date to speed things up
    if np.size(Date) == 1:
        et[0] = utc2et(Date, 0.0)
        et = et[0] + ut * 3600.0
    else:
        ud = np.unique(Date)
        for i in range(0, ud.size):
            use = np.where(Date == ud[i])[0]
            tmp = utc2et(ud[i], 0.0)
            et[use] = tmp + ut[use] * 3600.0

    for i in range(0, n):
        rot = sp.pxform('ECLIPDATE', 'HCI', et[i])
        x[i], y[i], z[i] = np.sum(rot * np.array([xi[i], yi[i], zi[i]]),
                                  axis=1)

    #free kernels
    sp.unload(lsk_path)
    sp.unload(spk_kernel)
    sp.unload(pck_kernel)
    sp.unload(hci_kernel)

    return (x, y, z)
Exemplo n.º 28
0
 def sensor_orientation(self):
     if not hasattr(self, '_sensor_orientation'):
         current_et = self.starting_ephemeris_time
         qua = np.empty((self.number_of_ephemerides, 4))
         for i in range(self.number_of_quaternions):
             # Find the rotation matrix
             camera2bodyfixed = spice.pxform(self.instrument_id,
                                             self.reference_frame,
                                             current_et)
             q = spice.m2q(camera2bodyfixed)
             qua[i, :3] = q[1:]
             qua[i, 3] = q[0]
             current_et += getattr(self, 'dt_quaternion', 0)
         self._sensor_orientation = qua
     return self._sensor_orientation.tolist()
Exemplo n.º 29
0
    def __init__(self, inst, et, abcorr, obsrvr, width, height, mag_limit):
        # input parameter
        self.inst = inst
        self.et = et
        self.abcorr = abcorr
        self.obsrvr = obsrvr
        self.width = width
        self.height = height

        # parameters equivalent to input parameter
        self.date = spice.et2utc(et, "ISOC", 3)
        self.inst_id = spice.bodn2c(inst)

        # Instrument FOV
        self.fov = Fov(self.inst_id)
        self.fov_in_degrees = self.fov.fovmax * 2.0 * spice.dpr()

        # geometry information
        self.pos, _ = spice.spkpos(obsrvr, et, self.fov.frame, abcorr, "SUN")
        self.obs2refmtx = spice.pxform(self.fov.frame, "J2000", et)
        self.ref2obsmtx = spice.pxform("J2000", self.fov.frame, et)

        # screen information
        pos_angle, angle_res, ra, dec = get_geometry_info(
            self.obs2refmtx, self.fov, width, height
        )

        self.center = self.fov.bounds_rect.center_vec
        self.pos_angle = pos_angle
        self.angle_res = angle_res
        self.ra = ra
        self.dec = dec

        # searched objects
        self.solar_objects = search_solar_objects(self)
        self.stars = search_stars(self, mag_limit)
Exemplo n.º 30
0
def printBoresights(angleSeparationA, angleSeparationB):
    """input manual rotation angles from SPICE kernels to calculate new and old boresight"""
    oldSoBoresight = [0.0, 0.0, 1.0]
    oldUVISBoresight = [0.0, 0.0, 1.0]
    rotationMatrixSoUVIS = sp.pxform("TGO_NOMAD_SO", "TGO_NOMAD_UVIS_OCC",
                                     sp.utc2et("2018 APR 01 00:00:00 UTC"))
    oldSoBoresightUVIS = np.dot(oldSoBoresight, rotationMatrixSoUVIS.T)
    oldBoresightSeparation = sp.vsep(oldUVISBoresight,
                                     oldSoBoresightUVIS) * sp.dpr() * 60.0
    print("oldBoresightSeparation")
    print(oldBoresightSeparation)

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

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

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

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

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

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

    oldSoUVISBoresightSeparation = sp.vsep(
        oldSoBoresightTGO, oldUVISBoresightTGO) * sp.dpr() * 60.0
    print("oldSoUVISBoresightSeparation")
    print(oldSoUVISBoresightSeparation)
Exemplo n.º 31
0
    def _sensor_orientation(self):
        if not hasattr(self, '_orientation'):
            ephem = self.ephemeris_time

            qua = np.empty((len(ephem), 4))
            for i, time in enumerate(ephem):
                instrument = self.label.get("INSTRUMENT_ID")
                # Find the rotation matrix
                camera2bodyfixed = spice.pxform(
                    "LISM_{}_HEAD".format(instrument), self.reference_frame,
                    time)
                q = spice.m2q(camera2bodyfixed)
                qua[i, :3] = q[1:]
                qua[i, 3] = q[0]
            self._orientation = qua
        return self._orientation.tolist()
Exemplo n.º 32
0
        # Get the distance from Jupiter center to image center
        
        vec_nh_center = sp.radrec(1, radec_center[0][0], radec_center[0][1])  # Vector from NH, to center of LORRI frame
        ang_jup_center = sp.vsep(vec_nh_jup, vec_nh_center)                   # Ang Sep btwn Jup and LORRI, radians
        dist_jup_center_rj = ang_jup_center * sp.vnorm(vec_nh_jup) / rj_km    # Convert from radians into RJ
        width_lorri = 0.3*hbt.d2r                                             # LORRI full width, radians
    
        dist_jup_center_rj_range = np.array([ang_jup_center-width_lorri/2, ang_jup_center+width_lorri/2]) * \
            sp.vnorm(vec_nh_jup) / rj_km                                      # Finally, we have min and max dist
                                                                              # in the central row of array, in rj
        range_rj_arr.append(sp.vnorm(vec_nh_jup)/rj_km)
   
        # Calc the elevation angle (aka sub-obs lat on Jup)
        
        mx = sp.pxform('J2000', 'IAU_JUPITER', t_i['ET'])
        vec_nh_jup_jup = sp.mxv(mx, vec_nh_jup)
        (dist, lon, lat) = sp.recrad(-vec_nh_jup_jup)
        
        angle_elev_arr.append(lat * hbt.r2d)  # Save the sub-obs latitude, in degrees
        
        # If we are on the LHS limb, then we need to reverse this, and negate it.
    
        if (is_limb_left):
            dist_jup_center_rj_range = -1 * dist_jup_center_rj_range[::-1]

        dist_proj_rj_arr.append((dist_jup_center_rj_range))
        
        # Get the distance above the ring plane -- that is, distance from image center to ring plane, projected.
        # To do this:
        #   - Project a vector from s/c along central LORRI pixel.
Exemplo n.º 33
0
    mlab.view(v)
    mlab.draw()

r_hat_virtis = np.array([0,0,1])
spice.furnsh("../input/spiceMetafile.tm")

# t = time for which the pointing is calculated
t = datetime.datetime(2015,4,25,0,28)
timeStamp = datetime.datetime.strftime(t, '%Y-%m-%dT%H:%M:%S')
et = spice.str2et(timeStamp)

# define virtis bore sight vector, first in instrument reference frame
# (which is [0,0,1]) and then rotate this vector into the comet centric
# frame of reference for the given time t.
rVirtis_hat = np.array([0,0,1])
R = spice.pxform('ROS_OSIRIS_NAC','67P/C-G_CK', et)
rVirtis_hat = np.dot(R, rVirtis_hat)
nPixelsX = 3
nPixelsY = 3
pVectors = pointing_vectors(nPixelsX, nPixelsY)

################################################################################
# compute coordinates of S/C and Sun in the comet body centric frame
m2km = 1000
observer = 'CHURYUMOV-GERASIMENKO'
corr = 'NONE'
frame = '67P/C-G_CK'
rSC, lt = spice.spkpos('ROSETTA', et, frame, corr, observer)
rSC = np.array(rSC, dtype=float) * m2km
rSC_hat = rSC / np.linalg.norm(rSC)
Exemplo n.º 34
0
    lun.close()
    
except IOError: # If Pickle file not found, then go ahead and read the catalog from scratch
    
    # Read HD into a table. Its positions are 1900. Coords are radians.

    print ("Reading HD catalog...")
    
    hd = read_hd_all()  
    num_stars = np.size(ra_1950)

    # Precess stars from 1900 to 2000
    
    print("Precessing to J2000")
    
    mx = sp.pxform('B1950', 'J2000', et[0]) # SPICE knows about 1950, but not 1900!
    
    ra_1950  = t['RA']
    dec_1950 = t['Dec']
        
    ra_2000  = []
    dec_2000 = []
    pt_1950  = []
    
    # Loop over every star and precess it individually. This loop is a real bottleneck.
    
    for i in range(num_stars):
        
        pt_1900 = sp.radrec(1, ra_1950[i], dec_1950[i])
        pt_1950 = sp.mxv(mx, pt_1900)
        pt_2000 = sp.mxv(mx, pt_1950) # SPICE will not precess 1900 -> 2000. So we apply 1950 -> 2000, do it 2x.
def test_frame_mu69_sunflower():
    
    """
    This is just a quick test routine to check that MU69 Sunflower frame is more-or-less working.
    
    This program prints the RA/Dec of MU69's Y and Z axes, under a variety of different conditions.
    
    No options.
    
    Things to verify here:
        - That the Z RA/Dec point toward the orbit pole RA/Dec in all cases. 
              Small deviations << 1 deg allowed
              
        - That the Y RA/Dec point toward the Sun always for _ROT Frames.
              (ie, Y RA/Dec changes with time.)
              
        - That the Y RA/Dec point toward the Sun for the _INERT frames on 1 Jan 2015,
              and slowly move after / before that, at roughly 1 deg/year.
              (ie, Y RA/Dec is pretty much fixed)
              
    16-Jan-2018. HBT verified that output values look pretty good.

    """
    
    tms = ['kernels_sunflower.tm']  # Define the metakernel, which in turn calls the sunflower .tf frame

    frames = ['2014_MU69_SUNFLOWER_ROT', '2014_MU69_SUNFLOWER_INERT']
        
    utcs = ['1 Jan 2005 00:00:00', '1 Jan 2015 00:00:00']
    
    frame_j2k = 'J2000'
    
    # Get values from Simon Porter. See email from MRS ~16-Jan-2018.
    # These RA/Dec values are also put into the .tf file which I have made.
    
    ra_mu69_sp = 272.426110231801*hbt.d2r
    dec_mu69_sp = 68.831520928192*hbt.d2r
    
    # Define the MU69 Z axis. We will rotate this, and it should point in specified direction.
    
    z_mu69 = [0, 0, 1] # MU69 +Z axis. It should point to the specified RA/Dec
    y_mu69 = [0, 1, 0] # MU69 +Y axis. It should point away from the Sun
    
    print("Simon Porter pole position:")
    print("RA = {}, Dec = {}".format(ra_mu69_sp*hbt.r2d, dec_mu69_sp*hbt.r2d))
    print("---")
    
    # Loop over input parameters. For each combination, do a calculation, and print the output.
    
    for tm in tms:    
        for frame in frames: 
            for utc in utcs:
                sp.furnsh(tm)
                et = sp.utc2et(utc)
                mx = sp.pxform(frame, frame_j2k, et)
            
                z_mu69_j2k = sp.mxv(mx, z_mu69)                
                (_, ra_z, dec_z) = sp.recrad(z_mu69_j2k)

                y_mu69_j2k = sp.mxv(mx, y_mu69)                
                (_, ra_y, dec_y) = sp.recrad(y_mu69_j2k)
                
                print("Metakernel: {}".format(tm))
                print("UTC:        {}".format(utc))
                print("Frame:      {}".format(frame))
                print("Matrix:     \n{}".format(mx))
                print("Y RA = {}, Dec = {}".format(ra_y*hbt.r2d, dec_y*hbt.r2d))
                print("Z RA = {}, Dec = {}".format(ra_z*hbt.r2d, dec_z*hbt.r2d))
                print("\n---\n")
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
Exemplo n.º 37
0
def compute_backplanes(file, name_target, frame, name_observer, angle1=0, angle2=0, angle3=0):
    
    """
    Returns a set of backplanes for a single specified image. The image must have WCS coords available in its header.
    Backplanes include navigation info for every pixel, including RA, Dec, Eq Lon, Phase, etc.
    
    The results are returned to memory, and not written to a file.
    
    SPICE kernels must be alreaded loaded, and spiceypy running.
    
    Parameters
    ----
    
    file:
        String. Input filename, for FITS file.
    frame:
        String. Reference frame of the target body. 'IAU_JUPITER', 'IAU_MU69', '2014_MU69_SUNFLOWER_ROT', etc.
        This is the frame that the Radius_eq and Longitude_eq are computed in.
    name_target:
        String. Name of the central body. All geometry is referenced relative to this (e.g., radius, azimuth, etc)
    name_observer:
        String. Name of the observer. Must be a SPICE body name (e.g., 'New Horizons')
    
    Optional Parameters
    ----
    
    angle{1,2,3}:
        **NOT REALLY TESTED. THE BETTER WAY TO CHANGE THE ROTATION IS TO USE A DIFFERENT FRAME.**

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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