Adding an Instrument Interface

To add an instrument interface, one needs to implement a subclass of the abstract class pyklip.instruments.Instrument.Data, overriding the abstract methods and fields required by the class. Some fields may not not be relevant a particular instrument (e.g. wavelengths for a broadband instrument with only one channel) and we will provide suggestions on what to default the value to.

For a simple example, look at how pyklip.instruments.Instrument.GenericData implements the interface.

Now we will discuss what are the necessary steps to make your own instrument class.

Extending Instrument.Data

The first thing we need to do is create a new object that is a subclass of pyklip.instruments.Instrument.Data. To do this, we specify our new class inherits pyklip.instruments.Instrument.Data and we need to call the __init__() function of the super class (i.e. pyklip.instruments.Instrument.Data) as the first step of our new __init__() function.

import pyklip.instruments.Instrument.Data as Data

class MyInstrument(Data):
    """
    This is my new instrument class
    """
    # Constructor
    def __init__(self, args):
        # initialize the super class
        super(MyInstrument, self).__init__()

        # run some more initialization code here

    # the rest of the class goes here

Fields

For each required field, we use the following syntax to implement each required field. The field itself is just a wrapper for an internal field named _field, which should be set by the __init__() function. This is a little clunky, but allows python to ensure these fields are implemented. You should have one of the following code blocks for each required field:

@property
def field(self):
    return self._field
@input.setter
def field(self, newval):
    self._field = newval

Here are the fields that need to be implemented (some are optional and are marked as such). For the optional fields, you do not need to use the previous code block and make getter/setter methods; you can just set them like you normally set attributes.

input

This is the input data. It should be a numpy array of dimensions (Nframes, y, x). For dual-band or IFS data, the wavelength dimension should be merged into the total number of files so that Nframes = Nwvs x Nfiles. Basically, this should always be a 3-D cube, and we will use other bookkeeping fields to track the wavelengths and filenames for each frame.

output

This is where the output data gets stored. For initialization, set this equal to None so that the variable is defined. Othewise, you can expect that after pyKLIP, the 5-D output cube gets stored here with dimension (KL, Nfiles, Nwvs, y, x) where KL is the numbe of KL cutoffs you specified, Nfiles is the number of unique filenames, Nwvs is the number of unique wavelengths.

fmout (Optional)

This is where the output of the forward modelling is stored (unless otherwise noted by the specific KLIP-FM library). Refer to each KLIP-FM feature for how to make sense of this data.

centers

This is the image centers for each input frame of data. It should be a numpy array of dimensions (Nframes, 2) where the second dimension is the (x,y) center for that frame. This is required for all datasets.

filenames

This is an array of filenames that correspond to each frame. Depending on how the data is formatted, filenames can be duplicated so that more than one frame has the same filename (e.g. each frame in an IFS datacube). For RDI, filenames are required, so that the PSF library will exclude the science frames from the PSF library that was built. If you really don’t care about them, set them to something generic (e.g. for the first frame, “0.fits”)

filenums

This is an array of file numbers so that each filename corresponds to a certain number. This allows for easier manipulating of frames, since it is easier to sort and slice numbers than strings. For easy implementation, make the first filename corespond to 0, the second correspond to 1, and so on.

wvs

This is an array of wavelengths, required for SDI to figure out how to rescale the speckles. If you are working with broadband data or generally wavelength agnostic, make this an array of the same number (e.g. use the central wavelength of the filter or set it all to 1).

PAs

This is an array of angles for each frame. This is defined as the angle needed to rotate the image north up, which means it is a combination of the parallactic angle (angle from North to zenith in the direction towards East) and any instrumental angles (e.g. the angle from the image to zenith). For ADI, PAs are required to determine field rotation. If you don’t know or don’t want image rotation, set this to an array of 0 with length equal to the number of frames.

flipx (Optional)

This specifies a boolean that at the end, when derotating and stacking the images, whether to flip the x-axis. By default, this is set to True. In the end we want to rotate images North-up East-left (i.e. East CCW of North). If your image starts out with East clockwise of North, then flipx should be set to True. Otherwise, set it to False.

wcs

This is an array of astropy.wcs.WCS objects that specifies the orientation of each input image. Since pyKLIP primiarily uses PAs and flipx to figure out image orientation, this keyword isn’t strictly necessary, but could be good to have (e.g. pyklip.fakes uses it for fake planet injection, and it is generally nice to have in your final PSF subtracted images). Note that WCS objects have the method deepcopy which allows you to replicate WCS headers, so if you have multiple frames that share the same WCS, it is an easy to to give each frame its own WCS info. If you don’t have WCS info or don’t want to deal with it, set wcs to an array of None with length equal to the number of frames.

IWA

This is the inner working angle (radius, in pixels) of your data. pyKLIP will not reduce this part of your data and instead mask it as NaNs. If you don’t have an inner working angle well defined for your instrument, make this a parameter the user could pass in, guess one, or set it to 0. Note that this is a single number and not an array.

OWA (Optional)

This is the outer working angle for your data. By default this is None, and pyKLIP will use either the closest NaN to the center of the first image, or (if there are no NaNs) the edge of the image as the outer working angle. This is also a single number.

output, output_centers, output_wcs (Required-ish)

These fields corresponds to the output data, an array of dimensions (KLcutoffs, Nframes, y, x), the (x,y) center for each output frame, an array of dimensions (Nframes, 2), and an array of WCS headers corresponding to the output images, which are typically rotated North-up and East-left. The one you must explicitly define in your class is output, but you should expect the other two fields to also get populated after a KLIP reduction, so it could be useful to refer to those fields in savedata. Note that if you pass an array of None to wcs, output_wcs will also be an array of None. Also note that output_center is the same (x,y) coordinate repeated for each frame since the images are aligned together after KLIP.

Methods

Here are some required methods that need to be implemented. Of them, you definely want to make sure savedata() works.

readdata(self, filepaths)

This function should be able to read in files from a list of filenames, compile them together, and set up the fields for this dataset. Typically this function is called by the __init__() function. If your data doesn’t come in a form where reading it in like this is a very elegant solution, feel free to skip this function (by implementing this funciton with pass as the only command) and writing a different way to initalize your dataset.

savedata(self, filepath, data, klipparams=None, filetype="", zaxis=None, more_keywords=None)

This is the most important function to implement since it saves your pyKLIP reductions. The filepath is where to save the file to, and the data is what to save. klipparams is a string listing all of the pyKLIP parameters, and is typically saved into the histroy of a FITS file. filetype tells you the type of datacube this data is (i.e. “KL Mode Cube”, “PSF Subtracted Spectral Cube”). For data with just one wavelength, the data will only be KL Mode cubes where the third dimension is the KL mode cutoff. zaxis is used for KL Mode cubes to specify the KL mode cutoffs for each slice. more_keywords is additional keywords to save into the header for bookkeeping.

calibrate_output(self, img, spectral=False)

This handles the flux calibration of the image passed in via img. For spectral data cubes (i.e. the third dimension is wavelength), then the spectral flag is set to true.