Loading Data in Python

ACISpy can work with ACIS-related data from the commanded states database, the engineering archive, or thermal models produced by Xija. The first thing that must be done before any data can be worked with or plotted is to ingest the data in a Dataset object. Dataset objects can be created by a number of methods, which are documented here.

Fetching MSID Data from the Engineering Archive

Often, one will want to fetch data directly from the Chandra engineering archive and the commanded states database within a particular date and time range. The EngArchiveData class enables this functionality. The MSIDs you want to extract must be specified. The full set of commanded states are also loaded.

from acispy import EngArchiveData
tstart = "2016:091:01:00:00.000"
tstop = "2016:097:03:30:57.231"
msids = ["1deamzt", "1dpamzt"]
ds = EngArchiveData(tstart, tstop, msids)

By default, the MSIDs are at identical time values of 328 s (or roughly 5 min). You can change this to daily values by setting the stat keyword argument to "daily" or to the values at every time available by setting stat=None:

ds = EngArchiveData(tstart, tstop, msids, stat=None)

Additional options are provided for filtering out bad data and choosing the time cadence for MSIDs; for details see the API doc entry for EngArchiveData.

Fetching MSID Data From a Tracelog File

If you have a real-time tracelog file or one that has been extracted from a dump, you can also read MSID data from this file, using TracelogData. In this case, the state data corresponding to the times spanned by the tracelog file will be extracted from the commanded states database.

from acispy import TracelogData
ds = TracelogData("acisENG10d_00985114479.70.tl")

In this case, all of the MSIDs in the tracelog are ingested into the TracelogData dataset. The full set of commanded states are also loaded. You can also specify a subset of the time range in the tracelog using the tbegin and tend arguments:

from acispy import TracelogData
ds = TracelogData("some_data.tl", tbegin="2017:100", tend="2017:110:01:45:45")

Special Tracelog Files

There are three special classes for working with the 10-day tracelog file data, which an be used to obtain the data from these tracelog files which are updated every time there is a comm and have the last 10 days of data. They are:

You do not have to specify the tracelog file for these classes, but they will accept any other arguments also accepted by TracelogData:

from acispy import EngineeringTracelogData
ds = EngineeringTracelogData(tbegin="2018:060:00:00:00", tend="2018:061:02:30:00")

Fetching MSID Data from MAUDE

ACISPy can also access data from the MAUDE telemetry server. You must set up authentication to OCCWEB, for which there is some documentation here.

To access data from MAUDE, simply use the MaudeData class and provide a starting time, stopping time, and the list of MSIDs that you want. State data will be accessed using the commanded states database automatically.

from acispy import MaudeData
datestart = "2017:336:12:00:00"
datestop = "2017:337:12:00:00"
msids = ["1dpamzt", "1deamzt"]
ds = MaudeData(datestart, datestop, msids)

Reading Model Data from a Load

You can also fill a Dataset with predicted model data for a particular temperature model or multiple models corresponding to a particular load review using ThermalModelFromLoad:

from acispy import ThermalModelFromLoad
comps = ["1deamzt","1dpamzt","fptemp_11"]
ds = ThermalModelFromLoad("APR0416C", comps)

To get the corresponding MSIDs from the engineering archive during the same time frame, pass to ThermalModelFromLoad the keyword argument get_msids=True.

Reading Model Data from Files

The model validation tools (such as dea_check) output ASCII table files "temperatures.dat" and "states.dat" that contain the temperature and commanded state information as a function of time. If you have these files and would like to load them in, this can be done using :ThermalModelFromFiles:

from acispy import ThermalModelFromFiles
model_files = ["dea_model/temperatures.dat", "dpa_model/temperatures.dat",
               "fp_model/temperatures.dat"]
ds = ThermalModelFromFiles(model_files, "dea_model/states.dat", get_msids=True)

Like the previous Dataset type, this one takes the get_msids keyword argument to obtain the corresponding MSIDs from the archive if desired.

