def _nd_log_msg_(ant, reply, informs): """construct debug log messages for noisediode""" user_logger.debug('DEBUG: reply = {}' .format(reply)) user_logger.debug('DEBUG: arguments ({})= {}' .format(len(reply.arguments), reply.arguments)) user_logger.debug('DEBUG: informs = {}' .format(informs)) if len(reply.arguments) < 4: msg = 'Unexpected number of return arguments\n' msg += '4 values expected: code time on-frac cycle-len' msg += 'Found {}'.format(len(reply.arguments)) raise RuntimeError(msg) actual_time = float(reply.arguments[1]) actual_on_frac = float(reply.arguments[2]) actual_cycle = float(reply.arguments[3]) msg = ('Noise diode for antenna {} set at {}. ' .format(ant, actual_time)) user_logger.debug(msg) msg = ('Pattern set as {} sec ON for {} sec cycle length' .format(actual_on_frac * actual_cycle, actual_cycle)) user_logger.debug(msg) return actual_time
def _switch_on_off_(kat, timestamp, switch=0): """Switch noise-source on or off. Parameters ---------- kat : session kat container-like object Container for accessing KATCP resources allocated to schedule block. timestamp : float, optional Time since the epoch as a floating point number [sec] switch: int, optional off = 0 (default), on = 1 Returns ------- timestamp : float Linux timestamp reported by digitiser """ on_off = {0: 'off', 1: 'on'} msg = ('Request switch noise-diode {} at {}' .format(on_off[switch], timestamp)) user_logger.debug('DEBUG: {}'.format(msg)) # Noise Diodes are triggered on all antennas in array simultaneously # add lead time to ensure all digitisers set at the same time timestamp = _set_dig_nd_(kat, timestamp, switch=switch) return timestamp
def log_message(msg, level='info', boldtype=False, colourtext='black'): bold = boldtype colour = colourtext if level == 'debug': user_logger.debug(str(msg)) elif level == 'info': user_logger.info(str(msg)) elif level == 'warn': user_logger.warn(str(msg)) colour = 'orange' bold = True elif level == 'error': user_logger.error(str(msg)) colour = 'red' bold = True if not bold and colour == 'black': email_msg.append(timestamp() + level.upper() + ' ' + str(msg)) elif colour != 'black' and not(bold): email_msg.append('<font color="{colour}">{timestamp} {level} {msg}</font>'.format( colour=colour, timestamp=timestamp(), level=level.upper(), msg=str(msg))) else: email_msg.append('<font color="{colour}"><b>{timestamp} {level} {msg}</b></font>'.format( colour=colour, timestamp=timestamp(), level=level.upper(), msg=str(msg)))
def _katcp_reply_(dig_katcp_replies): """ KATCP timestamp return logs""" ant_ts_list = [] for ant in sorted(dig_katcp_replies): reply, informs = dig_katcp_replies[ant] if reply.reply_ok(): ant_ts_list.append(_nd_log_msg_(ant, reply, informs)) else: msg = 'Unexpected noise diode reply from ant {}'.format(ant) user_logger.warn(msg) user_logger.debug('DEBUG: {}'.format(reply.arguments)) continue # assume all ND timestamps similar and return average return np.mean(ant_ts_list)
def get_coordinates_as_radec(target_str, observer=None, convert_azel=False): """If celestial target is not (Ra, Dec) convert and return (Ra, Dec)""" target_str_type, target_str_coord = target_str.split('=') tgt_type = target_str_type.strip() tgt_coord = target_str_coord.strip() # a fundamental assumption will be that the user will give coordinates # in degrees, thus all input and output are in degrees if tgt_type == 'radec': try: ra_deg, dec_deg = np.array(tgt_coord.split(), dtype=float) pointing = SkyCoord(ra=ra_deg * u.degree, dec=dec_deg * u.degree, frame='icrs') except ValueError: ra_str, dec_str = tgt_coord.split() pointing = SkyCoord(ra=ra_str.strip(), dec=dec_str.strip(), unit=(u.hourangle, u.deg), frame='icrs') ra_hms, dec_dms = radec_to_string(pointing.ra, pointing.dec) tgt_coord = '{} {}'.format(ra_hms, dec_dms) elif tgt_type == 'gal': l_deg, b_deg = np.array(tgt_coord.split(), dtype=float) ra_hms, dec_dms = galactic_to_radec(l_deg, b_deg, as_string=True) tgt_type = "radec" tgt_coord = '{} {}'.format(ra_hms, str(dec_dms)) elif tgt_type == 'azel' and convert_azel: if observer is None: raise RuntimeError('(alt, az) -> (ra, dec) need observer input') location = observer_as_earth_location(observer) timestamp = datetime2timestamp(observer.date.datetime()) az_deg, el_deg = np.array(tgt_coord.split(), dtype=float) user_logger.debug("DEBUG: (az, el) to (ra, dec) conversion @ " "{} ({})".format(observer, observer.date)) ra_hms, dec_dms = altaz_to_radec(az_deg, el_deg, location, timestamp, as_string=True) tgt_type = "radec" tgt_coord = '{} {}'.format(ra_hms, dec_dms) else: pass # do nothing just pass the target along return tgt_type, tgt_coord
def parse_target_string(target_str, observer=None): """Unpack target input string into dictionary for easy parsing Input string format: name=, radec=, tags=, duration=, ... """ target = {} target_items = [item.strip() for item in target_str.split(",")] user_logger.debug("DEBUG: input target string '{}'".format(target_str)) # find observation type if specified obs_type = next((item for item in target_items if 'type' in item), None) convert_azel = False if obs_type is not None: _, what_type = obs_type.split('=') # if azel coord are scan observation, convert to (ra, dec) convert_azel = 'scan' in what_type target_keys = tgt_desc["names"] for item in target_items: key, value = item.split('=') for coord in SUPPORTED_COORDINATE_TYPES: if key.strip().startswith(coord): target["target_str"] = item # convert target coordinates to (ra, dec) in general target["coord"] = get_coordinates_as_radec( item, observer=observer, convert_azel=convert_azel) break if key.strip() in target_keys: target[key.strip()] = value.strip() if 'model' in key.strip(): target['flux_model'] = value.strip() else: target['flux_model'] = () if 'type' in key.strip(): target['obs_type'] = value.strip() if 'nd' in key.strip(): target['noise_diode'] = value.strip() if "coord" not in target.keys(): raise RuntimeError( "Target \'{}\' not currently supported by default".format( target_str)) if "duration" not in target.keys(): raise RuntimeError( "Target \'{}\' definition needs duration parameter".format( target_str)) user_logger.debug('DEBUG: output target \n{}'.format(target)) return target
def on(kat, timestamp=None, lead_time=_DEFAULT_LEAD_TIME): """Switch noise-source pattern on. Parameters ---------- kat : session kat container-like object Container for accessing KATCP resources allocated to schedule block. timestamp : float, optional (default = None) Time since the epoch as a floating point number [sec] lead_time : float, optional (default = system default lead time) Lead time before the noisediode is switched on [sec] Returns ------- timestamp : float Linux timestamp reported by digitiser """ if timestamp is None: timestamp = _get_nd_timestamp_(lead_time) true_timestamp = _switch_on_off_(kat, timestamp, switch=1) # on sleeptime = true_timestamp - time.time() user_logger.debug('DEBUG: now {}, sleep {}' .format(time.time(), sleeptime)) time.sleep(sleeptime) # default sleep to see for signal to get through user_logger.debug('DEBUG: now {}, slept {}' .format(time.time(), sleeptime)) msg = ('Report: noise-diode on at {}' .format(true_timestamp)) user_logger.info(msg) return true_timestamp
def read(target_items, observer=None): """Read targets info. Unpack targets target items to a katpoint compatible format Update all targets to have celestial (Ra, Dec) coordinates """ ntargets = len(target_items) target_rec_array = np.recarray(ntargets, dtype=tgt_desc) for cnt, target_item in enumerate(target_items): # build astrokat target info from dict definition target_dict = parse_target_string(target_item, observer=observer) # accumulate individual target dictionaries into # observation ready target rec-array target_tuple = build_target_tuple(target_dict) user_logger.debug('DEBUG: target object \n{}'.format(target_tuple)) target_rec_array[cnt] = target_tuple user_logger.debug('DEBUG: target parameters \n{}'.format( target_rec_array.dtype.names)) user_logger.trace('TRACE: target parameters types \n{}'.format( target_rec_array.dtype)) return target_rec_array
def scan(session, target, nd_period=None, lead_time=None, **kwargs): """Run basic scan observation. Parameters ---------- session: `CaptureSession` target: katpoint.Target nd_period: float noisediode period lead_time: float noisediode trigger lead time """ # trigger noise diode if set trigger(session.kat, duration=nd_period, lead_time=lead_time) try: timestamp = session.time except AttributeError: timestamp = time.time() user_logger.debug( "DEBUG: Starting scan across target: {}".format(timestamp)) user_logger.info("Scan target: {}".format(target)) return session.scan(target, **kwargs)
def on(kat, timestamp=None, lead_time=None): """Switch noise-source pattern on. Parameters ---------- kat : session kat container-like object Container for accessing KATCP resources allocated to schedule block. timestamp : float, optional (default = None) Time since the epoch as a floating point number [sec] lead_time : float, optional (default = system default lead time) Lead time before the noisediode is switched on [sec] Returns ------- timestamp : float Linux timestamp reported by digitiser """ if timestamp is None: timestamp = _get_nd_timestamp_(lead_time) true_timestamp = _switch_on_off_(kat, timestamp, switch=1) # on # NaN timestamp return during ND on command means something went wrong # abort observation if not np.isfinite(true_timestamp): msg = ('Failed to switch ND on, timestamp = {}, observation aborted'. format(true_timestamp)) raise RuntimeError(msg) sleeptime = true_timestamp - time.time() user_logger.debug('DEBUG: now {}, sleep {}'.format(time.time(), sleeptime)) if sleeptime > 0: time.sleep(sleeptime) # default sleep to see for signal to get through user_logger.debug('DEBUG: now {}, slept {}'.format(time.time(), sleeptime)) msg = ('Report: noise-diode on at {}'.format(true_timestamp)) user_logger.info(msg) return true_timestamp
def observe(session, ref_antenna, target_info, **kwargs): """Target observation functionality. Parameters ---------- session: `CaptureSession` target_info: """ target_visible = False target_name = target_info["name"] target = target_info["target"] duration = target_info["duration"] obs_type = target_info["obs_type"] # update (Ra, Dec) for horizontal coordinates @ obs time if ("azel" in target_info["target_str"]) and ("radec" in target.tags): tgt_coord = target_info["target_str"].split('=')[-1].strip() ra_hms, dec_dms = _get_radec_from_azel(ref_antenna.observer, tgt_coord, time.time()) target.body._ra = ra_hms target.body._dec = dec_dms # simple way to get telescope to slew to target if "slewonly" in kwargs: return session.track(target, duration=0.0, announce=False) # set noise diode behaviour nd_setup = None nd_lead = None if kwargs.get("noise_diode"): nd_setup = kwargs["noise_diode"] # user specified lead time if "lead_time" in nd_setup: nd_lead = nd_setup['lead_time'] # not a ND pattern if "cycle_len" not in nd_setup: nd_setup = None # implement target specific noise diode behaviour nd_period = None nd_restore = False if target_info["noise_diode"] is not None: if "off" in target_info["noise_diode"]: user_logger.info('Observation: No ND for target') nd_restore = True # disable noise diode pattern for target noisediode.off(session.kat, lead_time=nd_lead) else: nd_period = float(target_info["noise_diode"]) msg = "Initialising {} {} {}".format(obs_type.capitalize(), ", ".join(target.tags[1:]), target_name) if not np.isnan(duration): # scan types do not have durations msg += " for {} sec".format(duration) if np.isnan(duration) or duration > 1: user_logger.info(msg) # do the different observations depending on requested type session.label(obs_type.strip()) user_logger.trace("TRACE: performing {} observation on {}".format( obs_type, target)) if "drift_scan" in obs_type: target_visible = scans.drift_scan(session, ref_antenna, target, duration=duration, nd_period=nd_period, lead_time=nd_lead) elif "scan" in obs_type: # compensating for ' and spaces around key values if "raster_scan" in obs_type: if ("raster_scan" not in kwargs.keys()) or ("num_scans" not in kwargs["raster_scan"]): raise RuntimeError("{} needs 'num_scans' parameter".format( obs_type.capitalize())) nscans = float(kwargs["raster_scan"]["num_scans"]) if "scan_duration" not in kwargs["raster_scan"]: kwargs["raster_scan"]["scan_duration"] = duration / nscans else: if 'scan' not in kwargs.keys(): kwargs['scan'] = {'duration': duration} else: kwargs['scan']['duration'] = duration # TODO: fix raster scan and remove this scan hack if "forwardscan" in obs_type: scan_func = scans.forwardscan obs_type = "scan" elif "reversescan" in obs_type: scan_func = scans.reversescan obs_type = "scan" elif "return_scan" in obs_type: scan_func = scans.return_scan obs_type = "scan" elif "raster_scan" in obs_type: scan_func = scans.raster_scan else: scan_func = scans.scan target_visible = scan_func(session, target, nd_period=nd_period, lead_time=nd_lead, **kwargs[obs_type]) else: # track is default if nd_period is not None: user_logger.trace("TRACE: ts before nd trigger" "{} for {}".format(time.time(), nd_period)) noisediode.trigger(session.kat, duration=nd_period, lead_time=nd_lead) user_logger.trace("TRACE: ts after nd trigger {}".format( time.time())) user_logger.debug("DEBUG: Starting {}s track on target: " "{} ({})".format(duration, time.time(), time.ctime(time.time()))) if session.track(target, duration=duration): target_visible = True user_logger.trace("TRACE: ts after {} {}".format(obs_type, time.time())) if (nd_setup is not None and nd_restore): # restore pattern if programmed at setup user_logger.info('Observation: Restoring ND pattern') noisediode.pattern( session.kat, nd_setup, lead_time=nd_lead, ) return target_visible
def run_observation(opts, kat): """Extract control and observation information provided in observation file.""" obs_plan_params = opts.obs_plan_params # remove observation specific instructions housed in YAML file del opts.obs_plan_params # set up duration periods for observation control obs_duration = -1 if "durations" in obs_plan_params: if "obs_duration" in obs_plan_params["durations"]: obs_duration = obs_plan_params["durations"]["obs_duration"] # check for nonsensical observation duration setting if abs(obs_duration) < 1e-5: user_logger.error( "Unexpected value: obs_duration: {}".format(obs_duration)) return # TODO: the description requirement in sessions should be re-evaluated # since the schedule block has the description # Description argument in instruction_set should be retired, but is # needed by sessions # Assign proposal_description if available, else create a dummy if "description" not in vars(opts): session_opts = vars(opts) description = "Observation run" if "proposal_description" in vars(opts): description = opts.proposal_description session_opts["description"] = description nr_obs_loops = len(obs_plan_params["observation_loop"]) with start_session(kat.array, **vars(opts)) as session: session.standard_setup(**vars(opts)) # Each observation loop contains a number of observation cycles over LST ranges # For a single observation loop, only a start LST and duration is required # Target observation loop observation_timer = time.time() for obs_cntr, observation_cycle in enumerate( obs_plan_params["observation_loop"]): if nr_obs_loops > 1: user_logger.info("Observation loop {} of {}.".format( obs_cntr + 1, nr_obs_loops)) user_logger.info("Loop LST range {}.".format( observation_cycle["LST"])) # Unpack all target information if not ("target_list" in observation_cycle.keys()): user_logger.error( "No targets provided - stopping script instead of hanging around" ) continue obs_targets = observation_cycle["target_list"] target_list = obs_targets["target"].tolist() # build katpoint catalogues for tidy handling of targets catalogue = collect_targets(kat.array, target_list) obs_tags = [] for tgt in obs_targets: # catalogue names are no longer unique name = tgt["name"] # add tag evaluation to identify catalogue targets tags = tgt["target"].split(",")[1].strip() for cat_tgt in catalogue: if name == cat_tgt.name: if ("special" in cat_tgt.tags or "xephem" in cat_tgt.tags or tags == " ".join(cat_tgt.tags)): tgt["target"] = cat_tgt obs_tags.extend(cat_tgt.tags) break obs_tags = list(set(obs_tags)) cal_tags = [tag for tag in obs_tags if tag[-3:] == "cal"] # observer object handle to track the observation timing in a more user # friendly way # observer = catalogue._antenna.observer ref_antenna = catalogue.antenna observer = ref_antenna.observer start_datetime = timestamp2datetime(time.time()) observer.date = ephem.Date(start_datetime) user_logger.trace("TRACE: requested start time " "({}) {}".format( datetime2timestamp(start_datetime), start_datetime)) user_logger.trace("TRACE: observer at start\n {}".format(observer)) # Only observe targets in valid LST range if nr_obs_loops > 1 and obs_cntr < nr_obs_loops - 1: [start_lst, end_lst] = get_lst(observation_cycle["LST"], multi_loop=True) if end_lst is None: # for multi loop the end lst is required raise RuntimeError( 'Multi-loop observations require end LST times') next_obs_plan = obs_plan_params["observation_loop"][obs_cntr + 1] [next_start_lst, next_end_lst] = get_lst(next_obs_plan["LST"]) user_logger.trace("TRACE: current LST range {}-{}".format( ephem.hours(str(start_lst)), ephem.hours(str(end_lst)))) user_logger.trace("TRACE: next LST range {}-{}".format( ephem.hours(str(next_start_lst)), ephem.hours(str(next_end_lst)))) else: next_start_lst = None next_end_lst = None [start_lst, end_lst] = get_lst(observation_cycle["LST"]) # Verify the observation is in a valid LST range # and that it is worth while continuing with the observation # Do not use float() values, ephem.hours does not convert as # expected local_lst = observer.sidereal_time() user_logger.trace("TRACE: Local LST {}".format( ephem.hours(local_lst))) # Only observe targets in current LST range log_msg = "Local LST outside LST range {}-{}".format( ephem.hours(str(start_lst)), ephem.hours(str(end_lst))) if float(start_lst) < end_lst: # lst ends before midnight if not _same_day(start_lst, end_lst, local_lst): if obs_cntr < nr_obs_loops - 1: user_logger.info(log_msg) else: user_logger.error(log_msg) continue else: # lst ends after midnight if _next_day(start_lst, end_lst, local_lst): if obs_cntr < nr_obs_loops - 1: user_logger.info(log_msg) else: user_logger.error(log_msg) continue # Verify that it is worth while continuing with the observation # The filter functions uses the current time as timestamps # and thus incorrectly set the simulation timestamp if not kat.array.dry_run: # Quit early if there are no sources to observe if len(catalogue.filter(el_limit_deg=opts.horizon)) == 0: raise NoTargetsUpError( "No targets are currently visible - " "please re-run the script later") # Quit early if the observation requires all targets to be visible if opts.all_up and (len( catalogue.filter(el_limit_deg=opts.horizon)) != len(catalogue)): raise NotAllTargetsUpError( "Not all targets are currently visible - please re-run the script" "with --visibility for information") # List sources and their associated functions from observation tags not_cals_filter_list = [] for cal_type in cal_tags: not_cals_filter_list.append("~{}".format(cal_type)) cal_array = [cal.name for cal in catalogue.filter(cal_type)] if len(cal_array) < 1: continue # do not display empty tags user_logger.info("{} calibrators are {}".format( str.upper(cal_type[:-3]), cal_array)) user_logger.info("Observation targets are [{}]".format(", ".join([ repr(target.name) for target in catalogue.filter(not_cals_filter_list) ]))) # TODO: setup of noise diode pattern should be moved to sessions # so it happens in the line above if "noise_diode" in obs_plan_params: nd_setup = obs_plan_params["noise_diode"] nd_lead = nd_setup.get('lead_time') # Set noise diode period to multiple of correlator integration time. if not kat.array.dry_run: cbf_corr = session.cbf.correlator dump_period = cbf_corr.sensor.int_time.get_value() else: dump_period = 0.5 # sec user_logger.debug( 'DEBUG: Correlator integration time {} [sec]'.format( dump_period)) if "cycle_len" in nd_setup: if (nd_setup['cycle_len'] >= dump_period): cycle_len_frac = nd_setup['cycle_len'] // dump_period nd_setup['cycle_len'] = cycle_len_frac * dump_period msg = ('Set noise diode period ' 'to multiple of correlator dump period: ' 'cycle length = {} [sec]'.format( nd_setup['cycle_len'])) else: msg = ('Requested cycle length {}s ' '< correlator dump period {}s, ' 'ND not synchronised with dump edge'.format( nd_setup['cycle_len'], dump_period)) user_logger.warning(msg) noisediode.pattern( kat.array, nd_setup, lead_time=nd_lead, ) # Adding explicit init after "Capture-init failed" exception was # encountered session.capture_init() user_logger.debug("DEBUG: Initialise capture start with timestamp " "{} ({})".format(int(time.time()), timestamp2datetime( time.time()))) # Go to first target before starting capture user_logger.info("Slewing to first target") observe(session, ref_antenna, obs_targets[0], slewonly=True) # Only start capturing once we are on target session.capture_start() user_logger.trace("TRACE: capture start time after slew " "({}) {}".format(time.time(), timestamp2datetime( time.time()))) user_logger.trace( "TRACE: observer after slew\n {}".format(observer)) done = False sanity_cntr = 0 while not done: # small errors can cause an infinite loop here # preventing infinite loops sanity_cntr += 1 if sanity_cntr > 100000: user_logger.error("While limit counter has reached {}, " "exiting".format(sanity_cntr)) break # Cycle through target list in order listed targets_visible = False time_remaining = obs_duration observation_timer = time.time() for tgt_cntr, target in enumerate(obs_targets): katpt_target = target["target"] user_logger.debug("DEBUG: {} {}".format(tgt_cntr, target)) user_logger.trace( "TRACE: initial observer for target\n {}".format( observer)) # check target visible before doing anything # make sure the target would be visible for the entire duration target_duration = target['duration'] visible = True if type(katpt_target.body) is ephem.FixedBody: visible = above_horizon( target=katpt_target.body.copy(), observer=observer.copy(), horizon=opts.horizon, duration=target_duration) if not visible: show_horizon_status = True # warning for cadence targets only when they are due if (target["cadence"] > 0 and target["last_observed"] is not None): delta_time = time.time() - target["last_observed"] show_horizon_status = delta_time >= target[ "cadence"] if show_horizon_status: user_logger.warn("Target {} below {} deg horizon, " "continuing".format( target["name"], opts.horizon)) continue user_logger.trace( "TRACE: observer after horizon check\n {}".format( observer)) # check and observe all targets with cadences while_cntr = 0 cadence_targets = list(obs_targets) while True: tgt = cadence_target(cadence_targets) if not tgt: break # check enough time remaining to continue if obs_duration > 0 and time_remaining < tgt[ "duration"]: done = True break # check target visible before doing anything user_logger.trace("TRACE: cadence" "target\n{}\n {}".format( tgt, catalogue[tgt["name"]])) user_logger.trace("TRACE: initial observer for cadence" "target\n {}".format(observer)) user_logger.trace( "TRACE: observer before track\n {}".format( observer)) user_logger.trace( "TRACE: target observation # {} last observed " "{}".format(tgt["obs_cntr"], tgt["last_observed"])) cat_target = catalogue[tgt["name"]] if above_horizon( target=cat_target.body, observer=cat_target.antenna.observer.copy(), horizon=opts.horizon, duration=tgt["duration"]): if observe(session, ref_antenna, tgt, **obs_plan_params): targets_visible += True tgt["obs_cntr"] += 1 tgt["last_observed"] = time.time() else: # target not visibile to sessions anymore cadence_targets.remove(tgt) user_logger.trace( "TRACE: observer after track\n {}".format( observer)) user_logger.trace( "TRACE: target observation # {} last observed " "{}".format(tgt["obs_cntr"], tgt["last_observed"])) else: cadence_targets.remove(tgt) while_cntr += 1 if while_cntr > len(obs_targets): break if done: break user_logger.trace( "TRACE: observer after cadence\n {}".format(observer)) # observe non cadence target if target["cadence"] < 0: user_logger.trace( "TRACE: normal target\n {}".format(target)) user_logger.trace( "TRACE: observer before track\n {}".format( observer)) user_logger.trace("TRACE: ts before observe {}".format( time.time())) user_logger.trace("TRACE: target last " "observed {}".format( target["last_observed"])) targets_visible += observe(session, ref_antenna, target, **obs_plan_params) user_logger.trace( "TRACE: observer after track\n {}".format( observer)) user_logger.trace("TRACE: ts after observe {}".format( time.time())) if targets_visible: target["obs_cntr"] += 1 target["last_observed"] = time.time() user_logger.trace( "TRACE: target observation # {} last observed " "{}".format(target["obs_cntr"], target["last_observed"])) user_logger.trace( "TRACE: observer after track\n {}".format( observer)) # loop continuation checks delta_time = time.time() - session.start_time user_logger.trace( "TRACE: time elapsed {} sec".format(delta_time)) user_logger.trace( "TRACE: total obs duration {} sec".format( obs_duration)) if obs_duration > 0: time_remaining = obs_duration - delta_time user_logger.trace( "TRACE: time remaining {} sec".format( time_remaining)) next_target = obs_targets[(tgt_cntr + 1) % len(obs_targets)] user_logger.trace("TRACE: next target before cadence " "check:\n{}".format(next_target)) # check if there is a cadence target that must be run # instead of next target for next_cadence_tgt_idx in range( tgt_cntr + 1, len(obs_targets)): next_cadence_target = obs_targets[ next_cadence_tgt_idx % len(obs_targets)] if next_cadence_target["cadence"] > 0: user_logger.trace( "TRACE: time needed for next obs " "{} sec".format( next_cadence_target["cadence"])) next_target = obs_targets[next_cadence_tgt_idx % len(obs_targets)] continue user_logger.trace("TRACE: next target after cadence " "check:\n{}".format(next_target)) user_logger.trace("TRACE: time needed for next obs " "{} sec".format( next_target["duration"])) if (time_remaining < 1.0 or time_remaining < next_target["duration"]): user_logger.info( "Scheduled observation time lapsed - ending observation" ) done = True break # during dry-run when sessions exit time is reset so will be incorrect # outside the loop observation_timer = time.time() if obs_duration < 0: user_logger.info( "Observation list completed - ending observation") done = True # for multiple loop, check start lst of next loop if next_start_lst is not None: check_local_lst = observer.sidereal_time() if (check_local_lst > next_start_lst) or (not _next_day( next_start_lst, next_end_lst, check_local_lst)): user_logger.info("Moving to next LST loop") done = True # End if there is nothing to do if not targets_visible: user_logger.warning( "No more targets to observe - stopping script " "instead of hanging around") done = True user_logger.trace("TRACE: observer at end\n {}".format(observer)) # display observation cycle statistics # currently only available for single LST range observations if nr_obs_loops < 2: print user_logger.info("Observation loop statistics") total_obs_time = observation_timer - session.start_time if obs_duration < 0: user_logger.info("Single run through observation target list") else: user_logger.info("Desired observation time {:.2f} sec " "({:.2f} min)".format(obs_duration, obs_duration / 60.0)) user_logger.info("Total observation time {:.2f} sec " "({:.2f} min)".format(total_obs_time, total_obs_time / 60.0)) if len(obs_targets) > 0: user_logger.info("Targets observed :") for unique_target in np.unique(obs_targets["name"]): cntrs = obs_targets[obs_targets["name"] == unique_target]["obs_cntr"] durations = obs_targets[obs_targets["name"] == unique_target]["duration"] if np.isnan(durations).any(): user_logger.info("{} observed {} times".format( unique_target, np.sum(cntrs))) else: user_logger.info("{} observed for {} sec".format( unique_target, np.sum(cntrs * durations))) print
def _set_dig_nd_(kat, timestamp, nd_setup=None, switch=0, cycle=False): """Setting and implementing digitiser noise diode command Parameters ---------- kat : session kat container-like object Container for accessing KATCP resources allocated to schedule block. timestamp : float Linux timestamp in seconds at which to switch noise diode nd_setup : dict, optional (default = None, no pattern set) Noise diode pattern setup, with keys: 'antennas': options are 'all', or 'm062', or ...., 'cycle_len': the cycle length [sec], - must be less than 20 sec for L-band, etc., etc. switch : 0 or 1, optional (default = 0) Switch all noise diodes off (0) or on (1) Returns ------- timestamp : float Linux timestamp reported by digitiser """ if nd_setup is not None: # selected antennas for nd pattern nd_antennas = sorted(nd_setup['antennas'].split(",")) # nd pattern length [sec] cycle_length = nd_setup['cycle_len'] # on fraction of pattern length [%] on_fraction = nd_setup['on_frac'] msg = ('Repeat noise diode pattern every {} sec, ' 'with {} sec on and apply pattern to {}' .format(cycle_length, float(cycle_length) * float(on_fraction), nd_antennas)) user_logger.info(msg) else: nd_antennas = sorted(ant.name for ant in kat.ants) cycle_length = 1. on_fraction = switch # Noise diodes trigger is evaluated per antenna timestamps = [] for ant in nd_antennas: ped = getattr(kat, ant) reply = ped.req.dig_noise_source(timestamp, on_fraction, cycle_length) if not kat.dry_run: timestamps.append(_katcp_reply_({ant: reply})) else: msg = ('Dry-run: Set noise diode for antenna {} at ' 'timestamp {}'.format(ant, timestamp)) user_logger.debug(msg) if cycle: # add time [sec] to ensure all digitisers set at the same time timestamp += cycle_length * on_fraction # assuming ND for all antennas must be the same # only display single timestamp if not kat.dry_run: # test incorrect reply check if len(timestamps) < len(nd_antennas): err_msg = 'Noise diode activation not in sync' user_logger.error(err_msg) timestamp = np.mean(timestamps) msg = ('Set all noise diodes with timestamp {} ({})' .format(int(timestamp), time.ctime(timestamp))) user_logger.debug('DEBUG: {}'.format(msg)) return timestamp
def trigger(kat, duration=None, lead_time=_DEFAULT_LEAD_TIME): """Fire the noise diode before track. Parameters ---------- kat : session kat container-like object Container for accessing KATCP resources allocated to schedule block. duration : float, optional (default = None) Duration that the noisediode will be active [sec] lead_time : float, optional (default = system default lead time) Lead time before the noisediode is switched on [sec] """ if duration is None: return True # nothing to do msg = ('Firing noise diode for {}s before target observation' .format(duration)) user_logger.info(msg) user_logger.info('Add lead time of {}s' .format(lead_time)) user_logger.debug('DEBUG: issue command to switch ND on @ {}' .format(time.time())) if duration > lead_time: user_logger.trace('TRACE: Trigger duration > lead_time') # allow lead time for all to switch on simultaneously # timestamp on = now + lead on_time = on(kat, lead_time=lead_time) user_logger.debug('DEBUG: on {} ({})' .format(on_time, time.ctime(on_time))) user_logger.debug('DEBUG: fire nd for {}' .format(duration)) sleeptime = min(duration - lead_time, lead_time) user_logger.trace('TRACE: sleep {}' .format(sleeptime)) off_time = on_time + duration user_logger.trace('TRACE: desired off_time {} ({})' .format(off_time, time.ctime(off_time))) user_logger.trace('TRACE: delta {}' .format(off_time - on_time)) user_logger.debug('DEBUG: sleeping for {} [sec]' .format(sleeptime)) time.sleep(sleeptime) user_logger.trace('TRACE: ts after sleep {} ({})' .format(time.time(), time.ctime(time.time()))) else: user_logger.trace('TRACE: Trigger duration <= lead_time') cycle_len = _get_max_cycle_len(kat) nd_setup = {'antennas': 'all', 'cycle_len': cycle_len, 'on_frac': float(duration) / cycle_len, } user_logger.debug('DEBUG: fire nd for {} using pattern' .format(duration)) on_time = pattern(kat, nd_setup, lead_time=lead_time) user_logger.debug('DEBUG: pattern set {} ({})' .format(on_time, time.ctime(on_time))) off_time = _get_nd_timestamp_(lead_time) user_logger.trace('TRACE: desired off_time {} ({})' .format(off_time, time.ctime(off_time))) user_logger.debug('DEBUG: off {} ({})' .format(off_time, time.ctime(off_time))) off_time = off(kat, timestamp=off_time) sleeptime = off_time - time.time() user_logger.debug('DEBUG: now {}, sleep {}' .format(time.time(), sleeptime)) time.sleep(sleeptime) # default sleep to see for signal to get through user_logger.debug('DEBUG: now {}, slept {}' .format(time.time(), sleeptime))
def _get_scan_area_extents(target_list, antenna_, obs_start_ts, offset_deg=0.1): """Return the elevation, azimuth extents, and time extents of the scan.""" antenna = copy.copy(antenna_) antenna.observer.date = obs_start_ts is_rising_list = [] el_list = [] az_list = [] time_list = [] for target in target_list: _, el = target.azel(timestamp=obs_start_ts) el_list.append(el) if el < 0: user_logger.warning( target.name + "is below the horizon (%s deg at %s)", el, obs_start_ts) user_logger.debug('Min elevation of scan area at start: %s', min(el_list)) user_logger.debug('Max elevation of scan area at start: %s', max(el_list)) el_just_below_lowest = min(el_list) - np.radians(_HORIZON_ANGLE_OFFSET_DEG) antenna.observer.horizon = el_just_below_lowest for target in target_list: next_transit = antenna.observer.next_transit(target.body) next_setting = antenna.observer.next_setting(target.body) next_rising = antenna.observer.next_rising(target.body) is_rising = next_transit < next_setting < next_rising is_rising_list.append(is_rising) user_logger.debug("Is Target rising :%s, %s" % (target.body, is_rising)) if all(is_rising_list): # Rising sources antenna.observer.horizon = max(el_list) + np.radians( offset_deg) # top point user_logger.debug('Highest elevation for rising source: %s', max(el_list)) for target in target_list: antenna.observer.next_rising( target.body) # rise through the scan line az_list.append(target.body.rise_az) time_list.append(target.body.rise_time) else: antenna.observer.horizon = min(el_list) - np.radians( offset_deg) # bottom point user_logger.debug('Lowest elevation for setting source: %s', min(el_list)) for target in target_list: antenna.observer.next_setting( target.body) # Set through the scan line az_list.append(target.body.set_az) time_list.append(target.body.set_time) min_time = katpoint.Timestamp(min(time_list)) max_time = katpoint.Timestamp(max(time_list)) user_logger.debug( "Scan Area Points: el: %s, az: %s %s, time: %s %s", antenna.observer.horizon, min(az_list), max(az_list), min_time, max_time, ) return ( antenna.observer.horizon, min(az_list), max(az_list), min_time, max_time, )
def _set_dig_nd_(kat, timestamp, nd_setup=None, switch=0, cycle=False): """Setting and implementing digitiser noise diode command Parameters ---------- kat : session kat container-like object Container for accessing KATCP resources allocated to schedule block. timestamp : float Linux timestamp in seconds at which to switch noise diode nd_setup : dict, optional (default = None, no pattern set) Noise diode pattern setup, with keys: 'antennas': options are 'all', or 'm062', or ...., 'cycle_len': the cycle length [sec], - must be less than 20 sec for L-band, etc., etc. switch : 0 or 1, optional (default = 0) Switch all noise diodes off (0) or on (1) Returns ------- timestamp : float Linux timestamp reported by digitiser """ if nd_setup is not None: # selected antennas for nd pattern nd_antennas = sorted(nd_setup['antennas'].split(",")) # nd pattern length [sec] cycle_length = nd_setup['cycle_len'] # on fraction of pattern length [%] on_fraction = nd_setup['on_frac'] msg = ('Repeat noise diode pattern every {} sec, ' 'with {} sec on and apply pattern to {}'.format( cycle_length, float(cycle_length) * float(on_fraction), nd_antennas)) user_logger.info(msg) else: nd_antennas = sorted(ant.name for ant in kat.ants) cycle_length = 1. on_fraction = switch # Noise diodes trigger is evaluated per antenna replies = {} for ant in nd_antennas: ped = getattr(kat, ant) # The digitiser master controller takes about 15-50 ms per request, # so start panicking just before the deadline. if time.time() > timestamp - 0.02: user_logger.error( 'Requested noise diode timestamp %sZ will probably ' 'be in the past - please increase lead time', katpoint.Timestamp(timestamp)) skipped = ','.join(nd_antennas[len(replies):]) user_logger.error('Skipped setting these noise diodes: %s', skipped) break replies[ant] = ped.req.dig_noise_source(timestamp, on_fraction, cycle_length) if kat.dry_run: msg = ('Dry-run: Set noise diode for antenna {} at ' 'timestamp {}'.format(ant, timestamp)) user_logger.debug(msg) if cycle: # add time [sec] to ensure all digitisers set at the same time timestamp += cycle_length * on_fraction # assuming ND for all antennas must be the same # only display single timestamp if not kat.dry_run: timestamp = _katcp_reply_(replies) # test incorrect reply check if len(replies) < len(nd_antennas): user_logger.error('Noise diode activation not in sync') if np.isfinite(timestamp): msg = ( 'Set successful noise diodes with average timestamp {:.0f} ({}Z)'. format(timestamp, katpoint.Timestamp(timestamp))) user_logger.debug('DEBUG: {}'.format(msg)) return timestamp
def track(ant, target, ridx_position='l', duration=10, dry_run=False): # TODO: make the indexer timeout configurable parameter indexer_timeout = 120 ant.req.mode('STOP') if not dry_run: # Added sleep to wait for AP brakes to engage time.sleep(2) result = wait_until_sensor_equals(5.0, ant.name + '_mode', 'STOP') if result[0] == False: user_logger.error("Failed to set AP to 'STOP' mode. " "Indexer commands will not be processed.") return user_logger.info("Setting initial RI on %s to position : %s " % (ant.name, ridx_position.upper())) ant.req.ap_set_indexer_position(ridx_position) if not dry_run: result = wait_until_sensor_equals(indexer_timeout, ant.name + '_ap_indexer_position', ridx_position) else: result = (True, ridx_position) if result[0] == False: ridx_position_raw = kat.sensors.get( ant.name + '_ap_indexer_position_raw', None).get_value() user_logger.error( "Timed out while waiting %s seconds for " "indexer to reach '%s' position. " "Last position reading was %s degrees." % (indexer_timeout, ridx_position.upper(), ridx_position_raw)) # TODO: make this sequence easier to configure ridx_sequence = [ 'u', 'l', 'x', 's', 'l', 'u', 's', 'x', 'u', 'l', 's', 'x', 'l', 'u', 'x', 's', 'u', 'x', 'l', 's', 'l', 's' ] # Cycling indexer positions if not dry_run: for pos in ridx_sequence: ridx_last_position = kat.sensors.get( ant.name + '_ap_indexer_position', None).get_value() ridx_movement_start_time = time.time() user_logger.info("--- Moving RI to position: '%s' ---" % pos.upper()) ant.req.ap_set_indexer_position(pos) result = wait_until_sensor_equals( indexer_timeout, ant.name + '_ap_indexer_position', pos) user_logger.debug("Request result: '%s', " "last sensor reading: '%s'" % (result[0], result[1])) if result[0] == False: ridx_position_raw = kat.sensors.get( ant.name + '_ap_indexer_position_raw', None).get_value() ridx_brakes_released = kat.sensors.get( ant.name + '_ap_ridx_brakes_released', None).get_value() user_logger.error("Timed out while waiting %s seconds " "for indexer to reach '%s' position. " "Last position reading was %s degrees. " "Brakes released: '%s'. " % (indexer_timeout, pos.upper(), ridx_position_raw, ridx_brakes_released)) ridx_current_position = kat.sensors.get( ant.name + '_ap_indexer_position', None).get_value() time_to_index = time.time() - ridx_movement_start_time if ridx_current_position in ['undefined']: user_logger.warning("Movement ended in undefined position") else: user_logger.info( "RIDX from '%s' to '%s' position " "took '%s' seconds." % (ridx_last_position.upper(), ridx_current_position.upper(), time_to_index)) # 60 seconds comes from the antenna specification if (time_to_index > 60.0): user_logger.warning("Indexer took longer than 60 seconds!") user_logger.info("Dwell for %s seconds before " "going to next position." % duration) time.sleep(duration) user_logger.info("Pattern complete. Heading to next sky target.")
def observe(session, target_info, **kwargs): """Target observation functionality. Parameters ---------- session: `CaptureSession` target_info: """ target_visible = False target_name = target_info["name"] target = target_info["target"] duration = target_info["duration"] obs_type = target_info["obs_type"] # simple way to get telescope to slew to target if "slewonly" in kwargs: return session.track(target, duration=0.0, announce=False) # set noise diode behaviour nd_setup = None nd_lead = _DEFAULT_LEAD_TIME if kwargs.get("noise_diode"): nd_setup = kwargs["noise_diode"] # user specified lead time if "lead_time" in nd_setup: nd_lead = nd_setup['lead_time'] # not a ND pattern if "cycle_len" not in nd_setup: nd_setup = None # implement target specific noise diode behaviour nd_period = None nd_restore = False if target_info["noise_diode"] is not None: if "off" in target_info["noise_diode"]: user_logger.info('Observation: No ND for target') nd_restore = True # disable noise diode pattern for target noisediode.off(session.kat, lead_time=nd_lead) else: nd_period = float(target_info["noise_diode"]) msg = "Initialising {} {} {}".format(obs_type.capitalize(), ", ".join(target.tags[1:]), target_name) if not np.isnan(duration): # scan types do not have durations msg += " for {} sec".format(duration) if np.isnan(duration) or duration > 1: user_logger.info(msg) # do the different observations depending on requested type session.label(obs_type.strip()) user_logger.trace("TRACE: performing {} observation on {}".format( obs_type, target)) if "scan" in obs_type: # compensating for ' and spaces around key values if "drift_scan" in obs_type: scan_func = scans.drift_scan # TODO: fix raster scan and remove this scan hack elif "forwardscan" in obs_type: scan_func = scans.forwardscan obs_type = "scan" elif "reversescan" in obs_type: scan_func = scans.reversescan obs_type = "scan" elif "return_scan" in obs_type: scan_func = scans.return_scan obs_type = "scan" elif "raster_scan" in obs_type: scan_func = scans.raster_scan else: scan_func = scans.scan if obs_type in kwargs: # user settings other than defaults target_visible = scan_func(session, target, nd_period=nd_period, **kwargs[obs_type]) else: target_visible = scan_func(session, target, nd_period=nd_period) else: # track is default if nd_period is not None: user_logger.trace("TRACE: ts before nd trigger" "{} for {}".format(time.time(), nd_period)) noisediode.trigger(session.kat, duration=nd_period, lead_time=nd_lead) user_logger.trace("TRACE: ts after nd trigger {}".format( time.time())) user_logger.debug("DEBUG: Starting {}s track on target: " "{} ({})".format(duration, time.time(), time.ctime(time.time()))) if session.track(target, duration=duration): target_visible = True user_logger.trace("TRACE: ts after {} {}".format(obs_type, time.time())) if (nd_setup is not None and nd_restore): # restore pattern if programmed at setup user_logger.info('Observation: Restoring ND pattern') noisediode.pattern( session.kat, nd_setup, lead_time=nd_lead, ) return target_visible