def above_horizon(target, observer, horizon=20.0, duration=0.0): """Check target visibility. Utility function to calculate ephem horizontal coordinates """ # use local copies so you do not overwrite target time attribute horizon = ephem.degrees(str(horizon)) # must be celestial target (ra, dec) # check that target is visible at start of track start_ = timestamp2datetime(time.time()) [azim, elev] = _horizontal_coordinates(target, observer, start_) user_logger.trace("TRACE: target at start (az, el)= ({}, {})".format( azim, elev)) if not elev > horizon: return False # check that target will be visible at end of track if duration: end_ = timestamp2datetime(time.time() + duration) [azim, elev] = _horizontal_coordinates(target, observer, end_) user_logger.trace("TRACE: target at end (az, el)= ({}, {})".format( azim, elev)) return elev > horizon return True
def above_horizon(target, observer, horizon=20.0, duration=0.0): """Check target visibility.""" # use local copies so you do not overwrite target time attribute horizon = ephem.degrees(str(horizon)) if type(target) is not ephem.FixedBody: # anticipate katpoint special target for AzEl targets if 'alt' not in vars(target): raise RuntimeError('Unknown target type, exiting...') # 'StationaryBody' objects do not have RaDec coordinates # check pointing altitude is above minimum elevation limit return bool(target.alt >= horizon) # must be celestial target (ra, dec) # check that target is visible at start of track start_ = timestamp2datetime(time.time()) [azim, elev] = __horizontal_coordinates__(target, observer, start_) user_logger.trace("TRACE: target at start (az, el)= ({}, {})".format( azim, elev)) if not elev > horizon: return False # check that target will be visible at end of track if duration: end_ = timestamp2datetime(time.time() + duration) [azim, elev] = __horizontal_coordinates__(target, observer, end_) user_logger.trace("TRACE: target at end (az, el)= ({}, {})".format( azim, elev)) return elev > horizon return True
def test_time_conversion_symmetry(self): """Test katpoint and astrokat time conversion methods match and are symmetrical""" test_files = [ "test_obs/below-horizon-sim.yaml", "test_obs/image-cals-sim.yaml", "test_obs/image-sim.yaml", "test_obs/image-single-sim.yaml", "test_obs/targets-sim.yaml", "test_obs/two-calib-sim.yaml", ] for test_file in test_files: file_path = yaml_path(test_file) yaml_start_time = extract_start_time(file_path) yaml_start_time_str = str(yaml_start_time) astrokat_sec_since_epoch = utility.datetime2timestamp( yaml_start_time) katpoint_sec_since_epoch = katpoint.Timestamp( yaml_start_time_str).secs self.assertAlmostEqual( astrokat_sec_since_epoch, katpoint_sec_since_epoch, places=6, msg="timestamp conversion mismatch {}".format(test_file)) astrokat_datetime = utility.timestamp2datetime( astrokat_sec_since_epoch) katpoint_timestamp = katpoint.Timestamp(katpoint_sec_since_epoch) self.assertEqual( str(astrokat_datetime), yaml_start_time_str, msg="astrokat str time conversion mismatch for {}".format( test_file)) self.assertEqual( str(katpoint_timestamp), yaml_start_time_str, msg="katpoint str time conversion mismatch for {}".format( test_file))
def main(args): """Run the observation. Run the observation script read arguments from yaml file """ (opts, args) = astrokat.cli( os.path.basename(__file__), # remove redundant KAT-7 options long_opts_to_remove=[ "--mode", "--dbe-centre-freq", "--no-mask", "--horizon", "--centre-freq", ], args=args, ) # suppress the sessions noise diode, which is outdated # will use it again once functionality corrected # TODO: Currently the fire_noise_diode function in mkat_session.py is # outdated. This has to be updated to reflect the new noise diode pattern # implementation, and this default setting then removed. opts.nd_params = "off" # astrokat does not support observational command line parameters # all observational parameters must be in the YAML file # command line arguments will be dropped to unknown arguments unknown_args = [arg for arg in args if arg.startswith("--")] if any("horizon" in arg for arg in unknown_args): raise RuntimeError( "Command line option {} not supported. " "Please specify parameters in YAML file.".format("horizon")) # TODO: add correlator settings YAML option for config file # unpack observation from observation plan if opts.yaml: opts.obs_plan_params = read_yaml(opts.yaml) # ensure sessions has the YAML horizon value if given if "horizon" in opts.obs_plan_params: opts.horizon = opts.obs_plan_params["horizon"] else: opts.horizon = 20.0 # deg above horizon default # set log level if opts.debug: user_logger.setLevel(logging.DEBUG) if opts.trace: user_logger.setLevel(logging.TRACE) # process the flat list of targets into a structure with sources # convert celestial targets coordinates to all be equatorial (ra,dec) # horizontal coordinates (alt, az) # for scans, the coordinates will be converted to enable delay tracking # for tracks the coordinates will be left as is with no delay tracking # planetary bodies are passed through to katpoint Target as is # elliptical solar bodies such as comets are also passed through as katpoint Targets for obs_dict in opts.obs_plan_params['observation_loop']: start_ts = timestamp2datetime(time.time()) if "durations" in opts.obs_plan_params: obs_time_info = opts.obs_plan_params["durations"] if "start_time" in obs_time_info: start_ts = obs_time_info["start_time"] mkat = astrokat.Observatory(datetime=start_ts) obs_targets = targets.read(obs_dict["target_list"], observer=mkat.observer) obs_dict['target_list'] = obs_targets # setup and observation with Telescope(opts) as kat: run_observation(opts, kat)
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 source_elevation(catalogue, ref_antenna): """ Generates a plot of elevation over time for 24 hour period for all sources in provided catalogue at a specific location @param catalogue: katpoint.Catalogue object @param ref_antenna: katpoint.Antenna object @return: matplotlib figure handle """ catalogue.antenna = ref_antenna horizon = numpy.degrees(ref_antenna.observer.horizon) # All times and timestamps assumed UTC, no special conversion to # accommodate SAST allowed to prevent confusion creation_date = catalogue.antenna.observer.date creation_timestamp = datetime2timestamp(creation_date.datetime()) time_range = creation_timestamp + numpy.arange(0, 24. * 60. * 60., 360.) timestamps = [timestamp2datetime(ts) for ts in time_range] fig = plt.figure(figsize=(15, 7), facecolor='white') ax = plt.subplot(111) plt.subplots_adjust(right=0.8) fontP = FontProperties() fontP.set_size('small') for cnt, target in enumerate(catalogue.targets): elev = [] for idx, timestamp in enumerate(timestamps): catalogue.antenna.observer.date = ephem.Date(timestamp) target.body.compute(catalogue.antenna.observer) elev.append(numpy.degrees(target.body.alt)) label = '{} '.format(target.name) target.tags.remove('radec') target.tags.remove('target') if 'target' in target.tags else None label += ', '.join(target.tags) myplot, = plt.plot_date(timestamps, elev, fmt='.', linewidth=0, label=label) ax.axhspan(15, horizon, facecolor='k', alpha=0.1) plt.grid() plt.legend(loc='center left', bbox_to_anchor=(1, 0.5), prop={'size': 10}, numpoints=1) plt.ylabel('Elevation (deg)') plt.ylim(15, 90) plt.yticks(fontsize=10) # fix tick positions for proper time axis display utc_hrs = [timestamps[0] + timedelta(hours=hr) for hr in range(0, 25, 1)] box = ax.get_position() ax.set_position([box.x0, box.y0, box.width * 0.9, box.height]) ax.set_xlim(utc_hrs[0], utc_hrs[-1]) ax.xaxis.set_major_locator(mdates.HourLocator(byhour=range(24), interval=1)) locs = ax.get_xticks() locs_labels = matplotlib.dates.num2date(locs) locator = matplotlib.ticker.FixedLocator(locs) ax.xaxis.set_major_locator(locator) ax.margins(x=0) utc_timestamps = [locs_lbl.strftime('%H:%M') for locs_lbl in locs_labels] lst_timestamps = [] for locs_ts in locs_labels: catalogue.antenna.observer.date = ephem.Date(locs_ts) lst_time = '{}'.format(catalogue.antenna.observer.sidereal_time()) lst_time_str = datetime.strptime(lst_time, '%H:%M:%S.%f').strftime('%H:%M') lst_timestamps.append(lst_time_str) ax.set_xticklabels(lst_timestamps, rotation=30, fontsize=10) ax.set_xlabel('Local Sidereal Time') ax2 = ax.twiny() box = ax2.get_position() ax2.set_position([box.x0, box.y0, box.width * 0.9, box.height]) ax2.set_xlim(ax.get_xlim()) ax2.set_xticks(ax.get_xticks()) ax2.margins(x=0) ax2.xaxis.set_major_locator(locator) ax2.set_xticklabels(utc_timestamps, rotation=30, fontsize=10) ax2.set_xlabel('Time (UTC) starting from {}'.format( datetime.utcfromtimestamp(creation_timestamp).strftime( '%Y-%m-%d %H:%M:%S'))) imfile = 'elevation_utc_lst.png' print('Elevation plot {}'.format(imfile)) plt.savefig(imfile, dpi=300) return fig
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'] # 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 for observation_cycle in obs_plan_params['observation_loop']: # 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 = read_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) for tgt in obs_targets: tgt['target'] = catalogue[tgt['name']] # observer object handle to track the observation timing in a more user friendly way observer = catalogue._antenna.observer # Only observe targets in valid LST range [start_lst, end_lst] = get_lst(observation_cycle['LST']) # 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' ) user_logger.info('Imaging targets are [{}]'.format(', '.join([ repr(target.name) for target in catalogue.filter(['~bpcal', '~gaincal']) ]))) user_logger.info("Bandpass calibrators are [{}]".format(', '.join( [repr(bpcal.name) for bpcal in catalogue.filter('bpcal')]))) user_logger.info("Gain calibrators are [{}]".format(', '.join( [repr(gaincal.name) for gaincal in catalogue.filter('gaincal')]))) # 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 # Target observation loop with start_session(kat.array, **vars(opts)) as session: session.standard_setup(**vars(opts)) 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)) # 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() # Only observe targets in current LST range if start_lst < end_lst: in_range = ( (ephem.hours(local_lst) >= ephem.hours(str(start_lst))) and (ephem.hours(local_lst) < ephem.hours(str(end_lst)))) if not in_range: user_logger.error( 'Local LST outside LST range {}-{}'.format( ephem.hours(str(start_lst)), ephem.hours(str(end_lst)))) continue else: # else assume rollover at midnight to next day out_range = ( (ephem.hours(local_lst) < ephem.hours(str(start_lst))) and (ephem.hours(local_lst) > ephem.hours(str(end_lst)))) if out_range: user_logger.error( 'Local LST outside LST range {}-{}'.format( ephem.hours(str(start_lst)), ephem.hours(str(end_lst)))) continue # 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.keys(): noisediode.pattern(kat.array, session, obs_plan_params['noise_diode']) # 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, 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 for cnt, target in enumerate(obs_targets): katpt_target = target['target'] user_logger.debug('DEBUG: {} {}'.format(cnt, target)) user_logger.trace( 'TRACE: initial observer for target\n {}'.format( observer)) # check target visible before doing anything if not above_horizon(katpt_target, horizon=opts.horizon): 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'])) if above_horizon(catalogue[tgt['name']], horizon=opts.horizon): if observe(session, 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, 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[(cnt + 1) % len(obs_targets)] for next_tgt_idx in range(cnt + 1, len(obs_targets)): next_target = obs_targets[next_tgt_idx % len(obs_targets)] user_logger.trace( 'TRACE: time needed for next obs {} sec'. format(next_target['cadence'])) if next_target['cadence'] > 0: continue user_logger.trace( 'TRACE: time needed for next obs {} sec'. format(next_target['duration'])) if time_remaining < 1. or \ time_remaining < next_target['duration']: user_logger.info( 'Scheduled observation time lapsed - ending observation' ) done = True break if obs_duration < 0: user_logger.info( 'Observation list completed - ending observation') 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 print user_logger.info("Observation loop statistics") total_obs_time = time.time() - 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.)) user_logger.info( "Total observation time {:.2f} sec ({:.2f} min)".format( total_obs_time, total_obs_time / 60.)) 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 source_elevation(catalogue, ref_antenna): """Generate a plot of elevation over time for 24 hour period. For all sources in provided catalogue at a specific location Parameters ---------- catalogue: katpoint.Catalogue ref_antenna: katpoint.Antenna A MeerKAT reference antenna Returns ------- matplotlib figure handle """ catalogue.antenna = ref_antenna horizon = numpy.degrees(ref_antenna.observer.horizon) # All times and timestamps assumed UTC, no special conversion to # accommodate SAST allowed to prevent confusion creation_date = catalogue.antenna.observer.date creation_timestamp = datetime2timestamp(creation_date.datetime()) time_range = creation_timestamp + numpy.arange(0, 24.0 * 60.0 * 60.0, 360.0) timestamps = [timestamp2datetime(ts) for ts in time_range] fig = plt.figure(figsize=(15, 7), facecolor="white") ax = plt.subplot(111) plt.subplots_adjust(right=0.8) fontP = FontProperties() fontP.set_size("small") for cnt, target in enumerate(catalogue.targets): elev = [] for idx, timestamp in enumerate(timestamps): catalogue.antenna.observer.date = ephem.Date(timestamp) target.body.compute(catalogue.antenna.observer) elev.append(numpy.degrees(target.body.alt)) label = "{} ".format(target.name) target.tags.remove("radec") if "target" in target.tags: target.tags.remove("target") label += ", ".join(target.tags) myplot, = plt.plot_date(timestamps, elev, fmt='.', linewidth=0, label=label) ax.axhspan(15, horizon, facecolor="k", alpha=0.1) plt.grid() plt.legend(loc='center left', bbox_to_anchor=(1, 0.5), prop={'size': 10}, numpoints=1) plt.ylabel("Elevation (deg)") plt.ylim(15, 90) plt.yticks(fontsize=10) # fix tick positions for proper time axis display utc_hrs = [timestamps[0] + timedelta(hours=hr) for hr in range(0, 25, 1)] box = ax.get_position() ax.set_position([box.x0, box.y0, box.width * 0.9, box.height]) ax.set_xlim(utc_hrs[0], utc_hrs[-1]) ax.xaxis.set_major_locator(mdates.HourLocator(byhour=range(24), interval=1)) locs = ax.get_xticks() locs_labels = matplotlib.dates.num2date(locs) locator = matplotlib.ticker.FixedLocator(locs) ax.xaxis.set_major_locator(locator) utc_timestamps = [locs_lbl.strftime("%H:%M") for locs_lbl in locs_labels] lst_timestamps = [] for locs_ts in locs_labels: catalogue.antenna.observer.date = ephem.Date(locs_ts) lst_time = "{}".format(catalogue.antenna.observer.sidereal_time()) lst_time_str = datetime.strptime(lst_time, "%H:%M:%S.%f").strftime("%H:%M") lst_timestamps.append(lst_time_str) ax.set_xticklabels(lst_timestamps, rotation=30, fontsize=10) ax.set_xlabel("Local Sidereal Time") ax2 = ax.twiny() box = ax2.get_position() ax2.set_position([box.x0, box.y0, box.width * 0.9, box.height]) ax2.set_xlim(ax.get_xlim()) ax2.set_xticks(ax.get_xticks()) ax2.xaxis.set_major_locator(locator) ax2.set_xticklabels(utc_timestamps, rotation=30, fontsize=10) ax2.set_xlabel('Time (UTC) starting from {}'.format( datetime.utcfromtimestamp(creation_timestamp).strftime( '%Y-%m-%d %H:%M:%S'))) return fig