def _get_dark_cal_id_scalar(date, select='before'): dark_cals = get_dark_cal_dirs() dark_id = date_to_dark_id(date) # Special case if dark_id is exactly an existing dark cal then return that dark # cal regardless of the select method. if dark_id in dark_cals: return dark_id dark_cal_ids = list(dark_cals.keys()) date_secs = CxoTime(date).secs dark_cal_secs = CxoTime(np.array([dark_id_to_date(id_) for id_ in dark_cal_ids])).secs if select == 'nearest': ii = np.argmin(np.abs(dark_cal_secs - date_secs)) elif select in ('before', 'after'): ii = np.searchsorted(dark_cal_secs, date_secs) if select == 'before': ii -= 1 else: raise ValueError('select arg must be one of "nearest", "before", or "after"') if ii < 0: earliest = CxoTime(dark_cal_secs[0]).date[:8] raise MissingDataError( f'No dark cal found before {earliest}' f'(requested dark cal on {date})' ) try: out_dark_id = dark_cal_ids[ii] except IndexError: raise MissingDataError('No dark cal found {} {}'.format(select, date)) return out_dark_id
def get_validation_states(self, datestart, datestop): """ Get states for validation of the thermal model. Parameters ---------- datestart : string The start date to grab states afterward. datestop : string The end date to grab states before. """ start = CxoTime(datestart) stop = CxoTime(datestop) self.logger.info('Getting commanded states between %s - %s' % (start.date, stop.date)) states = kadi_states.get_states(start, stop, state_keys=STATE_KEYS, merge_identical=True) # Set start and end state date/times to match telemetry span. Extend the # state durations by a small amount because of a precision issue converting # to date and back to secs. (The reference tstop could be just over the # 0.001 precision of date and thus cause an out-of-bounds error when # interpolating state values). dt = 0.01 / 86400 states['tstart'][0] = (start - dt).secs states['datestart'][0] = (start - dt).date states['tstop'][-1] = (stop + dt).secs states['datestop'][-1] = (stop + dt).date return states
def __init__(self, date, model_spec, limit, constant_conditions, margin_factor): """ Run a Xija model for a given time and state profile. :param date: Date used for the dwell balance analysis :type date: str :param model_spec: Dictionary of model parameters for a single model :type model_spec: dict :param limit: Thermal Limit :type limit: float :param constant_conditions: Dictionary of conditions that remain constant for balance analysis, any required parameters not included as constant_conditions must be included in the dwell state 1 and state 2 inputs. This does not necessarily include initial conditions for primary and pseudo nodes. :type constant_conditions: dict :param margin_factor: Knockdown/safety factor to reduce predicted available limited dwell time, intended to add some conservatism to the ACIS maximum dwell time predictions, which are used to determine the available cooling time for models that heat at forward and normal sun attitudes :type margin_factor: float """ self.date = CxoTime(date).date self.datesecs = CxoTime(date).secs self.model_spec = model_spec self.limit = limit self.constant_conditions = constant_conditions self.margin_factor = margin_factor self.anchor_offset_time = np.nan self.anchor_limited_time = np.nan self.results = None
def gen_unused_range(tstart, tstop, t_backoff=1725000): tstop = CxoTime(tstop).secs - t_backoff spans = [{ 'fillcolor': 'black', 'line': {'width': 0}, 'opacity': 0.25, 'type': 'rect', 'x0': datetime.strptime(CxoTime(tstart).date, '%Y:%j:%H:%M:%S.%f'), 'x1': datetime.strptime(CxoTime(tstop).date, '%Y:%j:%H:%M:%S.%f'), 'y0': 0, 'y1': 1, 'xref': 'x', 'yref': 'y domain', }, { 'fillcolor': 'black', 'line': {'width': 0}, 'opacity': 0.25, 'type': 'rect', 'x0': datetime.strptime(CxoTime(tstart).date, '%Y:%j:%H:%M:%S.%f'), 'x1': datetime.strptime(CxoTime(tstop).date, '%Y:%j:%H:%M:%S.%f'), 'y0': 0, 'y1': 1, 'xref': 'x2', 'yref': 'y2 domain', } ] return spans
def get_planet_angular_sep(body: str, ra: float, dec: float, time=None, observer_position: str = 'earth') -> float: """Get angular separation between planet ``body`` and target ``ra``, ``dec``. Valid values for the ``observer_position`` argument are: - 'earth' (default, approximate, fastest) - 'chandra' (reasonably accurate fast, requires fetching ephemeris) - 'chandra-horizons' (most accurate, slow, requires internet access) :param body: str Body name (lower case planet name) :param ra: float RA in degrees :param dec: float Dec in degrees :param time: CxoTime-compatible object Time or times of observation :param observer_position: str Observer position ('earth', 'chandra', or 'chandra-horizons') :returns: angular separation (deg) """ from agasc import sphere_dist if not isinstance(time, CxoTime): time = CxoTime(time) if observer_position == 'earth': eci = get_planet_eci(body, time) body_ra, body_dec = eci_to_radec(eci) elif observer_position == 'chandra': eci = get_planet_chandra(body, time) body_ra, body_dec = eci_to_radec(eci) elif observer_position == 'chandra-horizons': if time.shape == (): time = CxoTime([time, time + 1000 * u.s]) is_scalar = True else: is_scalar = False pos = get_planet_chandra_horizons(body, time[0], time[1], n_times=len(time)) body_ra = pos['ra'] body_dec = pos['dec'] if is_scalar: body_ra = body_ra[0] body_dec = body_dec[0] else: raise ValueError(f'{observer_position} is not an allowed value: ' f'("earth", "chandra", or "chandra-horizons")') sep = sphere_dist(ra, dec, body_ra, body_dec) return sep
def generate_step_2_plot_dict(plot_data, tstart, tstop, title, units='Celsius'): plot_start = datetime.strptime(CxoTime(tstart).date, '%Y:%j:%H:%M:%S.%f') plot_stop = datetime.strptime(CxoTime(tstop).date, '%Y:%j:%H:%M:%S.%f') plot_object = { 'data': plot_data, 'layout': { 'hovermode': "closest", 'autosize': False, 'width': width, 'height': height, 'margin': {'l': 50, 'r': 50, 't': 50, 'b': 70}, 'title': { 'text': title, 'font': title_format, 'y': 0.95, 'x': 0.5, 'xanchor': 'center', 'yanchor': 'top' }, 'yaxis': { 'title': { 'text': f'Resulting Temperatures for<br>Dwell #2 Guesses ({units})', 'font': label_format }, 'tickfont': axis_format, 'zeroline': False, 'linecolor': '#666666', 'linewidth': 1, 'mirror': True, 'anchor': 'x', }, 'xaxis': { 'domain': [0, 1], 'tickfont': axis_format, 'tickformatstops': time_axis_format, 'zeroline': False, 'linecolor': '#666666', 'linewidth': 1, 'mirror': True, 'range': [plot_start, plot_stop], 'showticklabels': True, 'tickangle': 30, 'anchor': 'y', }, 'showlegend': True, 'template': 'simple_white', }, } return plot_object
def get_durations(aca_arr): durations = [] for aca, next_aca in zip(aca_arr[:-1], aca_arr[1:]): if isinstance(next_aca, dict): dwell_end = next_aca['sched_stop'] else: man_dur = duration(aca.att, next_aca.att) dwell_end = CxoTime(next_aca.date) - man_dur * u.s durations.append(dwell_end - CxoTime(aca.date)) return durations
def paint_perigee(perigee_passages, states, plots): """ This function draws vertical dashed lines for EEF, Perigee and XEF events in the load.EEF and XEF lines are black; Perigee is red. You supply the list of perigee passage events which are: Radzone Start/Stop time Perigee Passage time The states you created in main The dictionary of plots you created The MSID (in this case FP_TEMP) used to access the dictionary """ # # Now plot any perigee passages that occur between xmin and xmax from cxotime import CxoTime for plot in plots.values(): for eachpassage in perigee_passages: # The index [1] item is always the Perigee Passage time. Draw that # line in red If this line is between tstart and tstop then it # needs to be drawn on the plot. otherwise ignore if states['tstop'][-1] >= CxoTime( eachpassage[0]).secs >= states['tstart'][0]: # Have to convert this time into the new x axis time scale # necessitated by SKA xpos = cxctime2plotdate([CxoTime(eachpassage[0]).secs]) ymin, ymax = plot['ax'].get_ylim() # now plot the line. plot['ax'].vlines(xpos, ymin, ymax, linestyle=':', color='red', linewidth=2.0) # Plot the perigee passage time so long as it was specified in # the CTI_report file if eachpassage[1] != "Not-within-load": perigee_time = cxctime2plotdate( [CxoTime(eachpassage[1]).secs]) plot['ax'].vlines(perigee_time, ymin, ymax, linestyle=':', color='black', linewidth=2.0)
def test_venus_position2(): # ******************************************************************************* # Target body name: Venus (299) {source: CHANDRA_MERGED} # Center body name: Chandra Observatory (spacecraft) (-151) {source: CHANDRA_MERGED} # Center-site name: BODYCENTRIC # ******************************************************************************* # Start time : A.D. 2020-Jan-01 00:00:00.0000 UT # Stop time : A.D. 2020-Jun-01 00:00:00.0000 UT # Step-size : 21600 minutes # ******************************************************************************* txt = """ date ra dec 2020-01-01T00:00 21:08:43.02 -18:22:41.8 2020-01-16T00:00 22:19:56.31 -12:03:15.4 2020-01-31T00:00 23:26:25.34 -04:40:18.3 2020-02-15T00:00 00:29:55.07 +03:09:41.1 2020-03-01T00:00 01:31:42.96 +10:46:02.6 2020-03-16T00:00 02:32:52.02 +17:25:28.9 2020-03-31T00:00 03:32:39.01 +22:40:58.7 2020-04-15T00:00 04:26:52.03 +26:10:57.3 2020-04-30T00:00 05:07:55.28 +27:38:30.8 2020-05-15T00:00 05:22:08.73 +27:05:38.1 2020-05-30T00:00 04:59:36.37 +24:14:26.9 """ dat = ascii.read(txt) date = CxoTime(dat['date']) sc = SkyCoord(dat['ra'], dat['dec'], unit=(u.hr, u.deg)) eci = get_planet_chandra('venus', date) ra, dec = eci_to_radec(eci) dist = sphere_dist(ra, dec, sc.ra.to_value(u.deg), sc.dec.to_value(u.deg)) * 3600 assert np.all(dist < 4.0)
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_planet_eci(body, time=None, pos_observer=None): """Get ECI apparent position for solar system ``body`` at ``time``. This uses the built-in JPL ephemeris file DE432s and jplephem. The position is computed at the supplied ``time`` minus the light-travel time from the observer to ``body`` to generate the apparent position on Earth at ``time``. Estimated accuracy of planet coordinates (RA, Dec) is as follows, where the JPL Horizons positions are used as the "truth". This assumes the observer position is Earth (default). - Venus: < 12 arcmin with peak around 2 arcmin - Mars: < 8 arcmin with peak around 1.5 arcmin - Jupiter: < 1 arcmin with peak around 0.5 arcmin - Saturn: < 0.5 arcmin with peak around 0.3 arcmin :param body: Body name (lower case planet name) :param time: Time or times for returned position (default=NOW) :param pos_observer: Observer position (default=Earth) :returns: Earth-Centered Inertial (ECI) position (km) as (x, y, z) or N x (x, y, z) """ time = CxoTime(time) pos_planet = get_planet_barycentric(body, time) if pos_observer is None: pos_observer = get_planet_barycentric('earth', time) dist = np.sqrt(np.sum((pos_planet - pos_observer)**2, axis=-1)) * u.km light_travel_time = (dist / const.c).to(u.s) pos_planet = get_planet_barycentric(body, time - light_travel_time) return pos_planet - pos_observer
def add_inputs(p, limits, date, dwell_type, roll, chips): """ Add input data to Timbre coposite results. :param p: Output data :type p: pd.DataFrame :param limits: MSID limits used to produce included results :type limits: dict, pd.Series :param date: Date simulated :type date: str :param dwell_type: Output data category (i.e. limited or offset) :type dwell_type: str :param roll: Body roll used to produce included results :type roll: int, float :param chips: Number of ACIS chips used to produce included results :type chips: int :returns: Pandas Dataframe of results data with associated simulation input data :rtype: pd.DataFrame """ for msid, limit in limits.items(): p[msid + '_limit'] = np.float(limit) p['date'] = date p['datesecs'] = CxoTime(date).secs p['dwell_type'] = dwell_type p['roll'] = roll p['chips'] = chips p.reset_index(inplace=True) p = p.rename(columns={'index': 'pitch'}) return p
def get_options(): parser = argparse.ArgumentParser() parser.add_argument("filename", default='test_gui.json', help="Model file") parser.add_argument( "--days", type=float, default=15, # Fix this help="Number of days in fit interval (default=90") parser.add_argument( "--stop", default=CxoTime() - 10, # remove this help="Stop time of fit interval (default=model values)") parser.add_argument("--maxiter", default=1000, type=int, help="Maximum number of fit iterations (default=1000)") parser.add_argument("--fit-method", default="simplex", help="Sherpa fit method (simplex|moncar|levmar)") parser.add_argument("--inherit-from", help="Inherit par values from model spec file") parser.add_argument("--set-data", action='append', dest='set_data_exprs', default=[], help="Set data value as '<comp_name>=<value>'") parser.add_argument("--quiet", default=False, action='store_true', help="Suppress screen output") return parser.parse_args()
def change_stop(self): stop_date = self.stop_text.text() try: _ = CxoTime(stop_date).secs self.start_label.setText("Stop time: {}".format(stop_date)) self.stop_date = stop_date except ValueError: raise_error_box("Write Table Error", f"Stop time not valid: {stop_date}") self.stop_text.setText("")
def get_dark_cal_ids(dark_cals_dir=MICA_FILES['dark_cals_dir'].abs): """ Get an ordered dict dates as keys and dark cal identifiers (YYYYDOY) as values. :param dark_cals_dir: directory containing dark cals. :returns: ordered dict of absolute directory paths """ dark_cal_ids = sorted([fn for fn in os.listdir(dark_cals_dir) if re.match(r'[12]\d{6}$', fn)]) dates = [CxoTime(d[:4] + ':' + d[4:]).date for d in dark_cal_ids] return OrderedDict(zip(dates, dark_cal_ids))
def gen_range_annotations(tstart, tstop, yloc1, yloc2, t_backoff=1725000): tstart = CxoTime(tstart).secs tstop = CxoTime(tstop).secs tmid = tstop - t_backoff ttext = (tstop + tmid) / 2. arrow1 = { 'x': datetime.strptime(CxoTime(tmid).date, '%Y:%j:%H:%M:%S.%f'), 'y': yloc1, 'text': '', 'showarrow': True, 'arrowhead': 2, 'arrowwidth': 3, 'arrowcolor': 'rgb(100,100,100)', 'xref': "x2", 'yref': "y2", 'ax': datetime.strptime(CxoTime(ttext).date, '%Y:%j:%H:%M:%S.%f'), 'ay': yloc1, 'axref': 'x2', 'ayref': 'y2', 'xanchor': "center", 'yanchor': "bottom", 'font': label_format } arrow2 = { 'x': datetime.strptime(CxoTime(tstop).date, '%Y:%j:%H:%M:%S.%f'), 'y': yloc1, 'text': '', 'showarrow': True, 'arrowhead': 2, 'arrowwidth': 3, 'arrowcolor': 'rgb(100,100,100)', 'xref': "x2", 'yref': "y2", 'ax': datetime.strptime(CxoTime(ttext).date, '%Y:%j:%H:%M:%S.%f'), 'ay': yloc1, 'axref': 'x2', 'ayref': 'y2', 'xanchor': "center", 'yanchor': "bottom", 'font': label_format } text = { 'x': datetime.strptime(CxoTime(ttext).date, '%Y:%j:%H:%M:%S.%f'), 'y': yloc2, 'text': 'Data Range used for evaluation', 'showarrow': False, 'xref': "x2", 'yref': "y2", 'xanchor': "center", 'yanchor': "bottom", 'font': label_format } annotations = [arrow1, arrow2, text] return annotations
def get_planet_chandra(body, time=None): """Get position for solar system ``body`` at ``time`` relative to Chandra. This uses the built-in JPL ephemeris file DE432s and jplephem, along with the CXC predictive Chandra orbital ephemeris (from the OFLS). The position is computed at the supplied ``time`` minus the light-travel time from Chandra to ``body`` to generate the apparent position from Chandra at ``time``. Estimated accuracy of planet coordinates (RA, Dec) from Chandra is as follows, where the JPL Horizons positions are used as the "truth". - Venus: < 4 arcsec with a peak around 3.5 - Mars: < 3 arcsec with a peak around 2.0 - Jupiter: < 0.8 arcsec - Saturn: < 0.5 arcsec :param body: Body name :param time: Time or times for returned position (default=NOW) :returns: position relative to Chandra (km) as (x, y, z) or N x (x, y, z) """ from cheta import fetch time = CxoTime(time) # Get position of Chandra relative to Earth try: dat = fetch.MSIDset( ['orbitephem0_x', 'orbitephem0_y', 'orbitephem0_z'], np.min(time) - 500 * u.s, np.max(time) + 500 * u.s) except ValueError: raise NoEphemerisError('Chandra ephemeris not available') if len(dat['orbitephem0_x'].vals) == 0: raise NoEphemerisError('Chandra ephemeris not available') times = np.atleast_1d(time.secs) dat.interpolate(times=times) pos_earth = get_planet_barycentric('earth', time) # Chandra position in km chandra_eci = np.zeros_like(pos_earth) chandra_eci[..., 0] = dat['orbitephem0_x'].vals.reshape(time.shape) / 1000 chandra_eci[..., 1] = dat['orbitephem0_y'].vals.reshape(time.shape) / 1000 chandra_eci[..., 2] = dat['orbitephem0_z'].vals.reshape(time.shape) / 1000 planet_chandra = get_planet_eci(body, time, pos_observer=pos_earth + chandra_eci) return planet_chandra
def gen_limit_annotation(xloc, yloc, limit, units): text_dict = { 'x': datetime.strptime(CxoTime(xloc).date, '%Y:%j:%H:%M:%S.%f'), 'y': yloc, 'text': f'Limit = {limit} {units}', 'showarrow': False, 'xref': "x2", 'yref': "y2", 'xanchor': "center", 'yanchor': "bottom", 'font': label_format } return [text_dict, ]
def format_shapes(state_data): shape_data = [] s = zip(state_data['state_times'], state_data['state_keys']) for t1, s1 in s: t2, s2 = next(s) t3, s3 = next(s) # Ignore t4, s4 = next(s) # Ignore shape_data.extend([ { 'fillcolor': 'black', 'line': {'width': 0}, 'opacity': 0.05, 'type': 'rect', 'x0': datetime.strptime(CxoTime(t1).date, '%Y:%j:%H:%M:%S.%f'), 'x1': datetime.strptime(CxoTime(t2).date, '%Y:%j:%H:%M:%S.%f'), 'y0': 0, 'y1': 1, 'xref': 'x2', 'yref': 'y2 domain', }, { 'fillcolor': 'black', 'line': {'width': 0}, 'opacity': 0.05, 'type': 'rect', 'x0': datetime.strptime(CxoTime(t1).date, '%Y:%j:%H:%M:%S.%f'), 'x1': datetime.strptime(CxoTime(t2).date, '%Y:%j:%H:%M:%S.%f'), 'y0': 0, 'y1': 1, 'xref': 'x', 'yref': 'y domain', } ]) return shape_data
def format_step_2_plot_data(model_data, limit, tstart, tstop): plot_start = datetime.strptime(CxoTime(tstart).date, '%Y:%j:%H:%M:%S.%f') plot_stop = datetime.strptime(CxoTime(tstop).date, '%Y:%j:%H:%M:%S.%f') seq_colors = px.colors.n_colors(hex_to_rgba_str(colors[3], 1), hex_to_rgba_str(colors[0], 1), len(model_data), colortype='rgb') plot_data = [] for (t, results), c in zip(model_data.items(), seq_colors): model_results = results['model_results']['aacccdpt'] plot_data.append({ 'type': 'scattergl', 'x': format_dates(model_results.times), 'y': model_results.mvals, 'name': f't_dwell2 = {t:.1f}', 'line': {'color': c, 'width': 2, 'shape': 'hv'}, 'mode': 'lines', 'showlegend': True, 'xaxis': 'x', 'yaxis': 'y', }) plot_data.append({ 'type': 'scattergl', 'x': [plot_start, plot_stop], 'y': [limit, limit], 'name': 'Limit', 'line': {'color': 'black', 'width': 2, 'shape': 'hv', 'dash': 'dash'}, 'mode': 'lines', 'showlegend': False, 'xaxis': 'x', 'yaxis': 'y', }) return plot_data
def add_filter(self, filter_type): err_msg = '' if filter_type == "ignore": vals = [self.start_text.text(), self.stop_text.text()] elif filter_type == "bad_time": vals = [self.bt_start_text.text(), self.bt_stop_text.text()] try: if vals[0] == "*": vals[0] = self.model.datestart if vals[1] == "*": vals[1] = self.model.datestop lim = CxoTime(vals).date t0, t1 = CxoTime(lim).secs if t0 > t1: err_msg = "Filter stop is earlier than filter start!" except (IndexError, ValueError): if len(vals) == 2: err_msg = f"Invalid input for filter: {vals[0]} {vals[1]}" else: err_msg = "Filter requires two arguments, " \ "the start time and the stop time." if len(err_msg) > 0: raise_error_box("Filters Error", err_msg) else: if filter_type == "ignore": self.model.append_mask_time([lim[0], lim[1]]) bad = False self.mw.plots_box.add_fill(t0, t1) elif filter_type == "bad_time": self.model.append_bad_time([lim[0], lim[1]]) bad = True self.mw.plots_box.add_fill(t0, t1, bad=bad) self.start_text.setText('') self.stop_text.setText('') self.bt_start_text.setText('') self.bt_stop_text.setText('')
def date_to_dark_id(date): """ Convert ``date`` to the corresponding YYYYDOY format for a dark cal identifiers. :param date: any CxoTime compatible format :returns: dark id (YYYYDOY) """ time = CxoTime(date) date_str = time.date if not time.shape: return date_str[:4] + date_str[5:8] date_str = np.atleast_1d(date_str) chars = date_str.view((str, 1)).reshape(-1, date_str.dtype.itemsize // 4) result = np.hstack([chars[:, :4], chars[:, 5:8]]) result = np.frombuffer(result.tobytes(), dtype=(str, 7)) return result.reshape(time.shape)
def test_venus_position1(): """Obsid 18695 starcat at 2017:010:05:07:20.875, approx obs star 0510z""" # Output from JPL Horizons for Venus from Chandra date = CxoTime('2017-01-10T05:10') sc = SkyCoord('22:36:02.59', '-09:39:07.2', unit=(u.hr, u.deg)) q_att = [-0.54137601, 0.17071483, -0.10344611, 0.81674192] eci = get_planet_chandra('venus', date) ra, dec = eci_to_radec(eci) yag, zag = radec_to_yagzag(ra, dec, q_att) # Confirm yag value is on "left side" of CCD, opposite all stars in 18695 assert np.isclose(yag, 210.20, rtol=0, atol=0.01) assert np.isclose(zag, 69.45, rtol=0, atol=0.01) dist = sphere_dist(ra, dec, sc.ra.to_value(u.deg), sc.dec.to_value(u.deg)) * 3600 assert np.all(dist < 4.0)
def gen_shading_annotation(xloc, yloc, dwell1_text, dwell2_text): text1 = f'Lightly Shaded Vertical Bands = Dwell State #1 ({dwell1_text})' text2 = f'Unshaded Vertical Bands = Dwell State #2 ({dwell2_text})' text = f'{text1}<br>{text2}' text_dict = { 'x': datetime.strptime(CxoTime(xloc).date, '%Y:%j:%H:%M:%S.%f'), 'y': yloc, 'text': text, 'showarrow': False, 'xref': "x2", 'yref': "y2", 'xanchor': "right", 'yanchor': "bottom", 'font': label_format, 'align': 'right' } return [text_dict, ]
def _get_bs_cmds(self): """ Internal method used to obtain commands from the backstop file and store them. """ if os.path.isdir(self.backstop_file): # Returns a list but requires exactly 1 match backstop_file = get_globfiles( os.path.join(self.backstop_file, 'CR[0-9]*.backstop'))[0] self.backstop_file = backstop_file self.logger.info('Using backstop file %s' % self.backstop_file) # Read the backstop commands and add a `time` column bs_cmds = kadi.commands.get_cmds_from_backstop(self.backstop_file) bs_cmds['time'] = CxoTime(bs_cmds['date']).secs self.bs_cmds = bs_cmds self.tstart = bs_cmds[0]['time'] self.tstop = bs_cmds[-1]['time']
def get_planet_barycentric(body, time=None): """Get barycentric position for solar system ``body`` at ``time``. This uses the built-in JPL ephemeris file DE432s and jplephem. :param body: Body name (lower case planet name) :param time: Time or times for returned position (default=NOW) :returns: barycentric position (km) as (x, y, z) or N x (x, y, z) """ kernel = KERNEL.val if body not in BODY_NAME_TO_KERNEL_SPEC: raise ValueError(f'{body} is not an allowed value ' f'{tuple(BODY_NAME_TO_KERNEL_SPEC)}') spk_pairs = BODY_NAME_TO_KERNEL_SPEC[body] time = CxoTime(time) time_jd = time.jd pos = kernel[spk_pairs[0]].compute(time_jd) for spk_pair in spk_pairs[1:]: pos += kernel[spk_pair].compute(time_jd) return pos.transpose() # SPK returns (3, N) but we need (N, 3)
def save_ascii_table(self): from astropy.table import Table, Column dlg = QtWidgets.QFileDialog() dlg.setNameFilters( ["DAT files (*.dat)", "TXT files (*.txt)", "All files (*)"]) dlg.selectNameFilter("DAT files (*.dat)") dlg.setAcceptMode(dlg.AcceptSave) dlg.exec_() filename = str(dlg.selectedFiles()[0]) if filename != '': try: checked = [] for i, box in enumerate(self.check_boxes): if box.isChecked(): checked.append(i) t = Table() ts = CxoTime([self.start_date, self.stop_date]).secs ts[-1] += 1.0 # a buffer to make sure we grab the last point istart, istop = np.searchsorted(self.ftd.times, ts) c = Column(self.ftd.dates[istart:istop], name="date", format="{0}") t.add_column(c) for i, key in enumerate(self.ftd): if i in checked: c = Column(self.ftd[i][istart:istop], name=key, format=self.ftd.formats[i]) t.add_column(c) t.write(filename, overwrite=True, format='ascii.ecsv') self.last_filename = filename except IOError as ioerr: msg = QtWidgets.QMessageBox() msg.setStandardButtons(QtWidgets.QMessageBox.Ok) msg.setText("There was a problem writing the file:") msg.setDetailedText("Cannot write {}. {}".format( filename, ioerr.strerror)) msg.exec_()
def _plot_planets(ax, att, date0, duration, lim0, lim1): """ :param ax: plt.Axes Matplotlib axes object to use :param att: Quat Attitude quaternion :param date0: CxoTime-compatible Date of obs start :param duration: float Duration of plot (secs) :param lim0: float Lower limit on x, y axis (row) :param lim1: float Upper limit on x, y axis (col) :returns: boolean True if planets were plotted """ if not isinstance(att, Quat): att = Quat(att) date0 = CxoTime(date0) n_times = int(duration / 1000) + 1 dates = date0 + np.linspace(0, duration, n_times) * u.s planets = ('venus', 'mars', 'jupiter', 'saturn') has_planet = False for planet in planets: # First check if planet is within 2 deg of aimpoint using Earth as the # reference point (without fetching Chandra ephemeris). These values are # accurate to better than 0.25 deg. sep = get_planet_angular_sep(planet, ra=att.ra, dec=att.dec, time=date0 + ([0, 0.5, 1] * u.s) * duration, observer_position='earth') if np.all(sep > 2.0): continue # Compute ACA row, col for planet each ksec (approx) over the duration. # This uses get_planet_chandra which is accurate to 4 arcsec for Venus # and < 1 arcsec for Jupiter, Saturn. try: eci = get_planet_chandra(planet, dates) from_earth = False except NoEphemerisError: # Get the position from Earth using built-in DE432 eci = get_planet_eci(planet, dates) from_earth = True ra, dec = eci_to_radec(eci) yag, zag = radec_to_yagzag(ra, dec, att) row, col = yagzag_to_pixels(yag, zag, allow_bad=True) # Only plot planet within the image limits ok = (row >= lim0) & (row <= lim1) & (col >= lim0) & (col <= lim1) if np.any(ok): has_planet = True row = row[ok] col = col[ok] # Plot with green at beginning, red at ending ax.plot(row, col, '.', color='m', alpha=0.5) ax.plot(row[0], col[0], '.', color='g') label = planet.capitalize() if from_earth: err = GET_PLANET_ECI_ERRORS[planet].to(u.arcsec) label += f' (from Earth, errors to {err})' ax.plot(row[-1], col[-1], '.', color='r', label=label) if has_planet: ax.legend(loc='upper left', fontsize='small', facecolor='y', edgecolor='k')
def processRTS(self, RTS_load, SCS_NUM, NUM_HOURS, RTS_start_date): """ This method opens the specified RTS, reads each line and creates an array which contains the values in the line plus time stamps the line. inputs: RTS_load : Name of the LTCTI RTS file (e.g. 1_4_CTI) SCS_NUM : The SCS number this RTS file was run in (e.g. 135) NUM_HOURS : a string in the FOT request format: ddd:hh:mm:ss RTS_start_date : Start of the LTCTI in DOY format ACIS LTCTI RTS files contain comma separated lines, and each line entry can have 2,3 or 4 columns. Samples are: /CMD, OORMPEN ACIS,WSVIDALLDN, DELTA=00:00:01.000 /CMD, 2S2STHV, 2S2STHV2=0, DELTA=00:00:01.000 Any line that does not start with 'ACIS' is logged in the array but not used by ACIS Ops other than to extract and use a DELTA if any. The important point about using the DELTA's is that if one exists, you MUST apply The delta time to the ongoing time stamp before you save the time for that line. For example, suppose the time stamp is 2018:001:00:00:00.00 and you are processing this line: ACIS,WSPOW0CF3F,DELTA=00:00:01.000 The time stamp for that command is the present time stamp plus the DELTA in that line or: 2018:001:00:00:01.00 another way of saying it is that the DELTA is the delay between the previous command and the one you are processing. If there is no DELTA use the present time stamp value """ # Compile the regex match criteria for the lines in the CTI RTS files delta_match = re.compile('DELTA=') equal_match = re.compile('=') cmd_statement_match = re.compile('/CMD') acis_statement_match = re.compile('ACIS') # Convert the RTS start date into seconds. We will use this # to calculate the time of each command in the RTS present_date = RTS_start_date present_time = CxoTime(RTS_start_date).secs # Calculate the duration of the CTI run, in seconds, as if it was NOT interrupted # by a Return To Science....i.e. it ran to completion and was # followed by the Perigee passage cti_duration_secs = self.convert_RTS_DELTA_to_secs(NUM_HOURS) # Convert the RTS start date to seconds. present_time = CxoTime(RTS_start_date).secs # Create an empty array with the RTS_dtype RTS_cmds = np.array([], dtype=self.RTS_dtype) # Form the full path to the appropriate RTS file rts_file_path = os.path.join(self.RTS_file_loc, self.RTS_name + '.RTS') # Open the specified RTS file for this Long Term CTI run rts_load = open(rts_file_path, 'r') # Process each command in the RTS # You want to fill out the RTS_dtype to the degree that you can # # So for each line in the RTS file..... for eachline in rts_load: # Ignore the line if it is a comment or a blank line if (eachline[0] != '!') and (eachline[0:2] != '\n'): # Split and join the lines while eliminating all whitespace that # occasionally occurs with the list items split_line = ''.join(eachline.split()) # Now split the line on commas split_line = split_line.split(',') # /CMD or ACIS STATEMENT # Find out what the statement value is (/CMD or ACIS) if any(filter(cmd_statement_match.match, split_line)): # It's a /CMD line so set the statement to /CMD statement = '/CMD' elif any(filter(acis_statement_match.match, split_line)): # It's an ACIS line so set the statement to ACIS statement = 'ACIS' else: # It's neither a /CMD or ACIS line so set the statement to None statement = None # MNEMONIC if statement is not None: # Find the position in the list of the statment # It's usually the first line but don't make assumptions statement_pos = split_line.index(statement) # The Mnemonic comes immediately after the statement mnemonic = split_line[statement_pos + 1] # Grab all list items that have an equal sign equal_items_list = list(filter(equal_match.findall, split_line)) # Is there a DELTA= in any list item? d_match = list(filter(delta_match.findall, equal_items_list)) # If so, extract the time string in the DELTA entry, convert # it to seconds, and add those number of seconts to the present_time if d_match: # You have a match convert and save the value.......... delta_string = d_match[0].split('=') # Check to see if the Time String is &NUM_HOURS&, If it is, # then use cti_duration_secs if delta_string[-1] == '&NUM_HOURS&': dt = cti_duration_secs else: # Otherwise use the value represented by the string dt = self.convert_RTS_DELTA_to_secs('000:' + delta_string[-1]) # In either case, advance the prewsent time by dt present_time += dt # Get the index of the DELTA item in the equal_items_list # and remove that item from the list equal_items_list.remove(d_match[0]) else: dt = 0.0 # Whether you had a delta or not, convert the present time to a date present_date = CxoTime(present_time).date # Now if equal_items_list is not empty, after you removed the DELTA # line, then the RTS line had a # substitution parameter and a supbstitution parameter value. The # format of the line is "substitution_parameter=value'. if equal_items_list: # There is a substitution value in there so set the # variables appropriately. sub_split = equal_items_list[0].split('=') substitution_parameter = sub_split[0] substitution_parametr_value = sub_split[1] else: # The list was empty so there was no substitution parameter # so therefore set the two variables to None substitution_parameter = None substitution_parametr_value = None # Now you have all the information necessary to put an entry into the array RTS_cmds = np.r_[RTS_cmds, np.array([(present_date, present_time, statement, mnemonic, substitution_parameter, substitution_parametr_value, dt, SCS_NUM)], dtype=self.RTS_dtype)] # Done with the RTS file - close it rts_load.close() # Return the RTS_cmds array return RTS_cmds
def process_obsids(obsids, update=True, retry=False): report_root = REPORT_ROOT for obsid in obsids: strobs = "%05d" % obsid chunk_dir = strobs[0:2] topdir = os.path.join(report_root, chunk_dir) outdir = os.path.join(topdir, strobs) if os.path.exists(outdir) and not update: logger.info("Skipping {}, output dir exists.".format(obsid)) continue if not retry and os.path.exists(os.path.join(outdir, "proc_err")): logger.warning("Skipping {}, previous processing error.".format(obsid)) continue if not os.path.exists(outdir): os.makedirs("{}".format(outdir)) # Delete files from old failure if reprocessing for failfile in ['proc_err', 'trace.txt']: if os.path.exists(os.path.join(outdir, failfile)): os.unlink(os.path.join(outdir, failfile)) try: main(obsid) except: import traceback etype, emess, trace = sys.exc_info() logger.warning("Failed report on {}".format(obsid)) # Make an empty file to record the error status f = open(os.path.join(outdir, 'proc_err'), 'w') f.close() # Write out the traceback too trace_file = open(os.path.join(outdir, 'trace.txt'), 'w') traceback.print_tb(trace, file=trace_file) trace_file.close() # Write out a notes jason file notes = {'report_version': REPORT_VERSION, 'obsid': obsid, 'checked_date': CxoTime.now().date, 'last_sched': "{}".format(str(emess)), 'vv_version': None, 'vv_revision': None, 'aspect_1_id': None, 'ocat_status': None, 'long_term': None, 'short_term': None, 'starcheck': None} f = open(os.path.join(outdir, 'notes.json'), 'w') f.write(json.dumps(notes, sort_keys=True, indent=4)) f.close() # Make a stub html page proc_date = CxoTime.now().date jinja_env = jinja2.Environment( loader=jinja2.PackageLoader('mica.report')) jinja_env.line_comment_prefix = '##' jinja_env.line_statement_prefix = '#' template = jinja_env.get_template('proc_error.html') page = template.render(obsid=obsid, proc_date=proc_date, version=version) full_report_file = os.path.join(outdir, 'index.html') logger.info("Writing out error stub report to {}".format(full_report_file)) f = open(full_report_file, 'w') f.write(page) f.close() # Save the bad state in the database save_state_in_db(obsid, notes)