def get_data_2d(): stars0 = stars[0] q0 = Quat([0, 0, 45]) yags = [] zags = [] for ra, dec in stars0: yag, zag = radec2yagzag(ra, dec, q0) yags.append(yag * 3600) zags.append(zag * 3600) yags_obs_list = [] zags_obs_list = [] times = np.linspace(0, 1000, 10) rolls = np.sin(2 * np.pi * times / 666) * 100 / 3600 pitches = np.sin(2 * np.pi * times / 1000) * 50 / 3600 yaws = np.sin(2 * np.pi * times / 707) * 30 / 3600 qs = [] for roll, pitch, yaw in zip(rolls, pitches, yaws): dq = Quat([yaw, -pitch, roll]) q0_offset = q0 * dq qs.append(q0_offset) yags_obs = [] zags_obs = [] for ra, dec in stars0: yag, zag = radec2yagzag(ra, dec, q0_offset) yags_obs.append(yag * 3600) zags_obs.append(zag * 3600) yags_obs_list.append(yags_obs) zags_obs_list.append(zags_obs) return q0, rolls, pitches, yaws, qs, yags, zags, yags_obs_list, zags_obs_list
def test_calc_roll_pitch_yaw(stars, pitch, yaw, roll): roll /= 3600 pitch /= 3600 yaw /= 3600 q0 = Quat([0, 0, 45]) dq = Quat([yaw, -pitch, roll]) assert np.isclose(dq.roll, roll) assert np.isclose(dq.pitch, pitch) assert np.isclose(dq.yaw, yaw) q0_offset = q0 * dq assert np.isclose(q0_offset.roll, q0.roll + roll) yags = [] zags = [] yags_obs = [] zags_obs = [] for ra, dec in stars: yag, zag = radec2yagzag(ra, dec, q0) yags.append(yag * 3600) zags.append(zag * 3600) yag, zag = radec2yagzag(ra, dec, q0_offset) yags_obs.append(yag * 3600) zags_obs.append(zag * 3600) sigma = np.arange(len(yags)) + 1 out_roll, out_pitch, out_yaw = calc_roll_pitch_yaw(yags, zags, yags_obs, zags_obs, sigma) # Computed pitch, yaw, roll within 0.5 arcsec in roll, 0.02 arcsec pitch/yaw assert np.isclose(roll, out_roll, atol=0.5 / 3600, rtol=0.0) assert np.isclose(pitch, out_pitch, atol=0.02 / 3600, rtol=0.0) assert np.isclose(yaw, out_yaw, atol=0.02 / 3600, rtol=0.0)
def test_calc_roll(roll): stars = [ (-1, 1), # ra, dec in deg (1, 1), (0, 0), (1, -1) ] q0 = Quat([0, 0, 45]) dq = Quat([0, 0, roll]) assert np.isclose(dq.roll, roll) q0_roll = q0 * dq assert np.isclose(q0_roll.roll, q0.roll + roll) yags = [] zags = [] yags_obs = [] zags_obs = [] for ra, dec in stars: yag, zag = radec2yagzag(ra, dec, q0) yags.append(yag) zags.append(zag) yag, zag = radec2yagzag(ra, dec, q0_roll) yags_obs.append(yag) zags_obs.append(zag) out_roll = calc_roll(yags, zags, yags_obs, zags_obs) # Computed roll is within 0.1% of actual roll assert np.isclose(roll, out_roll, atol=0.0, rtol=0.001)
def get_trak_cat_from_telem(start, stop, cmd_quat): start = DateTime(start) stop = DateTime(stop) msids = [ "{}{}".format(m, i) for m in ['AOACYAN', 'AOACZAN', 'AOACFID', 'AOIMAGE', 'AOACFCT'] for i in range(0, 8) ] telem = fetch.MSIDset( ['AOACASEQ', 'CORADMEN', 'AOPCADMD', 'AONSTARS', 'AOKALSTR'] + msids, start, stop) att = fetch.MSIDset(['AOATTQT{}'.format(i) for i in [1, 2, 3, 4]], start, stop) cat = {} for slot in range(0, 8): track = telem['AOACFCT{}'.format(slot)].vals == 'TRAK' fid = telem['AOACFID{}'.format(slot)].vals == 'FID ' star = telem['AOIMAGE{}'.format(slot)].vals == 'STAR' n = 30 if np.count_nonzero(track) < n: continue if np.any(fid & track): cat[slot] = { 'type': 'FID', 'yag': telem['AOACYAN{}'.format(slot)].vals[fid & track][0], 'zag': telem['AOACZAN{}'.format(slot)].vals[fid & track][0] } else: n_samples = np.count_nonzero(track & star) if n_samples < (n + 4): continue # If there is tracked data with a star, let's try to get our n samples from about # the middle of the range mid_point = int(n_samples / 2.) yags = [] zags = [] for sample in range(mid_point - int(n / 2.), mid_point + int(n / 2.)): qref = Quat( normalize([ att['AOATTQT{}'.format(i)].vals[track & star][sample] for i in [1, 2, 3, 4] ])) ra, dec = yagzag2radec( telem['AOACYAN{}'.format(slot)].vals[track & star][sample] / 3600., telem['AOACZAN{}'.format(slot)].vals[track & star][sample] / 3600., qref) yag, zag = radec2yagzag(ra, dec, cmd_quat) yags.append(yag) zags.append(zag) # This doesn't detect MON just yet cat[slot] = { 'type': 'STAR', 'yag': np.median(yags) * 3600., 'zag': np.median(zags) * 3600. } return cat, telem
def make_plots_for_obsid(obsid, ra, dec, roll, starcat_time, catalog, outdir, red_mag_lim=10.7): """ Make standard starcheck plots for obsid and save as pngs with standard names. Writes out to stars_{obsid}.png and star_view_{obsid}.png in supplied outdir. :param obsid: Obsid used for file names :param ra: RA in degrees :param dec: Dec in degrees :param roll: Roll in degrees :param catalog: list of dicts or other astropy.table compatible structure with conventional starcheck catalog parameters for a set of ACQ/BOT/GUI/FID/MON items. :param outdir: output directory for png plot files :param red_mag_lim: faint limit """ # explicitly float convert these, as we may be receiving this from Perl passing strings ra = float(ra) dec = float(dec) roll = float(roll) # get the agasc field once and then use it for both plots that have stars stars = agasc.get_agasc_cone(ra, dec, radius=1.5, date=starcat_time) # We use the full star list for both the field plot and the main "catalog" plot # and we can save looking up the yang/zang positions twice if we add that content # to the stars in this wrapper yags, zags = radec2yagzag(stars['RA_PMCORR'], stars['DEC_PMCORR'], Quaternion.Quat([ra, dec, roll])) stars['yang'] = yags * 3600 stars['zang'] = zags * 3600 bad_stars = bad_acq_stars(stars) f_plot = plot_stars(attitude=[ra, dec, roll], catalog=None, stars=stars, title=None, starcat_time=starcat_time, bad_stars=bad_stars, red_mag_lim=None) f_plot.savefig(os.path.join(outdir, 'star_view_{}.png'.format(obsid)), dpi=80) plt.close(f_plot) cat_plot = plot_stars(attitude=[ra, dec, roll], catalog=catalog, stars=stars, title="RA=%.6f Dec=%.6f Roll=%.6f" % (ra, dec, roll), starcat_time=starcat_time, bad_stars=bad_stars, red_mag_lim=red_mag_lim) cat_plot.savefig(os.path.join(outdir, 'stars_{}.png'.format(obsid)), dpi=80) plt.close(cat_plot) compass_plot = plot_compass(roll) compass_plot.savefig(os.path.join(outdir, 'compass{}.png'.format(obsid)), dpi=80) plt.close(compass_plot)
def get_trak_cat_from_telem(start, stop, cmd_quat): start = DateTime(start) stop = DateTime(stop) msids = ["{}{}".format(m, i) for m in ['AOACYAN', 'AOACZAN', 'AOACFID', 'AOIMAGE', 'AOACFCT'] for i in range(0, 8)] telem = fetch.MSIDset(['AOACASEQ', 'CORADMEN', 'AOPCADMD', 'AONSTARS', 'AOKALSTR'] + msids, start, stop) att = fetch.MSIDset(['AOATTQT{}'.format(i) for i in [1, 2, 3, 4]], start, stop) cat = {} for slot in range(0, 8): track = telem['AOACFCT{}'.format(slot)].vals == 'TRAK' fid = telem['AOACFID{}'.format(slot)].vals == 'FID ' star = telem['AOIMAGE{}'.format(slot)].vals == 'STAR' n = 30 if np.count_nonzero(track) < n: continue if np.any(fid & track): cat[slot] = {'type': 'FID', 'yag': telem['AOACYAN{}'.format(slot)].vals[fid & track][0], 'zag': telem['AOACZAN{}'.format(slot)].vals[fid & track][0]} else: n_samples = np.count_nonzero(track & star) if n_samples < (n + 4): continue # If there is tracked data with a star, let's try to get our n samples from about # the middle of the range mid_point = int(n_samples / 2.) yags = [] zags = [] for sample in range(mid_point - int(n / 2.), mid_point + int(n / 2.)): qref = Quat(normalize([att['AOATTQT{}'.format(i)].vals[track & star][sample] for i in [1, 2, 3, 4]])) ra, dec = yagzag2radec( telem['AOACYAN{}'.format(slot)].vals[track & star][sample] / 3600., telem['AOACZAN{}'.format(slot)].vals[track & star][sample] / 3600., qref) yag, zag = radec2yagzag(ra, dec, cmd_quat) yags.append(yag) zags.append(zag) # This doesn't detect MON just yet cat[slot] = {'type': 'STAR', 'yag': np.median(yags) * 3600., 'zag': np.median(zags) * 3600.} return cat, telem
def get_stars(starcat_time, quaternion, radius=1.5): import agasc from Ska.quatutil import radec2yagzag from chandra_aca.transform import yagzag_to_pixels stars = agasc.get_agasc_cone(quaternion.ra, quaternion.dec, radius=radius, date=starcat_time) if 'yang' not in stars.colnames or 'zang' not in stars.colnames: # Add star Y angle and Z angle in arcsec to the stars table. # radec2yagzag returns degrees. yags, zags = radec2yagzag(stars['RA_PMCORR'], stars['DEC_PMCORR'], quaternion) stars['yang'] = yags * 3600 stars['zang'] = zags * 3600 # Update table to include row/col values corresponding to yag/zag rows, cols = yagzag_to_pixels(stars['yang'], stars['zang'], allow_bad=True) stars['row'] = rows stars['col'] = cols return stars
def radec_to_yagzag(ra, dec, q_att): """ Given RA, Dec, and pointing quaternion, determine ACA Y-ang, Z-ang. The input ``ra`` and ``dec`` values can be 1-d arrays in which case the output ``yag`` and ``zag`` will be corresponding arrays of the same length. This is a wrapper around Ska.quatutil.radec2yagzag but uses arcsec instead of deg for yag, zag. :param ra: Right Ascension (degrees) :param dec: Declination (degrees) :param q_att: ACA pointing quaternion :returns: yag, zag (arcsec) """ from Ska.quatutil import radec2yagzag yag, zag = radec2yagzag(ra, dec, q_att) yag *= 3600 zag *= 3600 return yag, zag
def _plot_field_stars(ax, stars, attitude, red_mag_lim=None, bad_stars=None): """ Plot plot field stars in yang and zang on the supplied axes object in place. :param ax: matplotlib axes :param stars: astropy.table compatible set of records of agasc entries of stars :param attitude: Quaternion-compatible attitude :param red_mag_lim: faint limit :param bad_stars: boolean mask of stars to be plotted in red """ stars = Table(stars) quat = Quaternion.Quat(attitude) if bad_stars is None: bad_stars = np.zeros(len(stars), dtype=bool) if 'yang' not in stars.colnames or 'zang' not in stars.colnames: # Add star Y angle and Z angle in arcsec to the stars table. # radec2yagzag returns degrees. yags, zags = radec2yagzag(stars['RA_PMCORR'], stars['DEC_PMCORR'], quat) stars['yang'] = yags * 3600 stars['zang'] = zags * 3600 # Update table to include row/col values corresponding to yag/zag rows, cols = yagzag_to_pixels(stars['yang'], stars['zang'], allow_bad=True) stars['row'] = rows stars['col'] = cols # Initialize array of colors for the stars, default is black. Use 'object' # type to not worry in advance about string length and also for Py2/3 compat. colors = np.zeros(len(stars), dtype='object') colors[:] = 'black' colors[bad_stars] = BAD_STAR_COLOR if red_mag_lim: # Mark stars with the FAINT_STAR_COLOR if they have MAG_ACA # that is fainter than red_mag_lim but brighter than red_mag_lim # plus a rough mag error. The rough mag error calculation is # based on the SAUSAGE acq stage 1 check, which uses nsigma of # 3.0, a mag low limit of 1.5, and a random error of 0.26. nsigma = 3.0 mag_error_low_limit = 1.5 randerr = 0.26 caterr = stars['MAG_ACA_ERR'] / 100. error = nsigma * np.sqrt(randerr**2 + caterr**2) error = error.clip(mag_error_low_limit) # Faint and bad stars will keep their BAD_STAR_COLOR # Only use the faint mask on stars that are not bad colors[(stars['MAG_ACA'] >= red_mag_lim) & (stars['MAG_ACA'] < red_mag_lim + error) & ~bad_stars] = FAINT_STAR_COLOR # Don't plot those for which MAG_ACA is fainter than red_mag_lim + error # This overrides any that may be 'bad' colors[stars['MAG_ACA'] >= red_mag_lim + error] = 'none' size = symsize(stars['MAG_ACA']) # scatter() does not take an array of alphas, and rgba is # awkward for color='none', so plot these in a loop. for color, alpha in [(FAINT_STAR_COLOR, FAINT_STAR_ALPHA), (BAD_STAR_COLOR, BAD_STAR_ALPHA), ('black', 1.0)]: colormatch = colors == color ax.scatter(stars[colormatch]['row'], stars[colormatch]['col'], c=color, s=size[colormatch], edgecolor='none', alpha=alpha)
def plot_starcheck(catalog, quat=None, field=None, title=None): cat = catalog fig = plt.figure(num=1, figsize=(4, 4)) ax = fig.add_subplot(1, 1, 1) face = backcolor # plot the box and set the labels plt.xlim(2900, -2900) plt.ylim(-2900, 2900) b1hw = 2560 box1 = plt.Rectangle((b1hw, -b1hw), -2 * b1hw, 2 * b1hw, fill=False) ax.add_patch(box1) b2hw = 2600 box2 = plt.Rectangle((b2hw, -b2hw), -2 * b2hw, 2 * b2hw, fill=False) ax.add_patch(box2) ax.scatter([-2700, -2700, -2700, -2700, -2700], [2400, 2100, 1800, 1500, 1200], c='orange', edgecolors='orange', s=symsize(np.array([10.0, 9.0, 8.0, 7.0, 6.0]))) [l.set_rotation(90) for l in ax.get_yticklabels()] ax.grid() ax.set_ylabel("Zag (arcsec)") ax.set_xlabel("Yag (arcsec)") # plot starcheck catalog gui = cat[cat['type'] == 'GUI'] acq = cat[cat['type'] == 'ACQ'] bot = cat[cat['type'] == 'BOT'] fid = cat[cat['type'] == 'FID'] for row in cat: ax.annotate("%s" % row['idx'], xy=(row['yang'] - 120, row['zang'] + 60)) ax.scatter(gui['yang'], gui['zang'], facecolors=face, edgecolors='green', s=100) ax.scatter(bot['yang'], bot['zang'], facecolors=face, edgecolors='green', s=100) for acq_star in acq: acq_box = plt.Rectangle( (acq_star['yang'] - acq_star['halfw'], acq_star['zang'] - acq_star['halfw']), width=acq_star['halfw'] * 2, height=acq_star['halfw'] * 2, color='blue', fill=False) ax.add_patch(acq_box) for acq_star in bot: acq_box = plt.Rectangle( (acq_star['yang'] - acq_star['halfw'], acq_star['zang'] - acq_star['halfw']), width=acq_star['halfw'] * 2, height=acq_star['halfw'] * 2, color='blue', fill=False) ax.add_patch(acq_box) ax.scatter(fid['yang'], fid['zang'], facecolors=face, edgecolors='green', marker='o', s=200) ax.scatter(fid['yang'], fid['zang'], facecolors=face, edgecolors='green', marker='+', linewidth=3, s=200) # plot field if present if field is not None: faint_plot_mag = 11.0 bright_field = field[field['MAG_ACA'] < faint_plot_mag] yags = [] zags = [] for star in bright_field: yag, zag = radec2yagzag(star['RA_PMCORR'], star['DEC_PMCORR'], quat) yag *= 3600 zag *= 3600 yags.append(yag) zags.append(zag) bright_field = Ska.Numpy.add_column(bright_field, 'yang', yags) bright_field = Ska.Numpy.add_column(bright_field, 'zang', zags) color = np.ones(len(bright_field), dtype='|S10') color[:] = 'red' faint = ((bright_field['CLASS'] == 0) & (bright_field['MAG_ACA'] >= 10.7)) color[faint] = 'orange' ok = ((bright_field['CLASS'] == 0) & (bright_field['MAG_ACA'] < 10.7)) color[ok] = frontcolor size = symsize(bright_field['MAG_ACA']) ax.scatter(bright_field['yang'], bright_field['zang'], c=color.tolist(), s=size, edgecolors=color.tolist()) if title is not None: fig.suptitle(title) return fig
def _plot_field_stars(ax, stars, attitude, red_mag_lim=None, bad_stars=None): """ Plot plot field stars in yang and zang on the supplied axes object in place. :param ax: matplotlib axes :param stars: astropy.table compatible set of records of agasc entries of stars :param attitude: Quaternion-compatible attitude :param red_mag_lim: faint limit :param bad_stars: boolean mask of stars to be plotted in red """ stars = Table(stars) quat = Quaternion.Quat(attitude) if bad_stars is None: bad_stars = np.zeros(len(stars), dtype=bool) if 'yang' not in stars.colnames or 'zang' not in stars.colnames: # Add star Y angle and Z angle in arcsec to the stars table. # radec2yagzag returns degrees. yags, zags = radec2yagzag(stars['RA_PMCORR'], stars['DEC_PMCORR'], quat) stars['yang'] = yags * 3600 stars['zang'] = zags * 3600 # Initialize array of colors for the stars, default is black colors = np.zeros(len(stars), dtype='S20') colors[:] = 'black' colors[bad_stars] = BAD_STAR_COLOR if red_mag_lim: # Mark stars with the FAINT_STAR_COLOR if they have MAG_ACA # that is fainter than red_mag_lim but brighter than red_mag_lim # plus a rough mag error. The rough mag error calculation is # based on the SAUSAGE acq stage 1 check, which uses nsigma of # 3.0, a mag low limit of 1.5, and a random error of 0.26. nsigma = 3.0 mag_error_low_limit = 1.5 randerr = 0.26 caterr = stars['MAG_ACA_ERR'] / 100. error = nsigma * np.sqrt(randerr**2 + caterr**2) error = error.clip(mag_error_low_limit) # Faint and bad stars will keep their BAD_STAR_COLOR # Only use the faint mask on stars that are not bad colors[(stars['MAG_ACA'] >= red_mag_lim) & (stars['MAG_ACA'] < red_mag_lim + error) & ~bad_stars] = FAINT_STAR_COLOR # Don't plot those for which MAG_ACA is fainter than red_mag_lim + error # This overrides any that may be 'bad' colors[stars['MAG_ACA'] >= red_mag_lim + error] = 'none' size = symsize(stars['MAG_ACA']) # scatter() does not take an array of alphas, and rgba is # awkward for color='none', so plot these in a loop. for color, alpha in [(FAINT_STAR_COLOR, FAINT_STAR_ALPHA), (BAD_STAR_COLOR, BAD_STAR_ALPHA), ('black', 1.0)]: colormatch = colors == color ax.scatter(stars[colormatch]['yang'], stars[colormatch]['zang'], c=color, s=size[colormatch], edgecolor='none', alpha=alpha)
def schedule_monitor_windows(t, predefined_locs=False, mag_thres=13, radius=25): """ :t: astropy Table :predefined_locs: if True, try the predefined CCD locations first, default=False. """ locations = Table.read(PREDEFINED, delimiter=' ', format='ascii') for row in t: obsid = row['obsid'] dur = row['duration'] num_allowed = row['num_mon_windows'] msg = ' '.join([ '\nObsID = {}, duration = {:.0f} sec:'.format(obsid, dur), '{} monitor windows allowed'.format(num_allowed) ]) print(msg) date = row['date'] quat = row['quat'] num_scheduled = 0 stars = {} # If flag set, try predefined locations first if predefined_locs: print("Predefined locations:") kwargs = {'mag_thres': mag_thres, 'radius': radius} if not all(locations['scheduled']) == 1: idx_list = schedule_predefined(locations, date, quat, num_allowed, **kwargs) num_scheduled = len(idx_list) # Update status of predefined locations for idx in idx_list: locations['scheduled'][idx] = 1 # All allowed predefined locations are scheduled, or predefined_locs=False, # Add random locations if needed if num_scheduled < num_allowed: print("Random locations:") ra, dec = quatutil.yagzag2radec(0, 0, quat) cat = agasc.agasc.get_agasc_cone(ra, dec, 1.5, date) # Filter to pick up bright spoiler stars ok = np.array(cat['MAG_ACA'] < mag_thres, dtype=bool) cat = cat[ok] yags, zags = quatutil.radec2yagzag(cat['RA_PMCORR'], cat['DEC_PMCORR'], quat) rows, cols = transform.yagzag_to_pixels(yags * 3600., zags * 3600, allow_bad=True) # Identify spoiler stars that fit on ccd ok = (rows > -512.5) * (rows < 511.5) * (cols > -512.5) * (cols < 511.5) rows = np.array(np.round(rows[ok]), dtype=int) cols = np.array(np.round(cols[ok]), dtype=int) # Collect spoiler stars vals = np.ones(len(rows)) stars = get_spoiler_stars(rows, cols, vals) # Now dict stars contains keys that represent (row, col) of # spoiler star centers and a 10px rim around each spoiler star. while num_scheduled < num_allowed: # Draw a random location on ccd, avoid edges: r, c = np.random.randint(-504, 505, 2) # Check if the location was previously scheduled - TBD # Check if the location is free of stars if (r, c) not in stars: yag, zag = transform.pixels_to_yagzag( r, c) # yag, zag in arcsec msg = ' '.join([ 'Schedule {} monitor window at:\n'.format( num_scheduled + 1), ' (yag, zag) = ({:.0f}, {:.0f}) arcsec\n'.format( yag, zag), ' (row, col) = ({}, {})'.format(r, c) ]) print(msg) num_scheduled = num_scheduled + 1 stars = get_spoiler_stars([r], [c], [5], stars, rim=4) # Update the PREDEFINED file - TBD return stars