Пример #1
0
    def _move_to(self, position):
        if self._moving:
            self._move_event.set()
            msg = "Attempt to move filter wheel when already moving"
            self.logger.error(msg)
            raise RuntimeError(msg)

        move_distance = position - self.position
        if self.is_unidirectional:
            # Filter wheel can only move one way, will have to go the long way around for -ve moves
            move_distance = move_distance % self._n_positions
        else:
            # Filter wheel can move either direction, just need magnitude of the move.
            move_distance = abs(move_distance)
        move_duration = move_distance * self._move_time

        move = threading.Timer(interval=move_duration,
                               function=self._complete_move,
                               args=(position, ))
        self._position = float('nan')
        self._moving = True
        move.start()

        if move_duration > self._timeout:
            move.join(timeout=self._timeout)
            # If still alive then kill and raise timeout
            if move.is_alive():
                self._move_event.set()
                self._moving = False
                msg = "Timeout waiting for filter wheel move to complete"
                self.logger.error(msg)
                raise error.Timeout(msg)
Пример #2
0
    def slew_to_target(self, blocking=False, timeout=180):
        """ Slews to the currently assigned target coordinates.

        Slews the mount to the coordinates that have been assigned by `~set_target_coordinates`.
        If no coordinates have been set, do nothing and return `False`, otherwise
        return response from the mount.

        If `blocking=True` then wait for up to `timeout` seconds for the mount
        to reach the `is_tracking` state. If a timeout occurs, raise a `pocs.error.Timeout`
        exception.

        Args:
            blocking (bool, optional): If command should block while slewing to
                home, default False.
            timeout (int, optional): Maximum time spent slewing to home, default
                180 seconds.

        Returns:
            bool: indicating success
        """
        success = False

        if self.is_parked:
            self.logger.info("Mount is parked")
        elif not self.has_target:
            self.logger.info("Target Coordinates not set")
        else:
            self.logger.debug('Slewing to target')
            success = self.query('slew_to_target')

            self.logger.debug("Mount response: {}".format(success))
            if success:
                if blocking:
                    # Set up the timeout timer
                    self.logger.debug(
                        f'Setting slew timeout timer for {timeout} sec')
                    timeout_timer = CountdownTimer(timeout)
                    block_time = 3  # seconds

                    while self.is_tracking is False:
                        if timeout_timer.expired():
                            self.logger.warning(
                                f'slew_to_target timout: {timeout} seconds')
                            raise error.Timeout('Problem slewing to target')

                        self.logger.debug(
                            f'Slewing to target, sleeping for {block_time} seconds'
                        )
                        timeout_timer.sleep(max_sleep=block_time)

                    self.logger.debug(f'Done with slew_to_target block')
            else:
                self.logger.warning('Problem with slew_to_target')

        return success
Пример #3
0
 def _poll_exposure(self,
                    readout_args,
                    exposure_time,
                    timeout=None,
                    interval=0.01):
     """ Wait until camera is no longer exposing or the timeout is reached. If the timeout is
     reached, an `error.Timeout` is raised.
     """
     if timeout is None:
         timer_duration = self._timeout + self._readout_time + exposure_time.to_value(
             u.second)
     else:
         timer_duration = timeout
     self.logger.debug(
         f"Polling exposure with timeout of {timer_duration} seconds.")
     timer = CountdownTimer(duration=timer_duration)
     try:
         while self.is_exposing:
             if timer.expired():
                 msg = f"Timeout (timer.duration={timer.duration!r}) waiting for exposure on"
                 f" {self} to complete"
                 raise error.Timeout(msg)
             time.sleep(interval)
     except Exception as err:
         # Error returned by driver at some point while polling
         self.logger.error(
             f'Error while waiting for exposure on {self}: {err!r}')
         self._exposure_error = repr(err)
         raise err
     else:
         # Camera type specific readout function
         try:
             self._readout(*readout_args)
         except Exception as err:
             self.logger.error(f"Error during readout on {self}: {err!r}")
             self._exposure_error = repr(err)
             raise err
     finally:
         # Make sure this gets set regardless of any errors
         self._is_exposing_event.clear()
