Tutorial ======== For the impatient, see the `Quickstart`_ section below. To take full advantage of all the capabilities of annie, continue on through the rest of the tutorial as well. Quickstart ----------- The very simplest way to run an annie simulation is to take some or all of the simulation parameters from an existing or planned observation via the starcheck catalog. This is done with the :func:`~annie.annie.run_from_starcheck` function:: >>> from annie import run_from_starcheck >>> obsid = 20201 >>> stop = 200 # seconds. Can also do `stop = 200 * u.s` with astropy units. >>> sc = run_from_starcheck(obsid, verbose=True, stop=stop) Running annie with: {'att_cmd': [193.228633, -63.884565, 39.69144], 'ccd_bgd_data': '2018:051:02:57:08.203', 'dither': {'pitch_ampl': 8.0, 'pitch_period': 707.1, 'pitch_phase': 0.0, 'yaw_ampl': 8.0, 'yaw_period': 1000.0, 'yaw_phase': 0.0}, 'starcat': <Table length=12> sc_id obsid idx slot id idnote ... yang zang dim res halfw pass notes int64 int64 int64 int64 int64 object ... int64 int64 int64 int64 int64 str2 str2 ----- ----- ----- ----- ---------- ------ ... ----- ----- ----- ----- ----- ---- ----- 2116 20201 1 0 1 None ... 919 -844 1 1 25 -- -- 2116 20201 2 1 5 None ... -1828 1053 1 1 25 -- -- 2116 20201 3 2 6 None ... 385 1697 1 1 25 -- -- 2116 20201 4 3 1178736784 None ... 2004 -622 28 1 160 a2 -- 2116 20201 5 4 1178737152 None ... 775 1438 32 1 180 -- -- 2116 20201 6 5 1178737496 None ... 1219 123 32 1 180 -- -- 2116 20201 7 6 1179259616 None ... -2112 -1949 28 1 160 a2 -- 2116 20201 8 7 1178736896 None ... 2420 -905 1 1 25 -- -- 2116 20201 9 7 1179257400 None ... 33 544 32 1 180 -- -- 2116 20201 10 0 1179257016 None ... -8 -2234 29 1 165 -- -- 2116 20201 11 1 1179257288 None ... 942 -257 28 1 160 a2 -- 2116 20201 12 2 1179127856 None ... -1782 -21 28 1 160 a2 --, 'stop': 200, 't_ccd': -11.4} >>> The :func:`~annie.annie.run_from_starcheck` function can also take as input the starcheck dict returned from `get_starcheck_catalog() <http://cxc.cfa.harvard.edu/mta/ASPECT/tool_doc/mica/api.html#mica.starcheck.get_starcheck_catalog>`_, or the text copied from the starcheck report. In the latter case one could easily update catalog parameters to evaluate a potential catalog update. In addition one can override any of the :meth:`~annie.annie.Spacecraft.track_stars_setup` parameters for the observation, for instance simulate this observation at a higher CCD temperature:: >>> sc = run_from_starcheck(obsid, t_ccd=-5.0, stop=stop) Now to look at the results, see the section on `Exploring annie's telemetry`_. Creating spacecraft subsystems ------------------------------ All spacecraft subsystems required to run a simulation are created using the :class:`~annie.annie.Spacecraft` master controller:: >>> from annie.annie import Spacecraft >>> sc = Spacecraft() The main s/c subsystems include the :class:`~annie.clock.Clock`, :class:`~annie.sky.Sky`, :class:`~annie.aca.ACA`, :class:`~annie.aca.CCD`, :class:`~annie.pcad.PCAD` and :class:`~annie.telem.Telem` objects:: >>> sc.__dict__ {'aca': <annie.aca.ACA at 0x7f50a85ca630>, 'ccd': <annie.aca.CCD at 0x7f50a85c4198>, 'clock': <Clock secs=0.000 ticks=0>, 'logger': <Logger root (CRITICAL)>, 'loglevel': 50, 'pcad': <annie.pcad.PCAD at 0x7f50a85ca7f0>, 'sky': <annie.sky.Sky at 0x7f50a85ca390>, 'start': 0, 'stop': 100, 'telem': <annie.telem.Telem at 0x7f50a85ca438>} Internal clock -------------- The annie clock units are as follows: =========== ========== ======= ============ Name Attribute Ticks Seconds =========== ========== ======= ============ Clock tick ``tick`` 1 0.016015625 Minor cycle ``mnc`` 1 0.016015625 Minor frame ``mnf`` 16 0.25625 Frame ``frame`` 64 1.025 Major frame ``mjf`` 2048 32.8 =========== ========== ======= ============ One frame lasts 1.025 seconds and there are 64 clock ticks per frame:: >>> from annie.clock import TICKS_PER_FRAME >>> print(TICKS_PER_FRAME) 64 The relative durations of these clock units is available in the ``TICKS_PER`` module dictionary:: >>> from annie.clock import TICKS_PER >>> print(TICKS_PER.keys()) dict_keys(['mnc', 'mnf', 'mjf', 'frame', 'sec']) >>> print(TICKS_PER['mnf']) 16 >>> print(TICKS_PER['sec']) 62.43902439024391 The clock module contains two classes, :class:`~annie.clock.ClockTime` and :class:`~annie.clock.Clock`. ClockTime ^^^^^^^^^ The :class:`~annie.clock.ClockTime` class provides functionality to convert Date or DateTime objects into the internal annie clock time:: >>> from annie.clock import ClockTime >>> import astropy.units as u >>> ct = ClockTime(15) # Initialize in ticks >>> ct <ClockTime secs=0.240, ticks=15> >>> ClockTime(32.8 * u.s) # Initialize in seconds <ClockTime secs=32.800, ticks=2048> >>> ClockTime('2017:001') # Absolute date <ClockTime secs=599616069.191 ticks=37439442369> It also provides functionality to manipulate the internal time, e.g. derive the time at the next unit:: >>> ct.at_next('frame') <ClockTime secs=1.025, ticks=64> or derive the number of ticks that have passed since the start of a given unit:: >>> ct = ClockTime(41 * u.s) >>> ct <ClockTime secs=41, ticks=2560> >>> ct.ticks_mod('mjf') 512 # 2560 - 2048 or perform arithmetic operations on the :class:`~annie.clock.ClockTime` objects:: >>> ct_delta = ClockTime(2) >>> ct + ct_delta <ClockTime secs=41.032, ticks=2562> Clock ^^^^^ The :class:`~annie.clock.Clock` class controls the internal clock. It inherits from the :class:`~annie.clock.ClockTime` class. It also inherits from the :class:`~annie.subsystem.SubSystem` class, and thus annie's clock is one of the spacecraft subsystems created within the :class:`~annie.annie.Spacecraft` master controller for the simulation:: >>> from annie.clock import Clock >>> from annie.annie import Spacecraft >>> import astropy.units as u >>> sc = Spacecraft() >>> c = sc.clock >>> c.start = 0 # ticks >>> c # Clock object <Clock secs=0.000, ticks=0> >>> c.start # ClockTime object <ClockTime secs=0.00, ticks=0> The end of the simulation is specified by setting the ``stop`` attribute:: >>> c.stop = 100 * u.s # Ticks or delta time via astropy units The simulation progresses via successive calls to the :meth:`~annie.clock.Clock.tick` method until the ``stop`` time is reached, at which point the method returns ``False``. >>> # The first tick() method call initializes/starts the clock >>> c.tick() True >>> c <Clock secs=0.000, ticks=0> >>> >>> # The next and following tick() calls move the clock by 1 tick >>> c.tick() True >>> c <Clock secs=0.016, ticks=1> Scheduling actions ------------------ Tasks ^^^^^ In each subsystem there are certain tasks that are executed regularly by the generic subsystem processing called every tick (:meth:`annie.subsystem.SubSystem.process`, see :meth:`annie.annie.Spacecraft.run` and :ref:`Running-an-annie-simulation`). Information about these tasks can be accessed through the tasks attribute, which is a list of tuples containing method, time unit, and tick offset within this time unit. For example, the PCAD subsystem performs attitude updates at the start of every minor frame and main processing at the start of every frame, while the ACA subsystem performs main processing one tick (one minor cycle) after the start of each frame:: >>> sc.pcad.tasks [('update_att', 'mnf', 0), ('main_processing', 'frame', 0)] >>> sc.aca.tasks [('main_processing', 'frame', 1)] Commands ^^^^^^^^ Commands executed by the subsystems are scheduled using the subsystem's :meth:`~annie.subsystem.SubSystem.command` method which adds the new commands to the subsystem's :attr:`~annie.subsystem.SubSystem.commands` dictionary:: >>> sc.pcad.commands defaultdict(list, {}) >>> sc.pcad.pending_commands [] >>> sc.pcad.command('set_att_cmd', 0 * u.s, att=[0., 0., 0.]) >>> sc.pcad.command('set_att_cmd', 100 * u.s, att=[10., 20., 0.]) >>> sc.pcad.commands defaultdict(list, {<ClockTime secs=0.016 ticks=1>: [('set_att_cmd', {'att': [0.0, 0.0, 0.0]})], <ClockTime secs=100.018 ticks=6245>: [('set_att_cmd', {'att': [10.0, 20.0, 0.0]})]}) >>> sc.pcad.pending_commands [] >>> print(sc.pcad.att_record.att_cmd) None The commands to be executed at this time are then added to the subsystem's :attr:`~annie.subsystem.SubSystem.pending_commands` list by the generic subsystem processing called every tick (:meth:`~annie.subsystem.SubSystem.process`, see :ref:`Running-an-annie-simulation`):: >>> sc.clock <Clock secs=0.016, ticks=1> >>> sc.pcad.process() >>> sc.pcad.commands defaultdict(list, {<ClockTime secs=0.016 ticks=1>: [('set_att_cmd', {'att': [0.0, 0.0, 0.0]})], <ClockTime secs=100.018 ticks=6245>: [('set_att_cmd', {'att': [10.0, 20.0, 0.0]})]}) >>> sc.pcad.pending_commands [('set_att_cmd', {'att': [0.0, 0.0, 0.0]})] >>> print(sc.pcad.att_record.att_cmd) None Finally, the commands are executed at their scheduled time as part of the subsystem's regularly scheduled tasks (e.g. :meth:`annie.pcad.PCAD.main_processing` which calls subsystem's :meth:`~annie.subsystem.SubSystem.execute_pending_commands` method). This removes the command from the pending commands list. However, the command is kept in the commands list which acts as a log of commands scheduled during the simulation:: >>> sc.pcad.main_processing() >>> sc.pcad.commands defaultdict(list, {<ClockTime secs=0.016 ticks=1>: [('set_att_cmd', {'att': [0.0, 0.0, 0.0]})], <ClockTime secs=100.018 ticks=6245>: [('set_att_cmd', {'att': [10.0, 20.0, 0.0]})]}) >>> sc.pcad.pending_commands [] >>> print(sc.pcad.att_record.att_cmd) <Quat q1=0.00000000 q2=-0.00000000 q3=0.00000000 q4=1.00000000> The next scheduled command gets processed and executed when the clock reaches its scheduled time:: >>> sc.clock.ticks = 6245 >>> sc.pcad.process() >>> sc.pcad.commands defaultdict(list, {<ClockTime secs=0.016 ticks=1>: [('set_att_cmd', {'att': [0.0, 0.0, 0.0]})], <ClockTime secs=100.018 ticks=6245>: [('set_att_cmd', {'att': [10.0, 20.0, 0.0]})]}) >>> sc.pcad.pending_commands [('set_att_cmd', {'att': [10.0, 20.0, 0.0]})] >>> sc.pcad.main_processing() >>> sc.pcad.commands defaultdict(list, {<ClockTime secs=0.016 ticks=1>: [('set_att_cmd', {'att': [0.0, 0.0, 0.0]})], <ClockTime secs=100.018 ticks=6245>: [('set_att_cmd', {'att': [10.0, 20.0, 0.0]})]}) >>> sc.pcad.pending_commands [] >>> print(sc.pcad.att_record.att_cmd) <Quat q1=0.01513444 q2=-0.17298739 q3=0.08583165 q4=0.98106026> Events ^^^^^^ Events to be executed at a given time are added to the subsystem's events queue (:attr:`annie.subsystem.SubSystem.events`) using the subsystem's :meth:`annie.subsystem.SubSystem.add_event` method:: >>> # Schedule a `flush` event (change the ccd status to `flush`) >>> sc.clock.ticks = 0 # reset the clock for the purpose of this example >>> sc.ccd.events defaultdict(list, {}) >>> sc.ccd.add_event('flush', sc.clock + 1 * u.mnc) >>> sc.ccd.events defaultdict(list, {<ClockTime secs=0.016 ticks=1>: [('flush', {})]}) The events in the events queue are executed at their scheduled time by the generic subsystem processing called every tick (:meth:`annie.subsystem.SubSystem.process`, see :ref:`Running-an-annie-simulation`). The execution of the event does not remove the event from the events list which acts as a log of events scheduled for a given subsystem in the course of the simulation:: >>> sc.ccd.status `idle` >>> sc.clock.tick() >>> sc.clock <Clock secs=0.016 ticks=1> >>> sc.ccd.process() >>> sc.ccd.status `flush` >>> sc.ccd.events defaultdict(list, {<ClockTime secs=0.016 ticks=1>: [('flush', {})]}) .. _annie-setup: Setting up an annie simulation ------------------------------ Setting up an annie simulation requires providing a number of subsystem parameters and issuing relevant commands. This is most conveniently done using the :meth:`annie.annie.Spacecraft.track_stars_setup` method. By convention, simulations normally start at time=0, and the end of the simulation is defined with a `stop` parameter (which then also corresponds to the duration). Once the parameters are set, the :meth:`~annie.annie.Spacecraft.track_stars_setup` method calls PCAD's :meth:`annie.pcad.PCAD.track_guide_stars` method which commands ACA to search for the guide stars and start guiding. The simulation parameter fall into **four categories**: * Stars (sky and catalog), * Attitude, * Conditions, * Image and image processing. ---- .. |sky| image:: images/sky.png :width: 140px :align: middle .. |attitude| image:: images/attitude.png :width: 140px :align: middle .. |conditions| image:: images/conditions.png :width: 140px :align: middle .. |image| image:: images/image.png :width: 140px :align: middle .. list-table:: :header-rows: 0 :widths: 10 60 * - |sky| - * `stars_source`, `stars_data` - the stars available in the ACA field of view are fetched from the AGASC if ``stars_source='agasc'`` and ``stars_data=None``. Alternatively, the user may build an arbitrary synthetic sky star field, e.g. to test a scenario with a spoiler star nearby a guide star. In this case the setup should read ``stars_source='table'`` and `stars_data` should be an astropy Table with columns: ``id, yag, zag, mag``. * `starcat` parameter defines the catalog with the guide stars to be tracked. Should be supplied as an astropy Table. See also :ref:`Running-an-annie-simulation`. * - |attitude| - * `att_source`, `att_data` - by default the attitudes for an annie run are simulated (``att_source='simulation'``, ``att_data=None``) based on the initial (commanded) attitude, dither pattern and attitude update control law. However, for the sake of flight data validation, it is possible to use a list of on-board or ground quaternions (``att_source=('telemetry'|'ground')``, ``att_data=<list of quaternions>``) * `att_cmd` - The commanded attitude must be provided as ``att_cmd=[ra, dec, roll]`` * `att_est` - estimated attitude * `att_true` - true attitude * `dither` - The user may provide the dither pattern as a dictionary, or use the default (standard ACIS 8 arcsec dither). * - |conditions| - * `t_ccd_ref`, `t_ccd` - parameter `t_ccd_ref` is used to set the reference temperature corresponding to the conditions at the time of an ACA DCC ``ccd_bgd_source='DCC'``, or the temperature corresponding to a synthetic dark background (``ccd_bgd_source='Custom_1024x1024'``). The user may trigger temperature scaling of the background data by defining the CCD temeprature `t_ccd` * `sp_flags`, `ms_flags`, `dp_flags`, `ir_flags` - the user may provide various flags (SP - saturated pixel, MS - multiple stars, DP - defective pixel, IR - ionizing radiation) and test their impact on the ACA tracking the stars. The flags are provided as dicts of lists ordered by the slot number. * TODO: proper testing, simulating the flags. * - |image| - * `img_source`, `img_data` - by default the star images are simulated (``img_source='simulation'`` and ``img_data=None``). However, the user may choose to 'shine' the flight data for the purpose of comparing the simulation and the telemetry. In this case the input should read ``img_source='telemetry'`` and ``img_data=<list of flight images>`` * `ccd_bgd_source`, `ccd_bgd_data` - by default the simulator uses dark background derived from the ACA DCC map taken at the date nearest to the date of the simulation. However, the user may choose from the number of custom options to replace the default background with an arbitrary background (see :meth:`~annie.annie.Spacecraft.track_stars_setup` and :meth:`~annie.aca.CCD.set_ccd_bgd_data` for details) * `bgd_algorithm` - this parameter sets the background subtraction algorithm **Background setup examples**:: >>> # No background, will use ccd_bgd_data = 0. >>> bgd_algorithm = 'Constant' >>> ccd_bgd_source = None >>> # Constant background >>> bgd_algorithm = 'Constant' >>> ccd_bgd_source = 'Constant' >>> ccd_bgd_data = 20. >>> # Flight algorithm and bgd data >>> bgd_algorithm = 'FlightBgd' >>> ccd_bgd_source = 'telemetry' >>> ccd_bgd_data = <list of telemetered BGDAVG values> >>> # Flight algorithm and ACA DCC bgd data >>> bgd_algorithm = 'FlightBgd' >>> ccd_bgd_source = 'DCC' >>> ccd_bgd_data = '2017:100' # fetch the ACA DCC nearest to this date >>> # Dynamic bgd algorithm and ACA DCC bgd data >>> bgd_algorithm = 'DynamBgd' >>> % ccd_bgd_source = 'DCC' # use the default setting for ccd_bgd_data = the date of the simulation >>> # Dynamic bgd algorithm and custom 1024x1024 DCC map scaled with temperature >>> bgd_algorithm = 'DynamBgd' >>> ccd_bgd_source = 'Custom_1024x1024' >>> ccd_bgd_data = <custom 1024x1024 ndarray or ACAImage> >>> t_ccd_ref = -14 # indicate that the custom bgd map was computed at -14C >>> t_ccd = -5 # scale it with temperature to -5C .. _Running-an-annie-simulation: Running an annie simulation --------------------------- Once all parameters are set and the stars are found and identified, the simulation is ready to be performed. This is done using the :meth:`annie.annie.Spacecraft.run` method, which initializes the annie clock and processes all the s/c subsystems at each clock tick, until the clock reaches the stop time specified by the user. The **minimal setup** requires the following settings: * the stop time of the simulation (`stop` parameter), and * a star catalog (an astropy Table, `starcat` parameter) * a commanded attitude. With this minimum setup, the system will perform a full simulation (image and attitude data will be simulated). Image processing will be performed assuming the ACA DCC background taken at the date closest to the date of the simulation, and the flight algorithm will be used to subtract the background (average bgd value derived from the eight corner pixels):: >>> # Example 1 (This uses the get_att, get_starcat, and get_dither helper methods from mica.starcheck) >>> from annie.annie import Spacecraft >>> import astropy.units as u >>> from mica.starcheck import get_att, get_starcheck, get_dither >>> sc = Spacecraft() >>> stop = 20 * u.s >>> obsid = 8008 >>> sc.track_stars_setup(stop=stop, att_cmd=get_att(obsid), starcat=get_starcat(obsid), dither=get_dither(obsid)) >>> sc.run() or:: >>> # Example 2 >>> from annie.annie import Spacecraft >>> import astropy.units as u >>> from astropy.table import Table >>> sc = Spacecraft() >>> stop = 20 * u.s >>> starcat = Table.read(""" slot type sz mag maxmag yang zang halfw 0 BOT 8x8 10.1 11.5 0.0 0.0 120 1 BOT 8x8 10.1 11.5 1000.0 0.0 120 2 BOT 8x8 10.1 11.5 0.0 1000.0 120 3 BOT 8x8 10.1 11.5 -1000.0 0.0 120 """, format='ascii', guess=False) >>> sc.track_stars_setup(stop=stop, starcat=starcat, stars_source='table', stars_data=starcat, att_cmd=[0., 0., 0.]) >>> sc.run() Exploring annie's telemetry --------------------------- Telemetry is collected by the :mod:`~annie.telem` module by the means of regularly executed :attr:`~annie.telem.Telem.tasks` defined for the :class:`~annie.telem.Telem` class. These tasks include (see :ref:`telem-subsystem`): * :meth:`~annie.telem.Telem.main_processing` which is called every frame; it collects * the :class:`~annie.pcad.SlotRecord` telemetry for each slot and stores it in :attr:`annie.telem.Telem.slot_records`, * the :class:`~annie.aca.StarDataRecord` telemetry for each slot and stores it in :attr:`annie.telem.Telem.star_data_records`, * :meth:`~annie.telem.Telem.attitude_processing` which is called every minor frame; it collects the :class:`~annie.pcad.AttitudeRecord` telemetry and stores it in :attr:`annie.telem.Telem.att_records`. Telemetry tables ^^^^^^^^^^^^^^^^ The spacecraft ``telem`` object has three sub-attributes that contain most of the useful output from an annie simulation. These are mostly just tabularized versions the full telemetry described above. * ``sc.telem.aca_slots``: dict of ACA :class:`~annie.aca.StarDataRecord` tables keyed by slot * ``sc.telem.pcad_slots``: dict of PCAD star :class:`~annie.pcad.SlotRecord` info tables () keyed by slot * ``sc.telem.pcad_att``: PCAD :class:`~annie.pcad.AttitudeRecord` info table These attributes are the recommended way to access the annie simulation results. The example below shows how to access these attributes to plot the y-angle centroids. In most cases the telemetry from the first 8.2 seconds of simulation are not interesting (where acquisition occurs), so we can just clip that using the :meth:`~annie.telem.Telem.clip` method:: >>> from annie import run_from_starcheck >>> sc = run_from_starcheck(20201, stop=700) >>> sc.telem.clip() Now let's plot the telemetry: >>> import matplotlib.pyplot as plt >>> %matplotlib >>> aca_slot = sc.telem.aca_slots[3] >>> plt.plot(aca_slot['time'], aca_slot['zag']) >>> plt.xlabel('Time (sec)') >>> plt.grid() >>> plt.title('Z-angle (arcsec)') .. image:: images/yag_example.png :width: 320px The next example shows how to compute the offsets between the true and estimated attitudes:: >>> times = sc.telem.pcad_att['time'] >>> atts_true = sc.telem.pcad_att['att_true'] >>> atts_est = sc.telem.pcad_att['att_est'] >>> dr = [] >>> dp = [] >>> dy = [] >>> for att_true, att_est in zip(atts_true, atts_est): >>> dq = att_true.dq(att_est) >>> dr.append(dq.roll0 * 3600) >>> dy.append(dq.yaw * 3600) >>> dp.append(dq.pitch * 3600) >>> >>> plt.plot(times, dr) >>> plt.ylabel('offset (arcsec)') >>> plt.xlabel('Time (s)') >>> plt.grid() >>> plt.title('Delta roll') .. image:: images/droll_example.png :width: 320px Slot records ^^^^^^^^^^^^ The telemetry records are dictionaries keyed by slot number, with values that are lists containing the relevant records ordered by time. The example below shows how to access the slot record telemetry that stores the :data:`~annie.pcad.GS_loss_count` counter Kalman status, star residuals, star magnitude, slot number, ACA function and a time stamp for each frame:: >>> sc.telem.slot_records.keys() dict_keys([3, 4, 5, 6, 7]) >>> # The first two slot records for slot number 3 >>> sc.telem.slot_records[3][:2] [<SlotRecord: slot=3 time=0.0 F_image=NULL_IMAGE>, <SlotRecord: slot=3 time=1.025 F_image=NULL_IMAGE>] >>> # Explore the content of an individual slot record >>> sr = sc.telem.slot_records[3][10] # 10th record for slot number 3 >>> print(sr) {'F_image': 'STAR', 'GS_loss_count': 0, 'Kalman_ok': True, 'clock': <ClockTime secs=10.250 ticks=640>, 'delta_y': 0.040003284291393326, 'delta_z': 0.076399486542315209, 'function': 'TRAK', 'mag': 9.2567258517843527, 'slot': 3, 'time': 10.249999999999998} Plot the image function for slot number 7:: >>> import numpy as np >>> F_images = np.array([sr.F_image for sr in sc.telem.slot_records[7]]) >>> times = np.array([sr.time for sr in sc.telem.slot_records[7]]) >>> state_codes = [(0, 'NULL_IMAGE'), (1, 'STAR')] >>> vals = np.zeros_like(F_images) >>> for state_code in state_codes: ... ok = F_images == state_code[1] ... vals[ok] = state_code[0] >>> from Ska.Matplotlib import plot_cxctime >>> plot_cxctime(times, vals, 'o--', state_codes=state_codes) .. image:: images/F_image_example.png :width: 400px .. _`star-data-record`: Star data records ^^^^^^^^^^^^^^^^^ To explore the star data use :attr:`~annie.telem.Telem.star_data_records`. Note that the star data record telemetry starts at ``t = 1.025 sec``, as opposed to the slot record telemetry that starts at ``t = 0 sec``:: >>> # Explore star data telemetry resulting from Example 1 above >>> sc.telem.star_data_records.keys() dict_keys([3, 4, 5, 6, 7]) >>> # The first two star data records for slot number 3 >>>sc.telem.star_data_records[3][:2] [<StarDataRecord: slot=3 time=1.025 row0=46 col0=222>, <StarDataRecord: slot=3 time=2.05 row0=511 col0=511>] >>> # Explore the content of an individual slot record >>> sdr = sc.telem.star_data_records[3][10] # 10th record for slot number 3 >>> print(sdr) {'bgd': 17.0, 'bgd_avg': 17.0, 'bgd_rms': 13.714108945658715, 'bgd_status': array([ True, False, True, True, True, True, True, False], dtype=bool), 'brightest': True, 'clock': <ClockTime secs=11.275 ticks=704>, 'col': 245.49913455685868, 'col1': 250, 'fid': False, 'function': 'TRAK', 'halfwidth': 120, 'image': <ACAImage row0=66 col0=242 array([[ 8, 45, 5, 35, 119, 7, 5, 5], [ 27, 89, 7, 5, 13, 5, 30, 35], [ 41, 6, 23, 56, 121, 39, 17, 3], [ 95, 12, 161, 479, 461, 198, 14, 11], [ 6, 17, 188, 833, 863, 220, 44, 22], [ 51, 12, 107, 397, 412, 108, 70, 165], [ 28, 23, 43, 161, 69, 56, 40, 69], [ 44, 35, 25, 9, 12, 24, 18, 37]])>, 'img_sum': 4753.3399205036621, 'last_sdr': <weakref at 0x7fa1325a6db8; to 'StarDataRecord' at 0x7fa1325b37b8>, 'mag': 9.2567258524020062, 'maxmag': 10.859, 'min_img_sum': 543.32441108392015, 'rate_c': 0.027156531569687559, 'rate_r': -0.023609485876363578, 'repeat_count': 0, 'row': 69.972698185568873, 'row1': 74, 'search_count': 4, 'size': 6, 'slot': 3, 'threshold': 10.859, 'time': 11.274999999999999, 'yag': -318.80588100432664, 'yag_cat': -318.30117934550685, 'zag': 1202.8794098096994, 'zag_cat': 1202.290747547197} Plot the ACA function for slot number 3: >>> import numpy as np >>> funcs = np.array([sdr.function for sdr in sc.telem.star_data_records[3]]) >>> times = np.array([sdr.time for sdr in sc.telem.star_data_records[3]]) >>> state_codes = [(0, 'NONE'), (1, 'TRAK'), (2, 'SRCH'), (3, 'RACQ')] >>> vals = np.zeros_like(funcs) >>> for state_code in state_codes: ok = funcs == state_code[1] vals[ok] = state_code[0] >>> from Ska.Matplotlib import plot_cxctime >>> plot_cxctime(times, vals, 'o--', state_codes=state_codes) .. image:: images/function_example.png :width: 400px Attitude records ^^^^^^^^^^^^^^^^ The :attr:`~annie.telem.Telem.att_records` attribute is a list containing an :attr:`~annie.pcad.AttitudeRecord` for each minor frame. It allows for exploration of the spacecraft attitude during the simulation. It contains the time history of the commanded, true and estimated attitudes, their yaw, pitch and roll components, and dither, true and estimated rates:: >>> # Access the 10th attitude record >>> ar = sc.telem.attitude_records[10] >>> print(ar) {'att_cmd': <Quat q1=0.14961427 q2=0.49089671 q3=0.83147065 q4=0.21282047>, 'att_est': <Quat q1=0.14961408 q2=0.49089675 q3=0.83147077 q4=0.21282004>, 'att_true': <Quat q1=0.14961406 q2=0.49089675 q3=0.83147078 q4=0.21281999>, 'clock': <ClockTime secs=2.562 ticks=160>, 'last_att_record': <weakref at 0x7fa1325800e8; to 'AttitudeRecord' at 0x7fa1325e3ac8>, 'pitch_cmd': 0.0, 'pitch_dither': 0.18214420697121447, 'pitch_dither_rate': 0.071068381251204654, 'pitch_err': -0.018200254323126025, 'pitch_est': 0.16394395264808845, 'pitch_est_rate': 0.07108680873629852, 'pitch_true': 0.18401325204517213, 'pitch_true_rate': 0.071978393967360957, 'roll_cmd': 0.0, 'roll_dither': 0.0, 'roll_dither_rate': 0.0, 'roll_err': 0.0, 'roll_est': 0.0, 'roll_est_rate': 0.0, 'roll_true': 0.0, 'roll_true_rate': 0.0, 'time': 2.5624999999999996, 'yaw_cmd': 0.0, 'yaw_dither': 0.12879973380786744, 'yaw_dither_rate': 0.050258967404293226, 'yaw_err': -0.0128749648904041, 'yaw_est': 0.11592476891746334, 'yaw_est_rate': 0.05026548245743669, 'yaw_true': 0.13012066185289703, 'yaw_true_rate': 0.050902715648813428} Kalman tracking ^^^^^^^^^^^^^^^^ The Kalman status and the number of Kalman stars are collected for each frame and stored in :attr:`annie.telem.Telem.Kalman_status` and :attr:`annie.telem.Telem.n_Kalman_stars`. The example below illustrates that there were enough Kalman stars in each slot and for each frame once the ACA function changed from 'SRCH' to 'TRAK' (``Kalman_status = True``; see :ref:`star-data-record` to learn how to access the ACA function data), and that the actual number of Kalman stars for each fo these frames was ``n_Kalman_stars = 5``:: >>> # Explore telemetry resulting from Example 1 above >>> # Frame no. 7 is the last frame with ACA function equal 'SRCH' >>> # while frame no. 8 is the first frame with ACA function equal 'TRAK' >>> sc.telem.Kalman_status[7:9] >>> [[False, False, False, False, False], [True, True, True, True, True]] >>> # The number of Kalman stars was 5 in all frames >>> # with ACA function equal 'TRAK' >>> sc.telem.n_Kalman_stars >>> [0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5]