Esempio n. 1
0
def solve_field(fname, timeout=15, solve_opts=None, *args, **kwargs):
    """ Plate solves an image.

    Note: This is a low-level wrapper around the underlying `solve-field`
        program. See `get_solve_field` for more typical usage and examples.


    Args:
        fname(str, required):       Filename to solve in .fits extension.
        timeout(int, optional):     Timeout for the solve-field command,
                                    defaults to 60 seconds.
        solve_opts(list, optional): List of options for solve-field.
    """
    solve_field_script = shutil.which('solve-field')

    if solve_field_script is None:  # pragma: no cover
        raise error.InvalidSystemCommand(
            f"Can't find solve-field, is astrometry.net installed?")

    # Add the options for solving the field
    if solve_opts is not None:
        options = solve_opts
    else:
        # Default options
        options = [
            '--guess-scale',
            '--cpulimit',
            str(timeout),
            '--no-verify',
            '--crpix-center',
            '--temp-axy',
            '--index-xyls',
            'none',
            '--solved',
            'none',
            '--match',
            'none',
            '--rdls',
            'none',
            '--corr',
            'none',
            '--downsample',
            '4',
            '--no-plots',
        ]

        if 'ra' in kwargs:
            options.append('--ra')
            options.append(str(kwargs.get('ra')))
        if 'dec' in kwargs:
            options.append('--dec')
            options.append(str(kwargs.get('dec')))
        if 'radius' in kwargs:
            options.append('--radius')
            options.append(str(kwargs.get('radius')))

    # Gather all the kwargs that start with `--` and are not already present.
    logger.debug(f'Adding kwargs: {kwargs!r}')

    def _modify_opt(opt, val):
        if isinstance(val, bool):
            opt_string = str(opt)
        else:
            opt_string = f'{opt}={val}'

        return opt_string

    options.extend([
        _modify_opt(opt, val) for opt, val in kwargs.items()
        if opt.startswith('--') and opt not in options
        and not isinstance(val, bool)
    ])

    cmd = [solve_field_script] + options + [fname]

    logger.debug(f'Solving with: {cmd}')
    try:
        proc = subprocess.Popen(cmd,
                                universal_newlines=True,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
    except Exception as e:
        raise error.PanError(f"Problem plate-solving in solve_field: {e!r}")

    return proc
Esempio n. 2
0
def solve_field(fname, timeout=15, solve_opts=None, **kwargs):
    """ Plate solves an image.

    Args:
        fname(str, required):       Filename to solve in .fits extension.
        timeout(int, optional):     Timeout for the solve-field command,
                                    defaults to 60 seconds.
        solve_opts(list, optional): List of options for solve-field.
        verbose(bool, optional):    Show output, defaults to False.
    """
    verbose = kwargs.get('verbose', False)
    if verbose:
        print("Entering solve_field")

    solve_field_script = shutil.which('panoptes-solve-field')

    if solve_field_script is None:  # pragma: no cover
        raise error.InvalidSystemCommand(
            "Can't find panoptes-solve-field: {}".format(solve_field_script))

    # Add the options for solving the field
    if solve_opts is not None:
        options = solve_opts
    else:
        options = [
            '--guess-scale',
            '--cpulimit',
            str(timeout),
            '--no-verify',
            '--no-plots',
            '--crpix-center',
            '--match',
            'none',
            '--corr',
            'none',
            '--wcs',
            'none',
            '--downsample',
            '4',
        ]

        if kwargs.get('overwrite', False):
            options.append('--overwrite')
        if kwargs.get('skip_solved', False):
            options.append('--skip-solved')

        if 'ra' in kwargs:
            options.append('--ra')
            options.append(str(kwargs.get('ra')))
        if 'dec' in kwargs:
            options.append('--dec')
            options.append(str(kwargs.get('dec')))
        if 'radius' in kwargs:
            options.append('--radius')
            options.append(str(kwargs.get('radius')))

    if fname.endswith('.fz'):
        options.append('--extension=1')

    cmd = [solve_field_script] + options + [fname]
    if verbose:
        print("Cmd:", cmd)

    try:
        proc = subprocess.Popen(cmd,
                                universal_newlines=True,
                                stdin=subprocess.PIPE,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
    except OSError as e:
        raise error.InvalidCommand(
            "Can't send command to panoptes-solve-field: {} \t {}".format(
                e, cmd))
    except ValueError as e:
        raise error.InvalidCommand(
            "Bad parameters to solve_field: {} \t {}".format(e, cmd))
    except Exception as e:
        raise error.PanError("Timeout on plate solving: {}".format(e))

    if verbose:
        print("Returning proc from solve_field")

    return proc
Esempio n. 3
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()
Esempio n. 4
0
    def take_exposure(self,
                      seconds=1.0 * u.second,
                      filename=None,
                      dark=False,
                      blocking=False,
                      timeout=None,
                      *args,
                      **kwargs):
        """Take an exposure for given number of seconds and saves to provided filename.

        Args:
            seconds (u.second, optional): Length of exposure.
            filename (str, optional): Image is saved to this filename.
            dark (bool, optional): Exposure is a dark frame, default False. On cameras that support
                taking dark frames internally (by not opening a mechanical shutter) this will be
                done, for other cameras the light must be blocked by some other means. In either
                case setting dark to True will cause the `IMAGETYP` FITS header keyword to have
                value 'Dark Frame' instead of 'Light Frame'. Set dark to None to disable the
                `IMAGETYP` keyword entirely.
            blocking (bool, optional): If False (default) returns immediately after starting
                the exposure, if True will block until it completes and file exists.
            timeout (astropy.Quantity): The timeout to use for the exposure. If None, will be
                calculated automatically.
        Returns:
            threading.Thread: The readout thread, which joins when readout has finished.
        """
        self._exposure_error = None

        if not self.is_connected:
            err = AssertionError("Camera must be connected for take_exposure!")
            self.logger.error(str(err))
            self._exposure_error = repr(err)
            raise err

        if not filename:
            err = AssertionError("Must pass filename for take_exposure")
            self.logger.error(str(err))
            self._exposure_error = repr(err)
            raise err

        if not self.can_take_internal_darks:
            if dark:
                try:
                    # Can't take internal dark, so try using an opaque filter in a filterwheel
                    self.filterwheel.move_to_dark_position(blocking=True)
                    self.logger.debug("Taking dark exposure using filter: " +
                                      self.filterwheel.filter_name(
                                          self.filterwheel._dark_position))
                except (AttributeError, error.NotFound):
                    # No filterwheel, or no opaque filter (dark_position not set)
                    self.logger.warning(
                        "Taking dark exposure without shutter or opaque filter."
                        " Is the lens cap on?")
            else:
                with suppress(AttributeError, error.NotFound):
                    # Ignoring exceptions from no filterwheel, or no last light position
                    self.filterwheel.move_to_light_position(blocking=True)

        # Check that the camera (and subcomponents) is ready
        if not self.is_ready:
            # Work out why the camera isn't ready.
            self.logger.warning(f'Cameras not ready: {self.readiness!r}')
            raise error.PanError(
                f"Attempt to start exposure on {self} while not ready.")

        if not isinstance(seconds, u.Quantity):
            seconds = seconds * u.second

        self.logger.debug(
            f'Taking seconds={seconds!r} exposure on {self.name}: filename={filename!r}'
        )

        header = self._create_fits_header(seconds, dark)

        if self.is_exposing:
            err = error.PanError(
                f"Attempt to take exposure on {self} while one already in progress."
            )
            self._exposure_error = repr(err)
            raise err

        try:
            # Camera type specific exposure set up and start
            self._is_exposing_event.set()
            readout_args = self._start_exposure(seconds, filename, dark,
                                                header, *args, *kwargs)
        except Exception as err:
            err = error.PanError(f"Error starting exposure on {self}: {err!r}")
            self._exposure_error = repr(err)
            self._is_exposing_event.clear()
            raise err

        # Start polling thread that will call camera type specific _readout method when done
        readout_thread = threading.Thread(target=self._poll_exposure,
                                          args=(readout_args, seconds),
                                          kwargs=dict(timeout=timeout))
        readout_thread.start()

        if blocking:
            self.logger.debug(f"Blocking on exposure event for {self}")
            readout_thread.join()
            while self.is_exposing:
                time.sleep(0.5)
            self.logger.trace(
                f'Exposure blocking complete, waiting for file to exist')
            while not os.path.exists(filename):
                time.sleep(0.1)
            self.logger.debug(
                f"Blocking complete on {self} for filename={filename!r}")

        return readout_thread
Esempio n. 5
0
def make_timelapse(directory,
                   fn_out=None,
                   glob_pattern='20[1-9][0-9]*T[0-9]*.jpg',
                   overwrite=False,
                   timeout=60,
                   **kwargs):
    """Create a timelapse.

    A timelapse is created from all the images in given ``directory``

    Args:
        directory (str): Directory containing image files.
        fn_out (str, optional): Full path to output file name, if not provided,
            defaults to `directory` basename.
        glob_pattern (str, optional): A glob file pattern of images to include,
            default '20[1-9][0-9]*T[0-9]*.jpg', which corresponds to the observation
            images but excludes any pointing images. The pattern should be relative
            to the local directory.
        overwrite (bool, optional): Overwrite timelapse if exists, default False.
        timeout (int): Timeout for making movie, default 60 seconds.
        **kwargs (dict):

    Returns:
        str: Name of output file

    Raises:
        error.InvalidSystemCommand: Raised if ffmpeg command is not found.
        FileExistsError: Raised if fn_out already exists and overwrite=False.
    """
    if fn_out is None:
        head, tail = os.path.split(directory)
        if tail == '':
            head, tail = os.path.split(head)

        field_name = head.split('/')[-2]
        cam_name = head.split('/')[-1]
        fname = f'{field_name}_{cam_name}_{tail}.mp4'
        fn_out = os.path.normpath(os.path.join(directory, fname))

    if os.path.exists(fn_out) and not overwrite:
        raise FileExistsError("Timelapse exists. Set overwrite=True if needed")

    ffmpeg = shutil.which('ffmpeg')
    if ffmpeg is None:
        raise error.InvalidSystemCommand(
            "ffmpeg not found, can't make timelapse")

    inputs_glob = os.path.join(directory, glob_pattern)

    try:
        ffmpeg_cmd = [
            ffmpeg,
            '-r',
            '3',
            '-pattern_type',
            'glob',
            '-i',
            inputs_glob,
            '-s',
            'hd1080',
            '-vcodec',
            'libx264',
        ]

        if overwrite:
            ffmpeg_cmd.append('-y')

        ffmpeg_cmd.append(fn_out)

        logger.debug(ffmpeg_cmd)

        proc = subprocess.Popen(ffmpeg_cmd,
                                universal_newlines=True,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        try:
            # Don't wait forever
            outs, errs = proc.communicate(timeout=timeout)
        except subprocess.TimeoutExpired:
            proc.kill()
            outs, errs = proc.communicate()
        finally:
            logger.debug(f"Output: {outs}")
            logger.debug(f"Errors: {errs}")

            # Double-check for file existence
            if not os.path.exists(fn_out):
                fn_out = None
    except Exception as e:
        raise error.PanError(f"Problem creating timelapse in {fn_out}: {e!r}")

    return fn_out
Esempio n. 6
0
def create_location_from_config():
    """
    Sets up the site and location details.

    These items are read from the 'site' config directive and include:
        * name
        * latitude
        * longitude
        * timezone
        * pressure
        * elevation
        * horizon

     """

    logger.debug('Setting up site details')

    try:
        config_site = get_config('location', default=None)
        if config_site is None:
            raise error.PanError(
                msg='location information not found in config.')

        name = config_site.get('name', 'Nameless Location')

        latitude = config_site.get('latitude')
        longitude = config_site.get('longitude')

        timezone = config_site.get('timezone')

        pressure = config_site.get('pressure', 0.680) * u.bar
        elevation = config_site.get('elevation', 0 * u.meter)
        horizon = config_site.get('horizon', 30 * u.degree)
        flat_horizon = config_site.get('flat_horizon', -6 * u.degree)
        focus_horizon = config_site.get('focus_horizon', -12 * u.degree)
        observe_horizon = config_site.get('observe_horizon', -18 * u.degree)

        location = {
            'name': name,
            'latitude': latitude,
            'longitude': longitude,
            'elevation': elevation,
            'timezone': timezone,
            'pressure': pressure,
            'horizon': horizon,
            'flat_horizon': flat_horizon,
            'focus_horizon': focus_horizon,
            'observe_horizon': observe_horizon,
        }
        logger.debug(f"Location: {location}")

        # Create an EarthLocation for the mount
        earth_location = EarthLocation(lat=latitude,
                                       lon=longitude,
                                       height=elevation)
        observer = Observer(location=earth_location,
                            name=name,
                            timezone=timezone)

        site_details = {
            "location": location,
            "earth_location": earth_location,
            "observer": observer
        }

        return site_details

    except Exception as e:
        raise error.PanError(msg=f'Bad site information: {e!r}')