Пример #4
0
    def correct_tracking(self, correction_info, axis_timeout=30.):
        """ Make tracking adjustment corrections.

        Args:
            correction_info (dict[tuple]): Correction info to be applied, see
                `get_tracking_correction`.
            axis_timeout (float, optional): Timeout for adjustment in each axis,
                default 30 seconds.

        Raises:
            `error.Timeout`: Timeout error.
        """
        for axis, corrections in correction_info.items():
            if not axis or not corrections:
                continue

            offset = corrections[0]
            offset_ms = corrections[1]
            delta_direction = corrections[2]

            self.logger.info("Adjusting {}: {} {:0.2f} ms {:0.2f}".format(
                axis, delta_direction, offset_ms, offset))

            self.query('move_ms_{}'.format(delta_direction),
                       '{:05.0f}'.format(offset_ms))

            # Adjust tracking for `axis_timeout` seconds then fail if not done.
            start_tracking_time = current_time()
            while self.is_tracking is False:
                if (current_time() - start_tracking_time).sec > axis_timeout:
                    raise error.Timeout(
                        "Tracking adjustment timeout: {}".format(axis))

                self.logger.debug(
                    "Waiting for {} tracking adjustment".format(axis))
                time.sleep(0.5)
Пример #5
0
def wait_for_events(
    events,
    timeout=600,
    sleep_delay=5 * u.second,
    callback=None,
):
    """Wait for event(s) to be set.

    This method will wait for a maximum of `timeout` seconds for all of the `events`
    to complete.

    Checks every `sleep_delay` seconds for the events to be set.

    If provided, the `callback` will be called every `sleep_delay` seconds.
    The callback should return `True` to continue waiting otherwise `False`
    to interrupt the loop and return from the function.

    .. doctest::

        >>> import time
        >>> import threading
        >>> from panoptes.utils.time import wait_for_events
        >>> # Create some events, normally something like taking an image.
        >>> event0 = threading.Event()
        >>> event1 = threading.Event()

        >>> # Wait for 30 seconds but interrupt after 1 second by returning False from callback.
        >>> def interrupt_cb(): time.sleep(1); return False
        >>> # The function will return False if events are not set.
        >>> wait_for_events([event0, event1], timeout=30, callback=interrupt_cb)
        False

        >>> # Timeout will raise an exception.
        >>> wait_for_events([event0, event1], timeout=1)
        Traceback (most recent call last):
          File "<input>", line 1, in <module>
          File ".../panoptes-utils/src/panoptes/utils/time.py", line 254, in wait_for_events
        panoptes.utils.error.Timeout: Timeout: Timeout waiting for generic event

        >>> # Set the events in another thread for normal usage.
        >>> def set_events(): time.sleep(1); event0.set(); event1.set()
        >>> threading.Thread(target=set_events).start()
        >>> wait_for_events([event0, event1], timeout=30)
        True

    Args:
        events (list(`threading.Event`)): An Event or list of Events to wait on.
        timeout (float|`astropy.units.Quantity`): Timeout in seconds to wait for events,
            default 600 seconds.
        sleep_delay (float, optional): Time in seconds between event checks.
        callback (callable): A periodic callback that should return `True` to continue
            waiting or `False` to interrupt the loop. Can also be used for e.g. custom logging.

    Returns:
        bool: True if events were set, False otherwise.

    Raises:
        error.Timeout: Raised if events have not all been set before `timeout` seconds.
    """
    with suppress(AttributeError):
        sleep_delay = sleep_delay.to_value('second')

    event_timer = CountdownTimer(timeout)

    if not isinstance(events, list):
        events = [events]

    start_time = current_time()
    while not all([event.is_set() for event in events]):
        elapsed_secs = round((current_time() - start_time).to_value('second'),
                             2)

        if event_timer.expired():
            raise error.Timeout(
                f"Timeout waiting for {len(events)} events after {elapsed_secs} seconds"
            )

        if callable(callback) and callback() is False:
            logger.warning(
                f"Waiting for {len(events)} events has been interrupted after {elapsed_secs} seconds"
            )
            break

        # Sleep for a little bit.
        event_timer.sleep(max_sleep=sleep_delay)

    return all([event.is_set() for event in events])
