def test_edge_checking(): """Test row/col edge checking""" # Within limits, doesn't fail yag, zag = pixels_to_yagzag(511.7, -511.7) yagzag_to_pixels(yag, zag) with pytest.raises(ValueError): pixels_to_yagzag(512.2, 0) with pytest.raises(ValueError): pixels_to_yagzag(0, -512.2) yag, zag = pixels_to_yagzag(512.2, -512.2, allow_bad=True) with pytest.raises(ValueError): yagzag_to_pixels(yag, zag)
def test_monitor_input_processing_ra_dec(stars): ra, dec = 0.1, 0.2 mag = 7.0 monitors = [[ra, dec, MonCoord.RADEC, mag, MonFunc.MON_FIXED]] aca = get_aca_catalog(**mod_std_info(att=stars.att, n_fid=0, n_guide=5), stars=stars, monitors=monitors) monitors = aca.mons.monitors assert len(monitors) == 1 assert isinstance(monitors, Table) yang, zang = radec_to_yagzag(ra, dec, aca.att) row, col = yagzag_to_pixels(yang, zang) assert np.allclose(monitors['yang'][0], yang) assert np.allclose(monitors['zang'][0], zang) assert np.allclose(monitors['ra'][0], ra) assert np.allclose(monitors['dec'][0], dec) assert np.allclose(monitors['row'][0], row) assert np.allclose(monitors['col'][0], col) ok = aca['type'] == 'MON' assert np.count_nonzero(ok) == 1 mon = aca[ok][0] assert np.allclose(mon['yang'], yang) assert np.allclose(mon['zang'], zang) assert np.allclose(mon['mag'], mag) assert np.allclose(mon['maxmag'], ACA.monitor_maxmag) assert mon['id'] == 1000
def get_fid_candidates(self): """ Get all fids for this detector that are on the CCD (with margin) and are not impacted by a bad pixel. This also finds fid spoiler stars and computes the spoiler_score. Result is updating self.cand_fids. """ yang, zang = get_fid_positions(self.detector, self.focus_offset, self.sim_offset) row, col = yagzag_to_pixels(yang, zang, allow_bad=True) ids = np.arange(len(yang), dtype=np.int64) + 1 # E.g. 1 to 6 for ACIS # Set up candidate fids table (which copies relevant meta data) and add # columns. cand_fids = FidTable([ids, yang, zang, row, col], names=['id', 'yang', 'zang', 'row', 'col']) shape = (len(cand_fids), ) cand_fids['mag'] = np.full(shape, FID.fid_mag) # 7.000 cand_fids['spoilers'] = np.full( shape, None) # Filled in with Table of spoilers cand_fids['spoiler_score'] = np.full(shape, 0, dtype=np.int64) self.log(f'Initial candidate fid ids are {cand_fids["id"].tolist()}') # First check that any manually included fid ids are valid by seeing if # the supplied fid is in the initial ids for this detector. if id_diff := set(self.include_ids_fid) - set(cand_fids['id']): raise ValueError(f'included fid ids {id_diff} are not valid')
def add_imposter(dark, acq, dyang, dzang, dmag): """ For testing, add an imposter (single hot pixel) at the specified delta location and mag relative to an ``acq`` star. Returns a new dark map. """ dark = dark.copy() yang = acq['yang'] + dyang zang = acq['zang'] + dzang row, col = yagzag_to_pixels(yang, zang) row0 = int(row + 512) col0 = int(col + 512) dark[row0, col0] += mag_to_count_rate(acq['mag'] + dmag) return dark
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 add_spoiler(stars, acq, dyang, dzang, dmag, mag_err=0.05): """ For testing, add a spoiler stars at the specified delta location and mag relative to an ``acq`` star. Returns a new stars table. """ stars = stars.copy() ok = stars['id'] == acq['id'] stars.add_row(stars[ok][0]) star = stars[-1] star['id'] = -star['id'] star['yang'] = acq['yang'] + dyang star['zang'] = acq['zang'] + dzang star['mag'] = acq['mag'] + dmag star['mag_err'] = mag_err star['MAG_ACA'] = star['mag'] row, col = yagzag_to_pixels(star['yang'], star['zang']) star['row'] = row star['col'] = col star['CLASS'] = 0 return stars
def plot_crs_visualization(obsid, plot_dir, crs=None, factor=20, save=False, on_the_fly=False): """ Plot visualization of OBC centroid residuals with respect to ground (obc) aspect solution for science (ER) observations in the yang/zang plain. :param obsid: obsid :param crs: dictionary with keys 'ground' and 'obc'. Dictionary values are dictionaries keyed by slot number containing corresponding CentroidResiduals objects. If ground or obc centroid residuals cannot be computed for a given slot, the value is None. :param on_the_fly: default False, if True then ignore param crs and calculate centroid residuals for the requested obsid """ # catalog cat = get_starcat(obsid) # keep only BOT and GUI entries ok = (cat['type'] == 'BOT') | (cat['type'] == 'GUI') cat = cat[ok] cat['idx'] = cat['slot'] # so that the plot is numbered by slot # attitude att = get_att(obsid) # stars cols = ['RA_PMCORR', 'DEC_PMCORR', 'MAG_ACA', 'MAG_ACA_ERR', 'CLASS', 'ASPQ1', 'ASPQ2', 'ASPQ3', 'VAR', 'POS_ERR'] stars = Table(names=cols) for star in cat: row = [] s = get_star(star['id']) for col in cols: row.append(s[col]) stars.add_row(row) fig = plot_stars(att, cat, stars) cs = ['orange', 'forestgreen', 'steelblue', 'maroon', 'gray'] if on_the_fly: crs = get_crs_per_obsid(obsid) else: if crs is None: raise ValueError('Need to provide crs if on_the_fly is False') if obsid > 40000: # ERs crs_ref = crs['obc'] else: crs_ref = crs['ground'] ax = fig.axes[0] for slot in cat['slot']: ok = cat['slot'] == slot yag = cat['yang'][ok] zag = cat['zang'][ok] yp, zp = yagzag_to_pixels(yag, zag) # 1 px -> factor px; 5 arcsec = 5 * factor arcsec try: """ Minus sign for y-coord to reflect sign flip in the pixel to yag conversion and yag scale going from positive to negative """ yy = yp - crs_ref[slot].dyags * factor zz = zp + crs_ref[slot].dzags * factor ax.plot(yy, zz, alpha=0.3, marker=',', color=cs[slot - 3]) ax.plot([-1000, -1020], [2700, 2700], color='k', lw=3) circle = plt.Circle((yp, zp), 5 * factor, color='darkorange', fill=False) ax.add_artist(circle) except Exception: pass plt.text(-511, 530, "ring radius = 5 arcsec (scaled)", color='darkorange') if save: outroot = os.path.join(plot_dir, f'crs_vis_{obsid}') logger.info(f'Writing plot file {outroot}.png') plt.savefig(outroot + '.png') plt.close() return crs
def process_monitors(self): """Process monitor window requests""" if self.monitors is None: return # Add columns for each of the three coordinate representations. The # original list input for monitors has been turned into a Table by the # Meta processing. monitors = self.monitors monitors['id'] = 0 monitors['ra'] = 0.0 monitors['dec'] = 0.0 monitors['yang'] = 0.0 monitors['zang'] = 0.0 monitors['row'] = 0.0 monitors['col'] = 0.0 for monitor in monitors: if monitor['coord_type'] == MonCoord.RADEC: # RA, Dec monitor['ra'], monitor['dec'] = monitor['coord0'], monitor['coord1'] monitor['yang'], monitor['zang'] = radec_to_yagzag( monitor['ra'], monitor['dec'], self.att) monitor['row'], monitor['col'] = yagzag_to_pixels( monitor['yang'], monitor['zang'], allow_bad=True) elif monitor['coord_type'] == MonCoord.ROWCOL: # Row, col monitor['row'], monitor['col'] = monitor['coord0'], monitor['coord1'] monitor['yang'], monitor['zang'] = pixels_to_yagzag( monitor['row'], monitor['col'], allow_bad=True, flight=True) monitor['ra'], monitor['dec'] = yagzag_to_radec( monitor['yang'], monitor['zang'], self.att) elif monitor['coord_type'] == MonCoord.YAGZAG: # Yag, zag monitor['yang'], monitor['zang'] = monitor['coord0'], monitor['coord1'] monitor['row'], monitor['col'] = yagzag_to_pixels( monitor['yang'], monitor['zang'], allow_bad=True) monitor['ra'], monitor['dec'] = yagzag_to_radec( monitor['yang'], monitor['zang'], self.att) # Process bona fide monitor windows according to function mon_id = 1000 for monitor in self.monitors: if monitor['function'] in (MonFunc.GUIDE, MonFunc.MON_TRACK): # Try to get star at MON position dist = np.linalg.norm([self.stars['yang'] - monitor['yang'], self.stars['zang'] - monitor['zang']], axis=0) idx = np.argmin(dist) if dist[idx] < 2.0: star = self.stars[idx] monitor['id'] = star['id'] monitor['mag'] = star['mag'] elif monitor['function'] == MonFunc.GUIDE: raise BadMonitorError('no acceptable AGASC star within ' '2 arcsec of monitor position') if monitor['function'] in (MonFunc.MON_FIXED, MonFunc.MON_TRACK): if monitor['id'] == 0: monitor['id'] = mon_id mon_id += 1 # Make a stub row for a MON entry using zero everywhere. This # also works for str (giving '0'). mon = {col.name: col.dtype.type(0) for col in self.itercols()} # These type codes get fixed later in merge_catalog mon['type'] = 'MFX' if monitor['function'] == MonFunc.MON_FIXED else 'MTR' mon['sz'] = '8x8' mon['dim'] = -999 # Set an obviously bad value for DTS, gets fixed later. mon['res'] = 0 mon['halfw'] = 20 mon['maxmag'] = ACA.monitor_maxmag for name in ('id', 'mag', 'yang', 'zang', 'row', 'col', 'ra', 'dec'): mon[name] = monitor[name] # Finally add the MON as a row in table self.add_row(mon) elif monitor['function'] != MonFunc.GUIDE: raise ValueError(f'unexpected monitor function {monitor["function"]}')
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