<commit_before>"""error sources for use in telescope tracking control loop.

The errorsources module provides classes that derive from the abstract ErrorSource class. Each
class defines a method compute_error() which returns an error vector representing the difference
between the current position and some measure of the ideal position. This information might come
from ephemeris data for a celestial or man-made object, a camera, or even human input. Error source
classes can also be designed to compute an error vector by combining data from multiple sources
or by intelligently switching between sources.
"""

from __future__ import print_function
import datetime
import math
from .control import ErrorSource
from .mathutils import *
import ephem
import numpy as np
try:
    import cv2
    from . import webcam
except ImportError as e:
    if 'cv2' in e.message:
        print('Failed to import cv2. Optical tracking requires OpenCV.')
        pass
    raise


class BlindErrorSource(ErrorSource):
    """Ephemeris based error source.

    This class implements an error source based on computed ephemeris information. It is dubbed
    a "blind" error source in the sense that no physical sensing of the target position is
    involved (such as by use of a camera). The error vector is computed by taking the difference
    of the mount's current position and the position of the target as predicted by the PyEphem
    package.

    Attributes:
        observer: PyEphem Observer object.
        target: PyEphem Target object.
        mount: TelescopeMount object.
        backlash_compensation: A boolean, True to enable backlash compensation.
        offset_callback: A function that can make adjustments to the target position.
        mount_position_cached: Cached position of the mount from last call to compute_error().
        target_position_cached: Cached position of the target from last call to compute_error().
    """

    def __init__(self, mount, observer, target, backlash_compensation=False):
        if backlash_compensation and not mount.backlash_supported():
            raise ValueError('mount does not support backlash compensation')
        self.observer = observer
        self.target = target
        self.mount = mount
        self.backlash_compensation = backlash_compensation
        self.offset_callback = None
        self.mount_position_cached = None
        self.target_position_cached = None
        self.axes = mount.get_axis_names()

    def register_offset_callback(self, callback):
        self.offset_callback = callback

    def get_axis_names(self):
        return self.axes

    def compute_error(self, retries=0):

        # Get coordinates of the target from a past time for use in determining direction of
        # motion. This needs to be far enough in the past that this position and the current
        # position are separated enough to compute an accurate motion vector.
        a_while_ago = datetime.datetime.utcnow() - datetime.timedelta(seconds=10)
        self.observer.date = ephem.Date(a_while_ago)
        self.target.compute(self.observer)
        target_position_prev = {}
        for axis in self.axes:
            target_position_prev[axis] = eval('self.target.' + axis) * 180.0 / math.pi

        # get coordinates of target for current time
        self.observer.date = ephem.Date(datetime.datetime.utcnow())
        self.target.compute(self.observer)
        target_position = {}
        for axis in self.axes:
            target_position[axis] = eval('self.target.' + axis) * 180.0 / math.pi
        self.target_position_cached = target_position

        # get current position of telescope (degrees)
        mount_position = self.mount.get_position()
        self.mount_position_cached = mount_position

        # make any corrections to predicted position
        if self.offset_callback is not None:
            adjusted_position = adjust_position(
                target_position_prev,
                target_position,
                self.offset_callback()
            )
        else:
            adjusted_position = target_position

        if self.backlash_compensation:
            target_motion_direction = {}
            for axis in self.axes:
                target_motion_direction[axis] = np.sign(
                    wrap_error(target_position[axis] - target_position_prev[axis])
                )

            # compensate for backlash if object is moving against the slew
            # direction used during alignment
            align_dir = self.mount.get_aligned_slew_dir()
            axes_to_adjust = {}
            for axis in self.axes:
                axes_to_adjust[axis] = align_dir[axis] != target_motion_direction[axis]
            mount_position = self.mount.remove_backlash(mount_position, axes_to_adjust)
            self.mount_position_cached = mount_position

        # compute pointing errors in degrees
        error = {}
        for axis in self.axes:
            error[axis] = wrap_error(mount_position[axis] - adjusted_position[axis])

        return error