Пример #6
0
def get_solve_field(fname, replace=True, overwrite=True, timeout=30, **kwargs):
    """Convenience function to wait for `solve_field` to finish.

    This function merely passes the `fname` of the image to be solved along to `solve_field`,
    which returns a subprocess.Popen object. This function then waits for that command
    to complete, populates a dictonary with the EXIF informaiton and returns. This is often
    more useful than the raw `solve_field` function.

    Example:

    >>> from panoptes.utils.images import fits as fits_utils

    >>> # Get our fits filename.
    >>> fits_fn = getfixture('unsolved_fits_file')

    >>> # Perform the solve.
    >>> solve_info = fits_utils.get_solve_field(fits_fn)

    >>> # Show solved filename.
    >>> solve_info['solved_fits_file']
    '.../unsolved.fits'

    >>> # Pass a suggested location.
    >>> ra = 15.23
    >>> dec = 90
    >>> radius = 5 # deg
    >>> solve_info = fits_utils.solve_field(fits_fn, ra=ra, dec=dec, radius=radius)

    >>> # Pass kwargs to `solve-field` program.
    >>> solve_kwargs = {'--pnm': '/tmp/awesome.bmp', '--overwrite': True}
    >>> solve_info = fits_utils.get_solve_field(fits_fn, **solve_kwargs, skip_solved=False)
    >>> assert os.path.exists('/tmp/awesome.bmp')

    Args:
        fname ({str}): Name of FITS file to be solved.
        replace (bool, optional): Saves the WCS back to the original file,
            otherwise output base filename with `.new` extension. Default True.
        overwrite (bool, optional): Clobber file, default True. Required if `replace=True`.
        timeout (int, optional): The timeout for solving, default 30 seconds.
        **kwargs ({dict}): Options to pass to `solve_field` should start with `--`.

    Returns:
        dict: Keyword information from the solved field.
    """
    skip_solved = kwargs.get('skip_solved', True)

    out_dict = {}
    output = None
    errs = None

    header = getheader(fname)
    wcs = WCS(header)

    # Check for solved file
    if skip_solved and wcs.is_celestial:
        logger.info(
            f"Skipping solved file (use skip_solved=False to solve again): {fname}"
        )

        out_dict.update(header)
        out_dict['solved_fits_file'] = fname
        return out_dict

    # Set a default radius of 15
    if overwrite:
        kwargs['--overwrite'] = True

    # Use unpacked version of file.
    was_compressed = False
    if fname.endswith('.fz'):
        logger.debug(f'Uncompressing {fname}')
        fname = funpack(fname)
        logger.debug(f'Using {fname} for solving')
        was_compressed = True

    logger.debug(f'Solving with: {kwargs!r}')
    proc = solve_field(fname, **kwargs)
    try:
        output, errs = proc.communicate(timeout=timeout)
    except subprocess.TimeoutExpired:
        proc.kill()
        output, errs = proc.communicate()
        raise error.Timeout(f'Timeout while solving: {output!r} {errs!r}')
    else:
        if proc.returncode != 0:
            logger.debug(f'Returncode: {proc.returncode}')
        for log in [output, errs]:
            if log and log > '':
                logger.debug(f'Output on {fname}: {log}')

        if proc.returncode == 3:
            raise error.SolveError(f'solve-field not found: {output}')

    new_fname = fname.replace('.fits', '.new')
    if replace:
        logger.debug(f'Overwriting original {fname}')
        os.replace(new_fname, fname)
    else:
        fname = new_fname

    try:
        header = getheader(fname)
        header.remove('COMMENT', ignore_missing=True, remove_all=True)
        header.remove('HISTORY', ignore_missing=True, remove_all=True)
        out_dict.update(header)
    except OSError:
        logger.warning(f"Can't read fits header for: {fname}")

    # Check it was solved.
    if WCS(header).is_celestial is False:
        raise error.SolveError(
            'File not properly solved, no WCS header present.')

    # Remove WCS file.
    os.remove(fname.replace('.fits', '.wcs'))

    if was_compressed and replace:
        logger.debug(f'Compressing plate-solved {fname}')
        fname = fpack(fname)

    out_dict['solved_fits_file'] = fname

    return out_dict
