def schedule_predefined(locations, date, quat, num_allowed, mag_thres=13, radius=25): """ :locations: astropy Table with predefined yag, zag, row0, col0 coordinates, and number of times the location has been scheduled in the past :date: date :quat: pointing quaternion of this dwell :num_allowed: allowed number of monitor windows for this dwell :mag_thres: faintest magnitude of a spoiler star. Default = 13 mag :radius: radius of search for spoiler stars. Default = 25 arcsec """ radius = (radius * u.arcsec).to('deg').value num_scheduled = 0 idx_list = [] for i, row in enumerate(locations): if num_scheduled < num_allowed and row['scheduled'] != 1: r = row['row'] c = row['col'] yag, zag = transform.pixels_to_yagzag(r, c) # yag, zag in arcsec ra, dec = quatutil.yagzag2radec(yag / 3600., zag / 3600., quat) cat = agasc.agasc.get_agasc_cone(ra, dec, radius, date) if all(cat['MAG_ACA']) > mag_thres or len(cat) == 0: 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 idx_list.append(i) """ else: msg = ' '.join(['Spoiler star(s) found at:\n', ' (yag, zag) = ({:.0f}, {:.0f}) arcsec\n'.format(yag, zag), ' (row, col) = ({}, {})'.format(r, c)]) print(msg) ok = cat['MAG_ACA'] <= mag_thres print(cat['AGASC_ID'][ok]) """ return idx_list
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 get_centroids(slot_data, img_size, bgd_object, nframes=None): """ For each frame: 1. Compute background image using get_background() method of bgd_object. Algorithm depends on the class of bgd_object - FlightBgd: current on-board algorithm - DarkCurrent_Median_Bgd: median for sampled pixels, average bgd for not sampled pixels - DarkCurrent_SigmaClip_Bgd: sigma clipping for sampled pixels, avg bgd for not sampled pixels 2. Subtract background 3. Compute centroids in image coordinates 0:8 (variable name: row/col) 4. Transform to get yagzag coordinates Returns: * rowcol_centroids: list of nframes centroid pairs, [[row centroid1, col centroid1], [], ...] * yanzan_centroids: list of nframes centroid pairs, [[yag centroid1, zag centroid1], [], ...] * bgd_imgs: list of nframes 8x8 arrays representing background images * deque_dicts: list of nframes dictionaries with keys being pixel coordinates, e.g. (210, 130), and vals being deques of ndeque pixel value samples :param slot_data: simulated or flight aca_l0 slot data :param img_size: image size, either 6 or 8 (pixels) :param bgd_object: background object defined in classes.py :param nframes: number of time frames """ if img_size not in [6, 8]: raise ValueError('get_centroids:: expected img_size = 6 or 8') if nframes is None: nframes = len(slot_data) yagzag_centroids = [] rowcol_centroids = [] bgd_imgs = [] deque_dicts = [] for index in range(0, nframes): frame_data = slot_data[index:index + 1] bgd_object.bgdavg = frame_data['BGDAVG'][0] if isinstance(bgd_object, (DynamBgd_Median, DynamBgd_SigmaClip)): bgd_object.img = frame_data['IMGRAW'][0] bgd_object.row0 = frame_data['IMGROW0'][0] bgd_object.col0 = frame_data['IMGCOL0'][0] bgd_img = bgd_object.get_background() # 8x8 bgd_imgs.append(bgd_img) if isinstance(bgd_object, (DynamBgd_Median, DynamBgd_SigmaClip)): deque_dict = bgd_object.deque_dict deque_dicts.append(deepcopy(deque_dict)) raw_img = frame_data['IMGRAW'][0].reshape(8, 8) img = raw_img - bgd_img # If dynamic background, don't oversubtract? # Value of pixel with bgd > raw image value will be set to zero: #if isinstance(bgd_object, (DynamBgd_Median, DynamBgd_SigmaClip)): # bgd_mask = bgd_img > raw_img # img = raw_img - ma.array(bgd_img, mask=bgd_mask) # img = img.data * ~bgd_mask # Calculate centroids for current bgd-subtracted img, use first moments rowcol = get_current_centroids(img, img_size) rowcol_centroids.append(rowcol) # Translate (row, column) centroid to (yag, zag) y_pixel = rowcol[0] + frame_data['IMGROW0'] z_pixel = rowcol[1] + frame_data['IMGCOL0'] yagzag = transform.pixels_to_yagzag(y_pixel, z_pixel) yagzag_centroids.append(yagzag) return rowcol_centroids, yagzag_centroids, bgd_imgs, deque_dicts
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