class OpticalErrorSource(ErrorSource):

    def __init__(
            self,
            cam_dev_path,<commit_msg>#64 #67 #68: BlindErrorSource handles both sides of meridian for equatorial mounts<commit_after>"""error sources for use in telescope tracking control loop.

The errorsources module provides classes that derive from the abstract ErrorSource class. Each
class defines a method compute_error() which returns an error vector representing the difference
between the current position and some measure of the ideal position. This information might come
from ephemeris data for a celestial or man-made object, a camera, or even human input. Error source
classes can also be designed to compute an error vector by combining data from multiple sources
or by intelligently switching between sources.
"""

from __future__ import print_function
import datetime
import math
from .control import ErrorSource
from .mathutils import *
import ephem
import numpy as np
try:
    import cv2
    from . import webcam
except ImportError as e:
    if 'cv2' in e.message:
        print('Failed to import cv2. Optical tracking requires OpenCV.')
        pass
    raise


class BlindErrorSource(ErrorSource):
    """Ephemeris based error source.

    This class implements an error source based on computed ephemeris information. It is dubbed
    a "blind" error source in the sense that no physical sensing of the target position is
    involved (such as by use of a camera). The error vector is computed by taking the difference
    of the mount's current position and the position of the target as predicted by the PyEphem
    package.

    Attributes:
        observer: PyEphem Observer object.
        target: PyEphem Target object.
        mount: TelescopeMount object.
        backlash_compensation: A boolean, True to enable backlash compensation.
        meridian_side: Selected side of meridian (equatorial only): 'east' or 'west'.
        offset_callback: A function that can make adjustments to the target position.
        mount_position_cached: Cached position of the mount from last call to compute_error().
        target_position_cached: Cached position of the target from last call to compute_error().
    """

    def __init__(
        self,
        mount,
        observer,
        target,
        backlash_compensation=False,
        meridian_side='west'
    ):
        """Inits BlindErrorSource object.

        Args:
            mount: A TelescopeMount object.
            observer: A PyEphem Observer object for the observer's location.
            target: A PyEphem Target object for the thing to point to.
            backlash_compensation: A boolean. When True, this class will
                attempt to compensate for the deadband caused by backlash
                in the mount's drive train.
            meridian_side: A string with values 'east' or 'west' that indicates
                which side of the meridian the mount should favor. Only applies
                to equatorial mounts.

        Raises:
            ValueError: For invalid argument values.
        """
        if backlash_compensation and not mount.backlash_supported():
            raise ValueError('mount does not support backlash compensation')
        if meridian_side not in ['east', 'west']:
            raise ValueError("meridian_side must be 'east' or 'west'")
        self.observer = observer
        self.target = target
        self.mount = mount
        self.backlash_compensation = backlash_compensation
        self.meridian_side = meridian_side
        self.offset_callback = None
        self.mount_position_cached = None
        self.target_position_cached = None
        self.axes = mount.get_axis_names()

    def register_offset_callback(self, callback):
        self.offset_callback = callback

    def get_axis_names(self):
        return self.axes

    def meridian_flip(self):
        self.meridian_side = 'east' if self.meridian_side == 'west' else 'west'

    def compute_error(self, retries=0):

        # Get coordinates of the target from a past time for use in determining direction of
        # motion. This needs to be far enough in the past that this position and the current
        # position are separated enough to compute an accurate motion vector.
        a_while_ago = datetime.datetime.utcnow() - datetime.timedelta(seconds=10)
        self.observer.date = ephem.Date(a_while_ago)
        self.target.compute(self.observer)
        target_position_prev = {}
        for axis in self.axes:
            target_position_prev[axis] = eval('self.target.' + axis) * 180.0 / math.pi

        # get coordinates of target for current time
        self.observer.date = ephem.Date(datetime.datetime.utcnow())
        self.target.compute(self.observer)
        target_position = {}
        for axis in self.axes:
            target_position[axis] = eval('self.target.' + axis) * 180.0 / math.pi
        self.target_position_cached = target_position

        # get current position of telescope (degrees)
        mount_position = self.mount.get_position()
        self.mount_position_cached = mount_position

        # make any corrections to predicted position
        if self.offset_callback is not None:
            adjusted_position = adjust_position(
                target_position_prev,
                target_position,
                self.offset_callback()
            )
        else:
            adjusted_position = target_position

        if self.backlash_compensation:
            target_motion_direction = {}
            for axis in self.axes:
                target_motion_direction[axis] = np.sign(
                    wrap_error(target_position[axis] - target_position_prev[axis])
                )

            # compensate for backlash if object is moving against the slew
            # direction used during alignment
            align_dir = self.mount.get_aligned_slew_dir()
            axes_to_adjust = {}
            for axis in self.axes:
                axes_to_adjust[axis] = align_dir[axis] != target_motion_direction[axis]
            mount_position = self.mount.remove_backlash(mount_position, axes_to_adjust)
            self.mount_position_cached = mount_position

        # compute pointing errors in degrees
        error = {}
        for axis in self.axes:
            if axis == 'dec':
                # error depends on which side of meridian is selected and which
                # side of meridian the mount is on currently
                if self.meridian_side == 'east':
                    if mount_position['pdec'] < 180.0:
                        error[axis] = mount_position[axis] - adjusted_position[axis]
                    else:
                        error[axis] = 180.0 - mount_position[axis] - adjusted_position[axis]
                else:
                    if mount_position['pdec'] < 180.0:
                        error[axis] = adjusted_position[axis] + mount_position[axis] - 180.0
                    else:
                        error[axis] = adjusted_position[axis] - mount_position[axis]
            elif axis == 'ra':
                # error depends on which side of meridian is selected
                mount_side = 'east' if mount_position['pdec'] < 180.0 else 'west'
                if self.meridian_side == mount_side:
                    error[axis] = wrap_error(mount_position[axis] - adjusted_position[axis])
                else:
                    error[axis] = wrap_error(mount_position[axis] - adjusted_position[axis] + 180.0)

                # avoid crossing through axis limit region (path to target
                # should not require counter weight to ever point up)
                if mount_position['pra'] - error[axis] > 360.0:
                    print('RA axis would have crossed through limits')
                    error[axis] = error[axis] + 360.0
                elif mount_position['pra'] - error[axis] < 0.0:
                    print('RA axis would have crossed through limits')
                    error[axis] = error[axis] - 360.0

            else:
                error[axis] = wrap_error(mount_position[axis] - adjusted_position[axis])

        return error


class OpticalErrorSource(ErrorSource):

    def __init__(
            self,
            cam_dev_path,
{'fraction_non_alphanumeric': 0.06250936891020836, 'fraction_numerical': 0.005396492280017988, 'mean_word_length': 2.400356778797146, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3910874', 'n_tokens_mistral': 3523, 'n_tokens_neox': 3231, 'n_words': 1195}