Пример #7
0
def get_solve_field(fname, replace=True, remove_extras=True, **kwargs):
    """Convenience function to wait for `solve_field` to finish.

    This function merely passes the `fname` of the image to be solved along to `solve_field`,
    which returns a subprocess.Popen object. This function then waits for that command
    to complete, populates a dictonary with the EXIF informaiton and returns. This is often
    more useful than the raw `solve_field` function

    Args:
        fname ({str}): Name of FITS file to be solved
        replace (bool, optional): Replace fname the solved file
        remove_extras (bool, optional): Remove the files generated by solver
        **kwargs ({dict}): Options to pass to `solve_field`

    Returns:
        dict: Keyword information from the solved field
    """
    verbose = kwargs.get('verbose', False)
    skip_solved = kwargs.get('skip_solved', True)

    out_dict = {}
    output = None
    errs = None

    file_path, file_ext = os.path.splitext(fname)

    header = getheader(fname)
    wcs = WCS(header)

    # Check for solved file
    if skip_solved and wcs.is_celestial:

        if verbose:
            print("Solved file exists, skipping",
                  "(pass skip_solved=False to solve again):", fname)

        out_dict.update(header)
        out_dict['solved_fits_file'] = fname
        return out_dict

    if verbose:
        print("Entering get_solve_field:", fname)

    # Set a default radius of 15
    kwargs.setdefault('radius', 15)

    proc = solve_field(fname, **kwargs)
    try:
        output, errs = proc.communicate(timeout=kwargs.get('timeout', 30))
    except subprocess.TimeoutExpired:
        proc.kill()
        raise error.Timeout("Timeout while solving")
    else:
        if verbose:
            print("Returncode:", proc.returncode)
            print("Output:", output)
            print("Errors:", errs)

        if proc.returncode == 3:
            raise error.SolveError('solve-field not found: {}'.format(output))

        if not os.path.exists(fname.replace(file_ext, '.solved')):
            raise error.SolveError('File not solved')

        try:
            # Handle extra files created by astrometry.net
            new = fname.replace(file_ext, '.new')
            rdls = fname.replace(file_ext, '.rdls')
            axy = fname.replace(file_ext, '.axy')
            xyls = fname.replace(file_ext, '-indx.xyls')

            if replace and os.path.exists(new):
                # Remove converted fits
                os.remove(fname)
                # Rename solved fits to proper extension
                os.rename(new, fname)

                out_dict['solved_fits_file'] = fname
            else:
                out_dict['solved_fits_file'] = new

            if remove_extras:
                for f in [rdls, xyls, axy]:
                    if os.path.exists(f):
                        os.remove(f)

        except Exception as e:
            warn('Cannot remove extra files: {}'.format(e))

    if errs is not None:
        warn("Error in solving: {}".format(errs))
    else:

        try:
            out_dict.update(getheader(fname))
        except OSError:
            if verbose:
                print("Can't read fits header for:", fname)

    return out_dict
