예제 #1
0
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
예제 #2
0
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
예제 #3
0
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)))
예제 #4
0
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)
예제 #5
0
파일: targets.py 프로젝트: ska-sa/astrokat
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
예제 #6
0
파일: targets.py 프로젝트: ska-sa/astrokat
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
예제 #7
0
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
예제 #8
0
파일: targets.py 프로젝트: ska-sa/astrokat
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
예제 #9
0
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)
예제 #10
0
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
예제 #11
0
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
예제 #12
0
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
예제 #13
0
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
예제 #14
0
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))
예제 #15
0
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,
    )
예제 #16
0
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
예제 #17
0
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.")
예제 #18
0
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