Initial Set-up¶
GIANT in its base form is also designed to be entirely mission agnostic. This makes it really easy to use GIANT for multiple missions without ever having to change the core GIANT code. Unfortunately this also means that GIANT isn’t 100% ready for use right out of the box. Instead, we need to complete a few simple classes to tailor things to our particular setup.
Typically this is done in a separate module so that we can store the updates we make and continue using them in the future. To make your setup module, begin by making a new directory (anywhere you want). For this tutorial, lets try to set up GIANT for DAWN’s approach to Vesta.
mkdir dawn_giant
cd dawn_giant
Customizing GIANT for DAWN¶
We need to define our mission specific implementation. This involves subclassing a few of the GIANT classes to ensure that they work with our current mission. The steps to do this are discussed in the following subsections.
Defining Our Imports¶
The first step to creating our GIANT customization module is to import all of the modules and packages we will need. It is considered good practice to do this at the top of each file, instead of spreading the imports throughout the code, so that is how we will build our module here. To begin, create a new file called dawn_giant.py in the dawn_giant directory and open it with your favorite text editor. Now, enter the following imports so we have everything available that we’ll need later. You’ll see these things in use in a little bit so just trust us that we need them for now.
# the GIANT classes we need to customize
from giant.image import OpNavImage
from giant.camera import Camera
# the attitude object that represents rotations in GIANT
from giant.rotations import Rotation
# a GIANT module for working with spice
import giant.utilities.spice_interface as spint
# a standard library module that lets us query and manipulate file paths
import os
# A module to provide access to the NAIF Spice routines
import spiceypy as spice
from spiceypy.utils.support_types import SpiceyError
# a standard library for representing dates and time deltas
from datetime import timedelta
# a library for parsing label files.
import pvl
Subclassing the Camera Class¶
With the images themselves handled, we can turn our attention to the Camera
class. The Camera
class
works as a container to store all of the images we are currently processing, as well as some information about the
camera itself, including the geometric camera model that maps points in the camera frame to points in an image. There
are typically two main things we need to update in the camera class. This is the special Camera.__init__()
method
and the Camera.preprocessor()
method. We set these methods for the DAWN framing cameras below
class DawnFCCamera(Camera):
# update the init function to use the new DawnFCImage class instead of the default OpNavImage class
def __init__(self, images=None, model=None, name=None, spacecraft_name=None,
frame=None, parse_data=True, psf=None, attitude_function=None, start_date=None, end_date=None,
default_image_class=DawnFCImage):
super().__init__(images=images, model=model, name=name, spacecraft_name=spacecraft_name,
frame=frame, parse_data=parse_data, psf=psf,
attitude_function=attitude_function, start_date=start_date, end_date=end_date,
default_image_class=default_image_class)
def preprocessor(self, image):
# here we might apply corrections to the image (like flat fields and darks) or we can extract extra
# information about the image and store it as another attribute (like dark_pixels which can be used to
# compute the noise level in the image). For the DAWN framing cameras though, we don't need to do anything
# so we just return the image unmodified.
return image
In the init method, we simply change the default value for the default_image_class
key word argument to point to
our new DawnFCImage
class that we just defined. We then pass all of these values to the default constructor for the
Camera
class and move on.
In the preprocessor()
method, we don’t have to do anything for the DAWN framing cameras except to
return the images as they are. For other cameras and missions, the preprocessor is where you can put things like
image corrections to remove fixed pattern noise, apply dark and flat field corrections, and extract covered active
pixels into the dark_pixels
attribute as a way to extract the noise level for each image.
Defining Functions to Return State Information¶
One non-required thing we can due is to predefine some functions that return the state (position, velocity, attitude) of
certain objects that we will frequently need. Since most of this data is coming from spice, we can use the GIANT
spice_interface
module to interface with SPICE and make the functions that we need.
# convenience functions
def sun_orientation(*args):
# always set the sun orientation to be the identity rotation (J2000) because it doesn't actually matter
return Rotation([0, 0, 0])
# define a function that will return the sun position in the inertial frame wrt SSB for a datetime
sun_position = spint.SpicePosition('SUN', 'J2000', 'NONE', 'SSB')
# define a function that will return the framing camera 1 attitude with respect to inertial for an input datetime
fc1_attitude = spint.SpiceOrientation('J2000', 'DAWN_FC1')
# define a function that will return the framing camera 2 attitude with respect to inertial for an input datetime
fc2_attitude = spint.SpiceOrientation('J2000', 'DAWN_FC2')
# define a function that will return the dawn spacecraft attitude with respect to inertial for an input datetime
dawn_attitude = spint.SpiceOrientation('J2000', 'DAWN_SPACECRAFT')
# define a function that will return the spacecraft state in the inertial frame wrt SSB for a datetime
dawn_state = spint.SpiceState('DAWN', 'J2000', 'NONE', 'SSB')
# define a function that will return the spacecraft position in the inertial frame wrt SSB for a datetime
dawn_position = spint.SpicePosition('DAWN', 'J2000', 'NONE', 'SSB')
# define a function that will return the vesta body fixed attitude with respect to inertial for an input datetime
# GIANT needs this to be from body fixed to inertial
vesta_attitude = spint.SpiceOrientation('VESTA_FIXED', 'J2000')
# define a function that will return vesta's position and velocity in the inertial frame wrt SSB for a datetime
vesta_state = spint.SpiceState('VESTA', 'J2000', 'NONE', 'SSB')
# define a function that will return vesta's position in the inertial frame wrt SSB for a datetime
vesta_position = spint.SpicePosition('VESTA', 'J2000', 'none', 'SSB')
Installing dawn_giant¶
The final step in customizing GIANT is to install our dawn_giant
module to our python path. While this isn’t a
required step, it makes it easier to have access to all of the work we just did from whatever directory we want, so
it is strongly recommended. The easiest way to perform this step is using setuptools and a setup.py file.
In the dawn_giant
directory, create a file called setup.py
and open it with your favorite text editor. Then,
place the following code into the file
from setuptools import setup
setup(
name='dawn_giant',
version='1.0',
description='Dawn Customizations for GIANT',
py_modules=['dawn_giant'],
install_requires=[
'giant',
'numpy',
'spiceypy',
'pvl',
'bs4',
'requests'
]
)
This script simply tells python that we want to install our dawn_giant module so that it is always available. It also
lists the external requirements that need to be installed for this file to work. If you’ve been following along to this
point then most of these requirements are already installed, with the exception of pvl, which we discussed above. The
nice thing is, when we run the setup.py
script, python will install pvl for us.
Now, be sure that your giant_env
is activated and then run python setup.py develop
from the dawn_giant
directory in order to install the dawn_giant
package. To test this install, simply cd to any other directory,
start an interactive python shell, and then try import dawn_giant
. This should complete successfully without any
errors.
And that is it, we have successfully customized GIANT to work for the DAWN mission and now we can move on to doing some actual processing.
The Full dawn_giant File¶
For your convenience the full dawn_giant.py
file is presented here
# the GIANT classes we need to customize
from giant.image import OpNavImage
from giant.camera import Camera
# the attitude object that represents rotations in GIANT
from giant.rotations import Rotation
# a GIANT module for working with spice
import giant.utilities.spice_interface as spint
# a standard library module that lets us query and manipulate file paths
import os
# A module to provide access to the NAIF Spice routines
import spiceypy as spice
from spiceypy.utils.support_types import SpiceyError
# a standard library for representing dates and time deltas
from datetime import timedelta
# a library for parsing label files.
import pvl
class DawnFCImage(OpNavImage):
def parse_data(self):
# be sure the image file exists
if os.path.exists(self.file):
# get the extension from the file
_, ext = os.path.splitext(self.file)
# replace the extension from the file with LBL to find the corresponding label file
lbl_file = self.file.replace(ext, '.LBL')
# check to see if the label file exists
if os.path.exists(lbl_file):
# read the label file
with open(lbl_file, 'r') as lfile:
# pvl treats \ as escape characters so we need to replace them
data = pvl.loads(lfile.read().replace('\\', '/'))
# extract the exposure time from the label and convert from ms to seconds
self.exposure = data["EXPOSURE_DURATION"].value / 1000
# set the exposure type based off of the exposure length to make handling long/short opnav sequences
# easier. This is typically camera specific and needs to be set by an analyst
if self.exposure > 1:
self.exposure_type = "long"
else:
self.exposure_type = "short"
# extract the observation observation_date (middle of the exposure time of the image)
self.observation_date = data["START_TIME"].replace(tzinfo=None) + timedelta(seconds=self.exposure / 2)
# get the temperature of the camera for this image
self.temperature = data["DAWN:T_LENS_BARREL"].value - 273.15 # convert kelvin to celsius
# get the quaternion of the rotation from the inertial frmame to the camera frame
# store the rotation as an attitude object. Need to move the scalar term last
self.rotation_inertial_to_camera = Rotation(data["QUATERNION"][1:] + data["QUATERNION"][0:1])
# get the target
self.target = data["TARGET_NAME"]
if self.target == "N/A":
self.target = None # notify that we don't have a target here
else:
self.target = self.target.split()[0] # throw out the target number and just keep the name
# get the instrument name (spice instrument frame name)
if data["INSTRUMENT_ID"] == "FC2":
self.instrument = "DAWN_FC2"
else:
self.instrument = "DAWN_FC1"
# set the spacecraft the camera belongs to
self.spacecraft = "DAWN"
# set the saturation value for this image
self.saturation = 2 ** 16 - 1
# query spice to get the camera position and velocity in the inertial frame,
# as well as the sun direction in the camera frame
# first convert the observation observation_date to ephemeris time
try:
et = spice.str2et(self.observation_date.isoformat())
except SpiceyError:
et = 0
print('unable to compute ephemeris time for image at time {}'.format(
self.observation_date.isoformat()))
# get the position and velocity of the camera (sc) with respect to the solar system bary center
try:
state, _ = spice.spkezr(self.spacecraft, et, 'J2000', 'NONE', "SSB")
self.position = state[:3]
self.velocity = state[3:]
except SpiceyError:
print('Unable to retrieve camera position and velocity \n'
'for {0} at time {1}'.format(self.instrument, self.observation_date.isoformat()))
else:
raise ValueError("we can't find the label file for this image so we can't parse the data."
"Looking for file {}".format(lbl_file))
class DawnFCCamera(Camera):
# update the init function to use the new DawnFCImage class instead of the default OpNavImage class
def __init__(self, images=None, model=None, name=None, spacecraft_name=None,
frame=None, parse_data=True, psf=None, attitude_function=None, start_date=None, end_date=None,
default_image_class=DawnFCImage):
super().__init__(images=images, model=model, name=name, spacecraft_name=spacecraft_name,
frame=frame, parse_data=parse_data, psf=psf,
attitude_function=attitude_function, start_date=start_date, end_date=end_date,
default_image_class=default_image_class)
def preprocessor(self, image):
# here we might apply corrections to the image (like flat fields and darks) or we can extract extra
# information about the image and store it as another attribute (like dark_pixels which can be used to
# compute the noise level in the image. For the DAWN framing cameras though, we don't need to do anything
# so we just return the image unmodified.
return image
# convenience functions
def sun_orientation(*args):
# always set the sun orientation to be the identity rotation (J2000) because it doesn't actually matter
return Rotation([0, 0, 0])
# define a function that will return the sun position in the inertial frame wrt SSB for a datetime
sun_position = spint.SpicePosition('SUN', 'J2000', 'NONE', 'SSB')
# define a function that will return the framing camera 1 attitude with respect to inertial for an input datetime
fc1_attitude = spint.SpiceOrientation('J2000', 'DAWN_FC1')
# define a function that will return the framing camera 2 attitude with respect to inertial for an input datetime
fc2_attitude = spint.SpiceOrientation('J2000', 'DAWN_FC2')
# define a function that will return the dawn spacecraft attitude with respect to inertial for an input datetime
dawn_attitude = spint.SpiceOrientation('J2000', 'DAWN_SPACECRAFT')
# define a function that will return the spacecraft state in the inertial frame wrt SSB for a datetime
dawn_state = spint.SpiceState('DAWN', 'J2000', 'NONE', 'SSB')
# define a function that will return the spacecraft position in the inertial frame wrt SSB for a datetime
dawn_position = spint.SpicePosition('DAWN', 'J2000', 'NONE', 'SSB')
# define a function that will return the vesta body fixed attitude with respect to inertial for an input datetime
# GIANT needs this to be from body fixed to inertial
vesta_attitude = spint.SpiceOrientation('VESTA_FIXED', 'J2000')
# define a function that will return vesta's position and velocity in the inertial frame wrt SSB for a datetime
vesta_state = spint.SpiceState('VESTA', 'J2000', 'NONE', 'SSB')
# define a function that will return vesta's position in the inertial frame wrt SSB for a datetime
vesta_position = spint.SpicePosition('VESTA', 'J2000', 'none', 'SSB')