Пример #8
0
def improve_wcs(fname, remove_extras=True, replace=True, timeout=30, **kwargs):
    """Improve the world-coordinate-system (WCS) of a FITS file.

    This will plate-solve an already-solved field, using a verification process
    that will also attempt a SIP distortion correction.

    Args:
        fname (str): Full path to FITS file.
        remove_extras (bool, optional): If generated files should be removed, default True.
        replace (bool, optional): Overwrite existing file, default True.
        timeout (int, optional): Timeout for the solve, default 30 seconds.
        **kwargs: Additional keyword args for `solve_field`. Can also include a
            `verbose` flag.

    Returns:
        dict: FITS headers, including solve information.

    Raises:
        error.SolveError: Description
        error.Timeout: Description
    """
    verbose = kwargs.get('verbose', False)
    out_dict = {}
    output = None
    errs = None

    if verbose:
        print("Entering improve_wcs: {}".format(fname))

    options = [
        '--continue',
        '-t',
        '3',
        '-q',
        '0.01',
        '--no-plots',
        '--guess-scale',
        '--cpulimit',
        str(timeout),
        '--no-verify',
        '--crpix-center',
        '--match',
        'none',
        '--corr',
        'none',
        '--wcs',
        'none',
        '-V',
        fname,
    ]

    proc = solve_field(fname, solve_opts=options, **kwargs)
    try:
        output, errs = proc.communicate(timeout=timeout)
    except subprocess.TimeoutExpired:
        proc.kill()
        raise error.Timeout("Timeout while solving")
    else:
        if verbose:
            print("Output: {}", output)
            print("Errors: {}", errs)

        if not os.path.exists(fname.replace('.fits', '.solved')):
            raise error.SolveError('File not solved')

        try:
            # Handle extra files created by astrometry.net
            new = fname.replace('.fits', '.new')
            rdls = fname.replace('.fits', '.rdls')
            axy = fname.replace('.fits', '.axy')
            xyls = fname.replace('.fits', '-indx.xyls')

            if replace and os.path.exists(new):
                # Remove converted fits
                os.remove(fname)
                # Rename solved fits to proper extension
                os.rename(new, fname)

                out_dict['solved_fits_file'] = fname
            else:
                out_dict['solved_fits_file'] = new

            if remove_extras:
                for f in [rdls, xyls, axy]:
                    if os.path.exists(f):
                        os.remove(f)

        except Exception as e:
            warn('Cannot remove extra files: {}'.format(e))

    if errs is not None:
        warn("Error in solving: {}".format(errs))
    else:
        try:
            out_dict.update(fits.getheader(fname))
        except OSError:
            if verbose:
                print("Can't read fits header for {}".format(fname))

    return out_dict
Пример #9
0
    def _efw_poll(self, filterwheel_ID, position, move_event, timeout):
        """
        Polls filter wheel until the current move is complete.

        Also monitors for errors while polling and checks position after the move is complete.
        Optionally sets a threading.Event to signal the end of the move. Has an optional timeout
        to raise an TimeoutError is the move takes longer than expected.

        Args:
            filterwheel_ID (int): integer ID of the filterwheel that is moving.
            position (int): position to move the filter wheel. Must be an integer >= 0.
            move_event (threading.Event, optional): Event to set once the move is complete
            timeout (u.Quantity, optional): maximum time to wait for the move to complete. Should
                be a Quantity with time units. If a numeric type without units is given seconds
                will be assumed.

            Raises:
                `panoptes.utils.error.PanError`: raised if the driver returns an error or if the final
                    position is not as expected.
                `panoptes.utils.error.Timeout`: raised if the move does not end within the period of
                    time specified by the timeout argument.
        """
        if timeout is not None:
            timer = CountdownTimer(duration=timeout)

        try:
            # No status query function in the SDK. Only way to check on progress of move
            # is to keep issuing the same move command until we stop getting the MOVING
            # error code back.
            error_code = self._CDLL.EFWSetPosition(
                ctypes.c_int(filterwheel_ID), ctypes.c_int(position))
            while error_code == ErrorCode.MOVING:
                if timeout is not None and timer.expired():
                    msg = "Timeout waiting for filterwheel {} to move to {}".format(
                        filterwheel_ID, position)
                    raise error.Timeout(msg)
                time.sleep(0.1)
                error_code = self._CDLL.EFWSetPosition(
                    ctypes.c_int(filterwheel_ID), ctypes.c_int(position))

            if error_code != ErrorCode.SUCCESS:
                # Got some sort of error while polling.
                msg = "Error while moving filterwheel {} to {}: {}".format(
                    filterwheel_ID, position,
                    ErrorCode(error_code).name)
                self.logger.error(msg)
                raise error.PanError(msg)

            final_position = self.get_position(filterwheel_ID)
            if final_position != position:
                msg = "Tried to move filterwheel {} to {}, but ended up at {}.".format(
                    filterwheel_ID, position, final_position)
                self.logger.error(msg)
                raise error.PanError(msg)

            self.logger.debug(
                f"Filter wheel {filterwheel_ID} moved to {position}.")
        finally:
            # Regardless must always set the Event when the move has stopped.
            if move_event is not None:
                move_event.set()
Пример #10
0
 def _timeout_move(self):
     self._move_event.set()
     msg = "Timeout waiting for filter wheel move to complete"
     self.logger.error(msg)
     raise error.Timeout(msg)