def test_sun_vec_versus_telemetry(): """ Test sun vector values `pitch` and `off_nominal_roll` versus flight telem. Include Load maneuver at 2017:349:20:52:37.719 in DEC1117 with large pitch and off_nominal_roll change (from around zero to -17 deg). State values are within 1.5 degrees of telemetry. """ state_keys = ['pitch', 'off_nom_roll'] start, stop = '2017:349:10:00:00', '2017:350:10:00:00' cmds = commands.get_cmds(start, stop) rk = states.get_states(state_keys=state_keys, cmds=cmds, merge_identical=True)[-20:-1] tstart = DateTime(rk['datestart']).secs tstop = DateTime(rk['datestop']).secs tmid = (tstart + tstop) / 2 # Pitch from telemetry dat = fetch.Msid('pitch', tstart[0] - 100, tstop[-1] + 100) dat.interpolate(times=tmid) delta = np.abs(dat.vals - rk['pitch']) assert np.max(rk['pitch']) - np.min(rk['pitch']) > 75 # Big maneuver assert np.all(delta < 1.5) # Off nominal roll (not roll from ra,dec,roll) from telemetry dat = fetch.Msid('roll', tstart[0] - 100, tstop[-1] + 100) dat.interpolate(times=tmid) delta = np.abs(dat.vals - rk['off_nom_roll']) assert np.max(rk['off_nom_roll']) - np.min( rk['off_nom_roll']) > 20 # Large range assert np.all(delta < 1.5)
def compare_msid(msid, stat): """ Compare ``stat`` data for ``msid``: locally generated vs. flight in /proj/sot/ska. """ # Start with the local version which is generated over a small slice of time # (a few days). fetch.msid_files.basedir = os.getcwd() local = fetch.Msid(msid, opt.start, opt.stop, stat=stat) # Now use definitive Ska flight data as reference for comparison. # Fetch over an interval which is just slightly longer. tstart = local.times[0] - 0.0001 tstop = local.times[-1] + 0.0001 fetch.msid_files.basedir = os.path.join(opt.flight_root, 'data', 'eng_archive') flight = fetch.Msid(msid, tstart, tstop, stat=stat) fails = [] if len(local) != len(flight): fails.append(' length local:{} flight{}'.format( len(local), len(flight))) else: for colname in local.colnames: local_value = getattr(local, colname) flight_value = getattr(flight, colname) # Do not explicitly test bads, this is done implicitly by virtue of # the bad filtering already applied. if colname == 'bads': continue # Extend the colname for reporting purposes if stat: colname = colname + '[{}]'.format(stat) # Check dtype equality if local_value.dtype != flight_value.dtype: fails.append('.{} dtype local:{} flight{}'.format( colname, local.dtype, flight.dtype)) continue # Define comparison operator based on type if local_value.dtype.kind in ('i', 'u', 'S', 'U', 'b'): # int, str, unicode, bool compare = lambda x, y: np.all(x == y) elif local_value.dtype.kind == 'f': # float compare = np.allclose else: fails.append('.{} unexpected dtype {}'.format( colname, local_value.dtype)) continue if not compare(local_value, flight_value): fails.append('.{} local != flight'.format(colname)) fails = [msid + fail for fail in fails] return fails
def collect_bad_data(start, stop, mask): """ find bad data input: start --- data collection starting time (usually <yyyy>:<jjj>:<hh>:<mm>:<ss> stop --- data collection stopping time mask --- mask to find the data (only two options: '0x7f', '0x0400' output: cnt --- counts of bad data """ # #--- get data from ska database # dat = fetch.Msid('HRC_SS_HK_BAD', start, stop) # #--- select bad data # if mask == '0x7f': bad = (dat.vals & 0x7f) > 0 else: bad = (dat.vals & 0x0400) > 0 # #--- count bad data occarnaces # cnt = count_bad_cases(bad) return cnt
def are_we_in_comm(verbose=False, cadence=2, fake_comm=False): # Always be fetching from MAUDE fetch.data_source.set('maude allow_subset=True') # These fetches are really fast. Slow the cadence a bit. time.sleep(cadence) # cadence is in seconds here # If there VCDU frame values within the last 60 seconds, this will not be empty ref_vcdu = fetch.Msid('CVCDUCTR', start=CxoTime.now() - 60 * u.s) # Will be True if in comm, False if not. in_comm = len(ref_vcdu) > 0 if fake_comm is True: in_comm = True if verbose: if in_comm: print( f'({CxoTime.now().strftime("%m/%d/%Y %H:%M:%S")} | VCDU {ref_vcdu.vals[-1]} | #{in_comm_counter}) IN COMM!', end='\r') elif not in_comm: print( f'({CxoTime.now().strftime("%m/%d/%Y %H:%M:%S")}) Not in Comm. ', end='\r\r\r') return in_comm
def get_n_kalman(start, stop): """ Get the AOKALSTR data with number of kalman stars reported by OBC """ start = DateTime(start).date stop = DateTime(stop).date dat = fetch.Msid('aokalstr', start, stop) dat.interpolate(1.025) return dat
def set_atts(self, source): """Get attitude solution quaternions from ``source``. One could also just set atts and att_times attributes directly. """ self.att_source = source tstart = DateTime(self.start).secs tstop = DateTime(self.stop).secs # Get attitudes and times if source == 'obc': telem = fetch.Msidset(['aoattqt*'], tstart, tstop) atts = np.vstack([ telem['aoattqt{}'.format(idx)].vals for idx in [1, 2, 3, 4] ]).transpose() att_times = telem['aoattqt1'].times # Fetch COBSQID at beginning and end of interval, check they match, and define obsid if self.obsid is None: obsid_start = fetch.Msid('COBSRQID', tstart, tstart + 60) obsid_stop = fetch.Msid('COBSRQID', tstop - 60, tstop) if len(obsid_start.vals) == 0 or len(obsid_stop.vals) == 0: raise ValueError( "Error getting COBSRQID telem for tstart:{} tstop:{} from fetch_source:{}" .format(tstart, tstop, fetch.data_source.sources()[0])) self.obsid = obsid_start.vals[-1] elif source == 'ground': atts, att_times, asol_recs = asp_l1.get_atts(start=tstart, stop=tstop) obsids = np.unique( np.array([int(rec['OBS_ID']) for rec in asol_recs])) if len(obsids) > 1: raise ValueError( "Time range covers more than one obsid; Not supported.") self.obsid = obsids[0] else: raise ValueError("att_source must be 'obc' or 'ground'") ok = (att_times >= tstart) & (att_times < tstop) self.atts = atts[ok, :] # (N, 4) numpy array self.att_times = att_times[ok]
def fix_stats_h5(msid, tstart, tstop, interval): dt = {'5min': 328, 'daily': 86400}[interval] ft['msid'] = msid ft['interval'] = interval datestart = DateTime(tstart).date with _set_msid_files_basedir(datestart): stats_file = msid_files['stats'].abs logger.info('Updating stats file {}'.format(stats_file)) index0 = int(tstart // dt) index1 = int(tstop // dt) + 1 indexes = np.arange(index0, index1 + 1, dtype=np.int32) times = indexes * dt logger.info('Indexes = {}:{}'.format(index0, index1)) logger.info('Fetching {} data between {} to {}'.format( msid, DateTime(times[0] - 500).date, DateTime(times[-1] + 500).date)) dat = fetch.Msid(msid, times[0] - 500, times[-1] + 500) # Check within each stat interval? if len(dat.times) == 0: logger.info('Skipping: No values within interval {} to {}'.format( DateTime(times[0] - 500).date, DateTime(times[-1] + 500).date)) return rows = np.searchsorted(dat.times, times) vals_stats = calc_stats_vals(dat, rows, indexes, interval) try: h5 = tables.openFile(stats_file, 'a') table = h5.root.data row0, row1 = np.searchsorted(table.col('index'), [index0, index1]) for row_idx, vals_stat in itertools.izip(range(row0, row1), vals_stats): if row1 - row0 < 50 or row_idx == row0 or row_idx == row1 - 1: logger.info('Row index = {}'.format(row_idx)) logger.info(' ORIGINAL: %s', table[row_idx]) logger.info(' UPDATED : %s', vals_stat) if opt.run: table[row_idx] = tuple(vals_stat) finally: h5.close() logger.info('')
def plot_aokalstr(e, edir): """ Plot AOKALSTR over the event with times in the supplied dictionary. :param e: dictionary with times of background event :param edir: directory for plots """ plt.figure(figsize=(4, 3)) aokalstr = fetch.Msid('AOKALSTR', e['event_tstart'] - 100, e['event_tstop'] + 100) aokalstr.plot() plt.ylim([0, 9]) plt.grid() plt.title('AOKALSTR') filename = "aokalstr_{}.png".format(e['event_datestart']) plt.savefig(os.path.join(edir, filename)) plt.close() return filename
def get_telem(dwell): subformat = fetch.Msid('COTLRDSF', dwell.start, dwell.stop) msids = [ 'AOACASEQ', 'AOPCADMD', 'AOKALSTR', 'COTLRDSF', 'COBSRQID', 'AOATTQT1', 'AOATTQT2', 'AOATTQT3', 'AOATTQT4', 'CORADMEN', '3TSCPOS', '3TSCMOVE' ] # don't bother getting the residuals if the dwell has PCAD subformat if not np.any(subformat.vals == 'PCAD'): res_msids = [ 'AORESY0', 'AORESY1_1', 'AORESY2_1', 'AORESY3', 'AORESY4', 'AORESY5_1', 'AORESY6_1', 'AORESY7', 'AORESZ0', 'AORESZ1_1', 'AORESZ2_1', 'AORESZ3', 'AORESZ4', 'AORESZ5_1', 'AORESZ6_1', 'AORESZ7' ] msids = msids + res_msids slot_cols = [ 'AOACFID', 'AOACMAG', 'AOACYAN', 'AOACZAN', 'AOACFCT', 'AOIMAGE', 'AOACIDP', 'AOACIIR', 'AOACIMS', 'AOACISP' ] slots = range(0, 8) slot_msids = [ "{}{}".format(field, slot) for field in slot_cols for slot in slots ] msids = msids + slot_msids if dwell.start > '2015:251': pcad_data = fetch.Msidset(msids + ['AOACIMSS'], start=dwell.start, stop=dwell.stop) else: pcad_data = fetch.Msidset(msids, start=dwell.start, stop=dwell.stop) if 'AORESY0' in pcad_data: for slot in [1, 2, 5, 6]: pcad_data['AORESY{}'.format(slot)] = pcad_data['AORESY{}_1'.format( slot)] pcad_data['AORESZ{}'.format(slot)] = pcad_data['AORESZ{}_1'.format( slot)] return pcad_data
def get_observed_metrics(obsid, metrics_file=None): """ Fetch manvr angle, one shot updates and aberration corrections, calculate centroid residuals and observed OBC roll error with respect to the ground (obc) solution for science (ER) observations as a function of time. Calculate also 50th and 95th percentile of the roll error, and log the preceding and next obsid. :param obsid: obsid """ # One shot manvrs = events.manvrs.filter(obsid=obsid) if len(manvrs) == 0: raise NoManvrError(f"No manvr for obsid={obsid}") manvr = manvrs[0] one_shot = manvr.one_shot one_shot_pitch = manvr.one_shot_pitch one_shot_yaw = manvr.one_shot_yaw # Manvr angle manvr_angle = manvr.angle # Next obsid manvr_next = manvr.get_next() if manvr_next: obsid_next = manvr_next.get_obsid() else: obsid_next = -9999 # Preceding obsid manvr_preceding = manvr.get_previous() if manvr_preceding: obsid_preceding = manvr_preceding.get_obsid() else: obsid_preceding = -9999 # Attitude errors att_errors = get_observed_att_errors(obsid, on_the_fly=True) att_flag = att_errors['flag'] if att_flag > 1: return ({'obsid': obsid, 'obsid_preceding': obsid_preceding, 'obsid_next': obsid_next, 'att_flag': att_flag, 'dwell': True}, None) if att_errors is None: return ({'obsid': obsid, 'obsid_preceding': obsid_preceding, 'obsid_next': obsid_next, 'att_flag': att_flag, 'dwell': False}, None) # dr statistics drs = att_errors['dr'] dr50 = float(np.percentile(np.abs(drs), 50)) dr95 = float(np.percentile(np.abs(drs), 95)) mean_date = DateTime(0.5 * (att_errors['time'][0] + att_errors['time'][-1])).date # Centroid residuals crs = att_errors['crs'] # Roll error at end of preceding observation preceding_roll_err = get_ending_roll_err(obsid_preceding, metrics_file=metrics_file) # Aberration correction aber_flag = 0 path_ = get_mp_dir(obsid)[0] if path_ is None: logger.info(f'No mp_dir for {obsid}. Skipping aber correction') aber_y = -9999 aber_z = -9999 aber_flag = 1 else: mp_dir = f"/data/mpcrit1/mplogs/{path_}" manerr = glob(f'{mp_dir}/*ManErr.txt') if len(manerr) == 0: logger.info(f'No ManErr file for {obsid}. Skipping aber correction') aber_y = -9999 aber_z = -9999 aber_flag = 2 else: dat = ascii.read(manerr[0], header_start=2, data_start=3) ok = dat['obsid'] == obsid if np.sum(ok) > 1: logger.info(f'More than one entry per {obsid}. Skipping aber correction') aber_y = -9999 aber_z = -9999 aber_flag = 3 else: aber_y = dat['aber-Y'][ok][0] aber_z = dat['aber-Z'][ok][0] if aber_flag == 0: one_shot_aber_corrected = np.sqrt((one_shot_pitch - aber_y)**2 + (one_shot_yaw - aber_z)**2) else: one_shot_aber_corrected = -9999 out_obsid = {'obsid': obsid, 'mean_date': mean_date, 'dr50': dr50, 'dr95': dr95, 'one_shot': one_shot, 'one_shot_pitch': one_shot_pitch, 'one_shot_yaw': one_shot_yaw, 'manvr_angle': manvr_angle, 'obsid_preceding': obsid_preceding, 'ending_roll_err': att_errors['dr'][-1], 'preceding_roll_err': preceding_roll_err, 'aber_y': aber_y, 'aber_z': aber_z, 'aber_flag': aber_flag, 'one_shot_aber_corrected': one_shot_aber_corrected, 'obsid_next': obsid_next, 'att_errors': att_errors, 'att_flag': att_flag, 'dwell': True} out_slot = {'obsid': obsid, 'slots': {k: {} for k in range(8)}} cat = crs['cat'] d = events.dwells.filter(obsid=obsid)[0] logger.info(f'Dwell at {d.start}') for slot in range(8): ok = cat['slot'] == slot out = {} if len(cat[ok]) > 0: out['id'] = cat['id'][ok][0] out['type'] = cat['type'][ok][0] out['mag'] = cat['mag'][ok][0] out['yang'] = cat['yang'][ok][0] out['zang'] = cat['zang'][ok][0] out['slot'] = slot if att_flag == 0: # Ground solution exists val = crs['ground'][slot] else: # Use obc solution val = crs['obc'][slot] if len(val.dyags) > 0 and len(val.dzags) > 0: out['std_dy'] = np.std(val.dyags) out['std_dz'] = np.std(val.dzags) out['rms_dy'] = np.sqrt(np.mean(val.dyags ** 2)) out['rms_dz'] = np.sqrt(np.mean(val.dzags ** 2)) out['median_dy'] = np.median(val.dyags) out['median_dz'] = np.median(val.dzags) drs = np.sqrt((val.dyags ** 2) + (val.dzags ** 2)) for dist in ['1.5', '3.0', '5.0']: out[f'f_within_{dist}'] = np.count_nonzero(drs < float(dist)) / len(drs) else: for metric in ['std_dy', 'std_dz', 'rms_dy', 'rms_dz', 'median_dy', 'median_dz']: out[metric] = -9999 for metric in ['f_within_5.0', 'f_within_3.0', 'f_within_1.5']: out[metric] = 0 mags = fetch.Msid(f'aoacmag{slot}', start=d.start, stop=d.stop) out['median_mag'] = np.median(mags.vals) out_slot['slots'][slot] = out return out_obsid, out_slot
from kadi import events from mica.quaternion import Quat, normalize from mica.starcheck import get_starcheck_catalog import os import time import re from Chandra.Time import DateTime import Ska.DBI from Ska.Shell import bash from astropy.table import Table import numpy as np from Ska.engarchive import fetch kalstr_obsids = [] for year in range(2008, 2017): dat = fetch.Msid('AOKALSTR', '{}:001'.format(year), '{}:001'.format(year+1)) lowkals = dat.logical_intervals('<=', '2 ') lowkals = lowkals[lowkals['duration'] < 120] # too-long intervals are spurious bad = lowkals['duration'] > 60 lowkals = lowkals[bad] for interval in lowkals: ds = events.dwells.filter(start=interval['tstart'], stop=interval['tstop']) kalstr_obsids.extend([d.get_obsid() for d in ds]) kalstr_obsids = np.unique(kalstr_obsids) kalstr_obsids = kalstr_obsids[kalstr_obsids != 0]
def main(): fetch.data_source.set('maude allow_subset=True') args = get_args() fake_comm = args.fake_comm chatty = args.report_errors # Will be True if user set --report_errors if fake_comm: bot_slack_channel = '#bot-testing' elif not fake_comm: bot_slack_channel = bot_slack_channel = '#comm_passes' # Initial settings recently_in_comm = False in_comm_counter = 0 # Loop infinitely :) while True: try: in_comm = are_we_in_comm(verbose=False, cadence=2, fake_comm=fake_comm) if not in_comm: if recently_in_comm: # We might have just had a loss in telemetry. Try again after waiting for a minute time.sleep(60) in_comm = are_we_in_comm(verbose=False, cadence=2) if in_comm: continue # Assuming the end of comm is real, then comm has recently ended and we need to report that. telem = grab_critical_telemetry(start=CxoTime.now() - 1800 * u.s) message = f"It appears that COMM has ended as of `{CxoTime.now().strftime('%m/%d/%Y %H:%M:%S')}` \n\n HRC was *{telem['HRC observing status']}* \n Last telemetry was in `{telem['Format']}` \n\n *HRC-I* was {telem['HRC-I Status']} \n *HRC-S* was {telem['HRC-S Status']} \n\n *Shields were {telem['Shield State']}* with a count rate of `{telem['Shield Rate']} cps` \n\n *HRC-I* Voltage Steps were (Top/Bottom) = `{telem['HRC-I Voltage Steps'][0]}/{telem['HRC-I Voltage Steps'][1]}` \n *HRC-S* Voltage Steps were (Top/Bottom) = `{telem['HRC-S Voltage Steps'][0]}/{telem['HRC-S Voltage Steps'][1]}` \n\n *Bus Current* was `{telem['Bus Current (DN)']} DN` (`{telem['Bus Current (A)']} A`) \n\n *FEA Temperature* was `{telem['FEA Temp']} C`" send_slack_message(message, channel=bot_slack_channel) recently_in_comm = False in_comm_counter = 0 print( f'({CxoTime.now().strftime("%m/%d/%Y %H:%M:%S")}) Not in Comm. ', end='\r\r\r') if in_comm: if fake_comm: # two days to make sure we grab previous comm start_time = CxoTime.now() - 2 * u.d elif not fake_comm: start_time = CxoTime.now( ) - 300 * u.s # 300 sec to make the grab really small if in_comm_counter == 0: # Then start the clock on the comm pass comm_start_timestamp = CxoTime.now() recently_in_comm = True in_comm_counter += 1 time.sleep(5) # Wait a few seconds for MAUDE to refresh latest_vcdu = fetch.Msid('CVCDUCTR', start=start_time).vals[-1] print( f'({CxoTime.now().strftime("%m/%d/%Y %H:%M:%S")} | VCDU {latest_vcdu} | #{in_comm_counter}) In Comm.', end='\r') if in_comm_counter == 5: # Now we've waited ~half a minute or so for MAUDE to update telem = grab_critical_telemetry(start=CxoTime.now() - 8 * u.h) # Craft a message string using this latest elemetry message = f"We are now *IN COMM* as of `{CxoTime.now().strftime('%m/%d/%Y %H:%M:%S')}` (_Chandra_ time). \n\n HRC is *{telem['HRC observing status']}* \n Telemetry Format = `{telem['Format']}` \n\n *HRC-I* is {telem['HRC-I Status']} \n *HRC-S* is {telem['HRC-S Status']} \n\n *Shields are {telem['Shield State']}* with a count rate of `{telem['Shield Rate']} cps` \n\n *HRC-I* Voltage Steps (Top/Bottom) = `{telem['HRC-I Voltage Steps'][0]}/{telem['HRC-I Voltage Steps'][1]}` \n *HRC-S* Voltage Steps (Top/Bottom) = `{telem['HRC-S Voltage Steps'][0]}/{telem['HRC-S Voltage Steps'][1]}` \n \n *Total Event* Rate = `{telem['TE Rate']} cps` \n *Valid Event* Rate = `{telem['VE Rate']} cps` \n \n *Bus Current* is `{telem['Bus Current (DN)']} DN` (`{telem['Bus Current (A)']} A`) \n \n *FEA Temperature* is `{telem['FEA Temp']} C`" # Send the message using our slack bot send_slack_message(message, channel=bot_slack_channel) # do a first audit of the telemetry upon announcement if in_comm_counter == 10: # Now we've waited a minute. Let's audit the telemetry and send amessage. audit_telemetry(start=comm_start_timestamp, channel=bot_slack_channel) except Exception as e: # MAUDE queries fail regularly as TM is streaming in (mismatched array sizes as data is being populated), 404s, etc. # The solution is almost always to simply try again. Therefore this script just presses on in the event of an Exception. if chatty: # Then we want a verbose error message, because we're obviously in testing mode print( f'({CxoTime.now().strftime("%m/%d/%Y %H:%M:%S")}) ERROR: {e}' ) print("Heres the traceback:") print(traceback.format_exc()) print("Pressing on...") elif not chatty: # Then we're likely in operational mode. Ignore the errors on the command line. print( f'({CxoTime.now().strftime("%m/%d/%Y %H:%M:%S")}) ERROR encountered! Use --report_errors to display them. ', end='\r\r\r') if in_comm_counter > 0: # Reset the comm counter to make the error "not count" in_comm_counter -= 1 continue
def calc_stats(obsid): obspar = mica.archive.obspar.get_obspar(obsid) if not obspar: raise ValueError("No obspar for {}".format(obsid)) manvr = None dwell = None try: manvrs = events.manvrs.filter(obsid=obsid, n_dwell__gt=0) dwells = events.dwells.filter(obsid=obsid) if dwells.count() == 1 and manvrs.count() == 0: # If there is more than one dwell for the manvr but they have # different obsids (unusual) so don't throw an overlapping interval kadi error # just get the maneuver to the attitude with this dwell dwell = dwells[0] manvr = dwell.manvr elif dwells.count() == 0: # If there's just nothing, that doesn't need an error here # and gets caught outside the try/except pass else: # Else just take the first matches from each manvr = manvrs[0] dwell = dwells[0] except ValueError: multi_manvr = events.manvrs.filter(start=obspar['tstart'] - 100000, stop=obspar['tstart'] + 100000) multi = multi_manvr.select_overlapping(events.obsids(obsid=obsid)) deltas = [np.abs(m.tstart - obspar['tstart']) for m in multi] manvr = multi[np.argmin(deltas)] dwell = manvr.dwell_set.first() if not manvr or not dwell: raise ValueError("No manvr or dwell for {}".format(obsid)) if not manvr.get_next(): raise ValueError("No *next* manvr so can't calculate dwell") if not manvr.guide_start: raise ValueError("No guide transition for {}".format(obsid)) if not manvr.kalman_start: raise ValueError("No Kalman transition for {}".format(obsid)) logger.info("Found obsid manvr at {}".format(manvr.start)) logger.info("Found dwell at {}".format(dwell.start)) starcheck = get_starcheck_catalog_at_date(manvr.guide_start) if starcheck is None or 'cat' not in starcheck or not len( starcheck['cat']): raise ValueError('No starcheck catalog found for {}'.format( manvr.get_obsid())) starcat_time = DateTime(starcheck['cat']['mp_starcat_time'][0]).secs starcat_dtime = starcat_time - DateTime(manvr.start).secs # If it looks like the wrong starcheck by time, give up if abs(starcat_dtime) > 300: raise ValueError( "Starcheck cat time delta is {}".format(starcat_dtime)) if abs(starcat_dtime) > 30: logger.warning("Starcheck cat time delta of {} is > 30 sec".format( abs(starcat_dtime))) # The NPNT dwell should end when the next maneuver starts, but explicitly confirm via pcadmd pcadmd = fetch.Msid('AOPCADMD', manvr.kalman_start, manvr.get_next().tstart + 20) next_nman_start = pcadmd.times[pcadmd.vals != 'NPNT'][0] vals, star_info = get_data(start=manvr.kalman_start, stop=next_nman_start, obsid=obsid, starcheck=starcheck) gui_stats = calc_gui_stats(vals, star_info) obsid_info = { 'obsid': obsid, 'obi': obspar['obi_num'], 'kalman_datestart': manvr.kalman_start, 'kalman_tstart': DateTime(manvr.kalman_start).secs, 'npnt_tstop': DateTime(next_nman_start).secs, 'npnt_datestop': DateTime(next_nman_start).date, 'revision': STAT_VERSION } catalog = Table(starcheck['cat']) catalog.sort('idx') guide_catalog = catalog[(catalog['type'] == 'GUI') | (catalog['type'] == 'BOT')] aacccdpt = fetch_sci.MSID('AACCCDPT', manvr.kalman_start, manvr.get_next().start) warm_threshold = 100.0 tccd_mean = np.mean(aacccdpt.vals) tccd_max = np.max(aacccdpt.vals) warm_frac = dark_model.get_warm_fracs(warm_threshold, manvr.start, tccd_mean) temps = { 'tccd_mean': tccd_mean, 'n100_warm_frac': warm_frac, 'tccd_max': tccd_max } return obsid_info, gui_stats, star_info, guide_catalog, temps