This Dataset type can also be used to import model data for the same MSID for different model runs:

from acispy import ThermalModelFromFiles
model_files = ["old_model/temperatures.dat", "new_model/temperatures.dat"]
ds = ThermalModelFromFiles(model_files, "old_model/states.dat", get_msids=True)

Directly Accessing Time Series Data from the Container

The Dataset object has dictionary-like access so that the data may be accessed directly. Data can be accessed by querying the Dataset object with a tuple giving the type of data desired and its name, for example:

# "ds" is a Dataset object
ds["states", "pitch"] # gives you the "pitch" state
ds["msids", "fptemp_11"] # gives you the "fptemp_11" pseudo-MSID
ds["model", "1deamzt"] # gives you the "1deamzt" model component

A (type, name) pairing and its associated data are referred to as a “field”. We’ll encounter examples of derived-fields later, which are derivations of new fields from existing ones.

It is not strictly necessary to specify the (type, name) tuple if the name is unique in the Dataset object. So the fields in the last block could also be accessed like this:

ds["pitch"] # gives you the "pitch" state
ds["fptemp_11"] # gives you the "fptemp_11" pseudo-MSID
ds["1deamzt"] # gives you the "1deamzt" model component

However, if the name is not unique (say it exists both as MSID data and a model component), then an error will be raised:

# "ds" is a Dataset object
ds["pitch"] # gives you the "pitch" state
ds["fptemp_11"] # gives you the "fptemp_11" pseudo-MSID
ds["1deamzt"] # gives you the "1deamzt" model component

We’ll use our example from before to fill up a Dataset:

from acispy import EngArchiveData
tstart = "2016:091:01:00:00.000"
tstop = "2016:097:03:30:57.231"
msids = ["1deamzt", "1dpamzt"]
ds = EngArchiveData(tstart, tstop, msids)

To see what fields are available from the Dataset, check the field_list attribute:

print(ds.field_list)
[('msids', '1deamzt'),
 ('msids', '1dpamzt'),
 ('states', 'datestart'),
 ('states', 'datestop'),
 ('states', 'tstart'),
 ('states', 'tstop'),
 ('states', 'q1'),
 ...
 ('states', 'q3'),
 ('states', 'q2'),
 ('states', 'q4'),
 ('states', 'pitch'),
 ('states', 'ccd_count')]

If you have loaded data for the same model component from more than one model, then these will appear in the Dataset with field types of the form "model[n]", where n is a a zero-based integer:

from acispy import ThermalModelFromFiles
model_files = ["old_model/temperatures.dat", "new_model/temperatures.dat"]
ds = ThermalModelFromFiles(model_files, "old_model/states.dat", get_msids=True)
print(ds.field_list)

gives:

[('model0', '1pdeaat'),
 ('model1', '1pdeaat'),
 ('states', 'q1'),
 ('states', 'q3'),
 ('states', 'q2'),
 ('states', 'q4'),
 ...
 ('states', 'pitch'),
 ('states', 'ccd_count')]

ACISpy Arrays

Data are returned as “ACISpy arrays”, which are simply NumPy arrays with a number of important attributes included.

Units

One such attribute is units, for those quantities which possess them. Units are added to ACISpy arrays using AstroPy Quantities. The following print statements illustrate how units are attached to various types of arrays:

print(ds["ccd_count"])
print(ds["pitch"])
print(ds["1deamzt"])
[6  6  6 ...,  4  4  4]

[ 155.78252178  155.94230537  155.95272431  ...,  142.85889318
  148.43712545  149.54367736] deg

[ 22.14923096  22.14923096  22.14923096 ...,  20.17999268
  20.17999268  20.17999268] deg_C

Note that some arrays (like ccd_count) do not have units.

Masks

Model data may include “bad times” where the model does not agree well with the actual telemetry, most likely because there was an unexpected event such as a safing action. All ACISpy arrays include a mask attribute, which is a boolean NumPy array the same shape as the array, which is True if the array is well-defined at that time and False if it is not. Currently, masks only have False values for model arrays:

print(ds["1dpamzt"].mask)
[ True  False  False  False ...,  True  True  True]

In future versions, masks will be also included for MSID data which have known “bad” values at certain times.

Timing Information

Since the MSIDs and states are defined at given times, each ACISpy array has timing information associated with it. The times attribute for a given array gives the timing information in seconds from the beginning of the mission:

print(ds["pitch"].times)
print(ds["1deamzt"].times)

prints something like:

[[  5.75763786e+08   5.75775250e+08   5.75775555e+08   5.75775860e+08
    5.75776165e+08   5.75776470e+08   5.75776775e+08   5.75777080e+08
    ...
    5.76285868e+08   5.76286168e+08   5.76286301e+08   5.76286325e+08
    5.76286469e+08   5.76286769e+08   5.76287070e+08   5.76287370e+08]
 [  5.75775250e+08   5.75775555e+08   5.75775860e+08   5.75776165e+08
    5.75776470e+08   5.75776775e+08   5.75777080e+08   5.75777385e+08
    ...
    5.76286168e+08   5.76286301e+08   5.76286325e+08   5.76286469e+08
    5.76286769e+08   5.76287070e+08   5.76287370e+08   5.76330630e+08]] s

 [  5.75773267e+08   5.75773300e+08   5.75773333e+08 ...,   5.76300659e+08
    5.76300691e+08   5.76300724e+08] s

Note that state times are two-dimensional arrays, of shape (2, n), since each state spans a tstart and a tstop.

Similiarly, the dates attribute contains the same information in terms of date-time strings:

print(ds["pitch"].dates)
[['2016:090:22:21:58.350' '2016:091:01:33:03.014' '2016:091:01:38:07.997'
  '2016:091:01:43:12.980' '2016:091:01:48:17.963' '2016:091:01:53:22.946'
  ...
  '2016:096:23:30:33.579' '2016:096:23:30:57.579' '2016:096:23:33:21.437'
  '2016:096:23:38:21.901' '2016:096:23:43:22.366' '2016:096:23:48:22.830']
 ['2016:091:01:33:03.014' '2016:091:01:38:07.997' '2016:091:01:43:12.980'
  '2016:091:01:48:17.963' '2016:091:01:53:22.946' '2016:091:01:58:27.929'
  ...
  '2016:096:23:30:57.579' '2016:096:23:33:21.437' '2016:096:23:38:21.901'
  '2016:096:23:43:22.366' '2016:096:23:48:22.830' '2016:097:11:49:22.579']]

Indexing and Slicing ACISpy Arrays

ACISpy arrays can be sliced and indexed using integers to access subsets of arrays in the usual way:

ds["1pdeaat"][1]
ds["ccd_count"][2:100]

However, it is also possible to index and slice arrays with timing information, whether with floating-point numbers (corresponding to seconds from the beginning of the mission) or date-time strings:

ds["pitch"][5.762e8] # indexing with a single time value

ds["1deicacu"][5.5e8:5.6e8] # slicing between two time values

ds["fep_count"]["2016:091:03:25:40.500"] # indexing with a single date-time string

ds["1pin1at"]["2017:050:00:00:00":"2017:060:00:00:00"] # slicing between two date-time strings

Timing Information

The timing data for each model component, MSID, and state can also be easily accessed from the times() and dates() methods:

print(ds.times('1deamzt'))
[  5.75773267e+08   5.75773300e+08   5.75773333e+08 ...,   5.76300659e+08   5.76300691e+08   5.76300724e+08] s
times = ds.times('pitch')
times[0] # Gives you the start times
times[1] # Gives you the stop times
print(ds.dates('1deamzt'))
['2016:091:01:00:00.222', '2016:091:01:00:33.022',
 '2016:091:01:01:05.822', ..., '2016:097:03:29:51.452',
 '2016:097:03:30:24.252', '2016:097:03:30:57.052']