<commit_before>"""Handles conversion between the sets of regions used in the `SosModel`
"""
from collections import defaultdict, namedtuple

import numpy as np
from rtree import index
from shapely.geometry import mapping, shape
from smif.convert.register import NDimensionalRegister, ResolutionSet

__author__ = "Will Usher, Tom Russell"
__copyright__ = "Will Usher, Tom Russell"
__license__ = "mit"

NamedShape = namedtuple('NamedShape', ['name', 'shape'])

class RegionSet(ResolutionSet):
    """Hold a set of regions, spatially indexed for ease of lookup when
    constructing conversion matrices.

    Parameters
    ----------
    set_name : str
        Name to use as identifier for this set of regions
    fiona_shape_iter: iterable
        Iterable (probably a list or a reader handle)
        of fiona feature records e.g. the 'features' entry of
        a GeoJSON collection

    """
    def __init__(self, set_name, fiona_shape_iter):
        self.name = set_name
        self._regions = []
        self.data = fiona_shape_iter

        self._idx = index.Index()
        for pos, region in enumerate(self._regions):
            self._idx.insert(pos, region.shape.bounds)

    @property
    def data(self):
        return self._regions

    @data.setter
    def data(self, value):
        names = {}
        for region in value:
            name = region['properties']['name']
            if name in names:
                raise AssertionError(
                    "Region set must have uniquely named regions - %s duplicated", name)
            names[name] = True
            self._regions.append(
                NamedShape(
                    name,
                    shape(region['geometry'])
                )
            )

    def get_entry_names(self):
        return [region.name for region in self.data]

    def as_features(self):
        """Get the regions as a list of feature dictionaries

        Returns
        -------
        list
            A list of GeoJSON-style dicts
        """
        return [
            {
                'type': 'Feature',
                'geometry': mapping(region.shape),
                'properties': {
                    'name': region.name
                }
            }
            for region in self._regions
        ]

    def centroids_as_features(self):
        """Get the region centroids as a list of feature dictionaries

        Returns
        -------
        list
            A list of GeoJSON-style dicts, with Point features corresponding to
            region centroids
        """
        return [
            {
                'type': 'Feature',
                'geometry': mapping(region.shape.centroid),
                'properties': {
                    'name': region.name
                }
            }
            for region in self._regions
        ]

    def intersection(self, bounds):
        """Return the subset of regions intersecting with a bounding box
        """
        return [self._regions[pos] for pos in self._idx.intersection(bounds)]

    def __getitem__(self, key):
        return self._regions[key]

    def __len__(self):
        return len(self._regions)

class RegionRegister(NDimensionalRegister):
    """Holds the sets of regions used by the SectorModels and provides conversion
    between data values relating to compatible sets of regions.
    """
    def convert(self, data, from_set_name, to_set_name):
        """Convert a list of data points for a given set of regions
        to another set of regions.

        Parameters
        ----------
        data: numpy.ndarray with dimension regions
        from_set_name: str
        to_set_name: str

        """
        from_set = self.get_entry(from_set_name)
        from_set_names = from_set.get_entry_names()
        to_set = self.get_entry(to_set_name)
        to_set_names = to_set.get_entry_names()

        converted = np.zeros(len(to_set))
        coefficents = self._conversion_coefficients(from_set, to_set)

        for from_region_name, from_value in zip(from_set_names, data):
            for to_region_name, coef in coefficents[from_region_name]:
                to_region_idx = to_set_names.index(to_region_name)
                converted[to_region_idx] += coef*from_value

        return converted

    def get_bounds(self, entry):
        return entry.shape.bounds

    def get_proportion(self, entry_a, entry_b):
        """Calculate the proportion of shape a that intersects with shape b
        """
        intersection = entry_a.shape.intersection(entry_b.shape)
        return intersection.area / entry_a.shape.area

    def _generate_coefficients(self, set_a, set_b):
        msg = "Generating region coefficients from set %s to %s"
        self.logger.info(msg, set_a, set_b)
        # from a to b
        self._conversions[set_a.name][set_b.name] = self._conversion_coefficients(set_a, set_b)
        # from b to a
        self._conversions[set_b.name][set_a.name] = self._conversion_coefficients(set_b, set_a)

    def _conversion_coefficients(self, from_set, to_set):
        """Return a dict containing the proportions of to_regions intersecting
        with each given from_region::

            {
                from_region.name: [
                    (to_region.name, proportion),
                    # ...
                ],
                # ...
            }

        """
        coefficients = defaultdict(list)

        for to_region in to_set:
            intersecting_from_regions = from_set.intersection(to_region.shape.bounds)

            for from_region in intersecting_from_regions:
                proportion = self.get_proportion(from_region, to_region)
                coefficient_pair = (to_region.name, proportion)
                coefficients[from_region.name].append(coefficient_pair)

        return coefficients

__REGISTER = RegionRegister()

def get_register():
    """Return single copy of RegionRegister
    """
    return __REGISTER
<commit_msg>Area now uses new coefficient functionality<commit_after>"""Handles conversion between the sets of regions used in the `SosModel`
"""
from collections import defaultdict, namedtuple

import numpy as np
from rtree import index
from shapely.geometry import mapping, shape
from smif.convert.register import NDimensionalRegister, ResolutionSet

__author__ = "Will Usher, Tom Russell"
__copyright__ = "Will Usher, Tom Russell"
__license__ = "mit"

NamedShape = namedtuple('NamedShape', ['name', 'shape'])

class RegionSet(ResolutionSet):
    """Hold a set of regions, spatially indexed for ease of lookup when
    constructing conversion matrices.

    Parameters
    ----------
    set_name : str
        Name to use as identifier for this set of regions
    fiona_shape_iter: iterable
        Iterable (probably a list or a reader handle)
        of fiona feature records e.g. the 'features' entry of
        a GeoJSON collection

    """
    def __init__(self, set_name, fiona_shape_iter):
        self.name = set_name
        self._regions = []
        self.data = fiona_shape_iter

        self._idx = index.Index()
        for pos, region in enumerate(self._regions):
            self._idx.insert(pos, region.shape.bounds)

    @property
    def data(self):
        return self._regions

    @data.setter
    def data(self, value):
        names = {}
        for region in value:
            name = region['properties']['name']
            if name in names:
                raise AssertionError(
                    "Region set must have uniquely named regions - %s duplicated", name)
            names[name] = True
            self._regions.append(
                NamedShape(
                    name,
                    shape(region['geometry'])
                )
            )

    def get_entry_names(self):
        return [region.name for region in self.data]

    def as_features(self):
        """Get the regions as a list of feature dictionaries

        Returns
        -------
        list
            A list of GeoJSON-style dicts
        """
        return [
            {
                'type': 'Feature',
                'geometry': mapping(region.shape),
                'properties': {
                    'name': region.name
                }
            }
            for region in self._regions
        ]

    def centroids_as_features(self):
        """Get the region centroids as a list of feature dictionaries

        Returns
        -------
        list
            A list of GeoJSON-style dicts, with Point features corresponding to
            region centroids
        """
        return [
            {
                'type': 'Feature',
                'geometry': mapping(region.shape.centroid),
                'properties': {
                    'name': region.name
                }
            }
            for region in self._regions
        ]

    def intersection(self, bounds):
        """Return the subset of regions intersecting with a bounding box
        """
        return [self._regions[pos] for pos in self._idx.intersection(bounds)]

    def __getitem__(self, key):
        return self._regions[key]

    def __len__(self):
        return len(self._regions)

class RegionRegister(NDimensionalRegister):
    """Holds the sets of regions used by the SectorModels and provides conversion
    between data values relating to compatible sets of regions.
    """
    def convert(self, data, from_set_name, to_set_name):
        """Convert a list of data points for a given set of regions
        to another set of regions.

        Parameters
        ----------
        data: numpy.ndarray with dimension regions
        from_set_name: str
        to_set_name: str

        """
        from_set = self.get_entry(from_set_name)
        to_set = self.get_entry(to_set_name)
        to_set_names = to_set.get_entry_names()

        converted = np.zeros(len(to_set))
        coefficents = self.get_coefficients(from_set.name, to_set.name)

        for from_idx, from_value in enumerate(data):
            for to_idx, _ in enumerate(to_set_names):
                coef = coefficents[from_idx, to_idx]
                converted[to_idx] += coef*from_value

        return converted

    def get_bounds(self, entry):
        return entry.shape.bounds

    def get_proportion(self, entry_a, entry_b):
        """Calculate the proportion of shape a that intersects with shape b
        """
        intersection = entry_a.shape.intersection(entry_b.shape)
        return intersection.area / entry_a.shape.area

    def _generate_coefficients(self, set_a, set_b):
        msg = "Generating region coefficients from set %s to %s"
        self.logger.info(msg, set_a, set_b)
        # from a to b
        self._conversions[set_a.name][set_b.name] = self._conversion_coefficients(set_a, set_b)
        # from b to a
        self._conversions[set_b.name][set_a.name] = self._conversion_coefficients(set_b, set_a)

    def _conversion_coefficients(self, from_set, to_set):
        """Return a dict containing the proportions of to_regions intersecting
        with each given from_region::

            {
                from_region.name: [
                    (to_region.name, proportion),
                    # ...
                ],
                # ...
            }

        """
        coefficients = defaultdict(list)

        for to_region in to_set:
            intersecting_from_regions = from_set.intersection(to_region.shape.bounds)

            for from_region in intersecting_from_regions:
                proportion = self.get_proportion(from_region, to_region)
                coefficient_pair = (to_region.name, proportion)
                coefficients[from_region.name].append(coefficient_pair)

        return coefficients

__REGISTER = RegionRegister()

def get_register():
    """Return single copy of RegionRegister
    """
    return __REGISTER
<|endoftext|>


<commit_before>import re, collections

__doc__ = """

Peter Norvig's famous spell checker.

"""

def words(text): return re.findall('[a-z]+', text.lower())

def train(features):
    model = collections.defaultdict(lambda: 1)
    for f in features:
        model[f] += 1
    return model

NWORDS = train(['gmail', 'yahoo', 'aol', 'hotmail', 'msn', 'sbcglobal', 'bellsouth', 'earthlink', 'com', 'net', 'org'])

alphabet = 'abcdefghijklmnopqrstuvwxyz'

def edits1(word):
   splits     = [(word[:i], word[i:]) for i in range(len(word) + 1)]
   deletes    = [a + b[1:] for a, b in splits if b]
   transposes = [a + b[1] + b[0] + b[2:] for a, b in splits if len(b)>1]
   replaces   = [a + c + b[1:] for a, b in splits for c in alphabet if b]
   inserts    = [a + c + b     for a, b in splits for c in alphabet]
   return set(deletes + transposes + replaces + inserts)

def known_edits2(word):
    return set(e2 for e1 in edits1(word) for e2 in edits1(e1) if e2 in NWORDS)

def known(words): return set(w for w in words if w in NWORDS)

def correct(word):
    candidates = known([word]) or known(edits1(word)) or known_edits2(word) or [word]
    return max(candidates, key=NWORDS.get)
<commit_msg>removing speller<commit_after><|endoftext|>


<commit_before>import re
import numpy as np
from astropy.table import Table, vstack
from kadi import events
from Ska.Numpy import interpolate
from Quaternion import Quat
import Ska.quatutil
from mica.archive import asp_l1
import mica.starcheck
from Ska.engarchive import fetch
from Chandra.Time import DateTime
import agasc

R2A = 206264.81  # Convert from radians to arcsec

def quat_vtransform(qs):
    """
    Transform a Nx4 matrix of quaternions into a Nx3x3 transform matrix
    :returns: Nx3x3 transform matrix
    :rtype: numpy array
    """
    x, y, z, w = qs[:, 0], qs[:, 1], qs[:, 2], qs[:, 3]
    xx2 = x * x * 2.
    yy2 = y * y * 2.
    zz2 = z * z * 2.
    xy2 = x * y * 2.
    wz2 = w * z * 2.
    zx2 = z * x * 2.
    wy2 = w * y * 2.
    yz2 = y * z * 2.
    wx2 = w * x * 2.

    t = np.empty((len(qs), 3, 3), float)
    t[:, 0, 0] = 1. - yy2 - zz2
    t[:, 0, 1] = xy2 - wz2
    t[:, 0, 2] = zx2 + wy2
    t[:, 1, 0] = xy2 + wz2
    t[:, 1, 1] = 1. - xx2 - zz2
    t[:, 1, 2] = yz2 - wx2
    t[:, 2, 0] = zx2 - wy2
    t[:, 2, 1] = yz2 + wx2
    t[:, 2, 2] = 1. - xx2 - yy2

    return t

def quat_vmult(q1, q2):
    # just assume q1 and q2 have same length
    mult = np.zeros((len(q1), 4))
    mult[:,0] =  q1[:,3] * q2[:,0] - q1[:,2] * q2[:,1] + q1[:,1] * q2[:,2] + q1[:,0] * q2[:,3]
    mult[:,1] =  q1[:,2] * q2[:,0] + q1[:,3] * q2[:,1] - q1[:,0] * q2[:,2] + q1[:,1] * q2[:,3]
    mult[:,2] = -q1[:,1] * q2[:,0] + q1[:,0] * q2[:,1] + q1[:,3] * q2[:,2] + q1[:,2] * q2[:,3]
    mult[:,3] = -q1[:,0] * q2[:,0] - q1[:,1] * q2[:,1] - q1[:,2] * q2[:,2] + q1[:,3] * q2[:,3]
    return mult

class CentroidResiduals(object):
    centroid_source = None
    att_source = None
    ra = None
    dec = None

    def set_centroids(self, start, stop, source, slot, alg=8):
        """
        Get centroids from ``source`` between ``start`` and ``stop``.

        One could also just set yags, yag_times, zags, zag_times
        attributes directly.

        :param start: start time
        :param stop: stop time
        :param source: 'ground' | 'obc'
        :param slot: ACA slot
        """
        self.centroid_source = source
        # Get centroids from Ska eng archive or mica L1 archive
        # Might want to add filtering for track status here too
        # Also need to include something for time offsets
        # And warn to console if we hit multiple obsids
        if source == 'ground':
            acen_files = sorted(asp_l1.get_files(start=start, stop=stop, content=['ACACENT']))
            acen = vstack([Table.read(f) for f in sorted(acen_files)], metadata_conflicts='silent')
            ok = ((acen['slot'] == slot) & (acen['alg'] == alg) & (acen['status'] == 0)
                  & (acen['time'] >= DateTime(start).secs) & (acen['time'] <= DateTime(stop).secs))
            yags = np.array(acen[ok]['ang_y'] * 3600)
            zags = np.array(acen[ok]['ang_z'] * 3600)
            yag_times = np.array(acen[ok]['time'])
            zag_times = np.array(acen[ok]['time'])
        elif source == 'obc':
            telem = fetch.Msidset(['AOACYAN{}'.format(slot), 'AOACZAN{}'.format(slot)], start, stop)
            # Filter centroids for reasonble-ness
            yok = telem['AOACYAN{}'.format(slot)].vals != -3276.8
            zok = telem['AOACZAN{}'.format(slot)].vals != -3276.8
            yags = telem['AOACYAN{}'.format(slot)].vals[yok]
            yag_times = telem['AOACYAN{}'.format(slot)].times[yok]
            zags = telem['AOACZAN{}'.format(slot)].vals[zok]
            zag_times = telem['AOACZAN{}'.format(slot)].times[zok]
        self.yags = yags
        self.yag_times = yag_times
        self.zags = zags
        self.zag_times = zag_times

    def set_atts(self, start, stop, source):
        """Get attitude solution quaternions from ``source`` between
        ``start`` and ``stop``

        One could also just set atts and att_times attributes directly.
        """
        self.att_source = source
        # Get attitudes and times
        if source == 'obc':
            # Need to figure out missing data
            telem = fetch.Msidset(['aoattqt*'], start, stop)
            atts = np.vstack([telem['aoattqt{}'.format(idx)].vals
                              for idx in [1, 2, 3, 4]]).transpose()
            att_times = telem['aoattqt1'].times
        if source == 'ground':
            asol_files = sorted(asp_l1.get_files(start=start, stop=stop, revision='last',
                                                 content=['ASPSOL']))
            acal_files = sorted(asp_l1.get_files(start=start, stop=stop, revision='last',
                                                 content=['ACACAL']))
            att_chunks = []
            time_chunks = []
            for f in asol_files:
                asol = Table.read(f)
                prefix = re.search("(.*)_asol1.*", f).group(1)
                # get the acal for each asol
                acal = None
                # There's probably a header to check for time ranges too, but use filenames to start
                for acf in acal_files:
                    if re.search("{}_acal1.*".format(prefix), acf):
                        acal = Table.read(acf)
                        break
                # Make a Nx4 list of the inv misalign quats
                q_mis_inv = np.repeat(Quat(acal['aca_misalign'][0]).inv().q,
                                      len(asol)).reshape((4, len(asol))).transpose()
                # Quaternion multiply the asol quats with that inv misalign and save
                # I could also do this with the transform matrix and then only need
                # one accessory quat function.
                att_chunks.append(quat_vmult(asol['q_att'], q_mis_inv))
                time_chunks.append(np.array(asol['time']))
            atts = np.vstack(att_chunks)
            att_times = np.hstack(time_chunks)
        self.atts = atts  # (N, 4) numpy array
        self.att_times = att_times

    def set_star(self, agasc_id=None, obsid=None, slot=None, date=None):
        """
        Set self.ra and dec from either agasc_id *or* obsid + slot.

        This assumes use of star in default agasc miniagasc (no 1.4 or 1.5 or very faint stars)

        Along the way set self.agasc_id.  One can also just set
        self.ra and dec directly.
        """
        if obsid is not None and slot is not None:
            if date is None:
                raise ValueError("Need date to look up star catalog if obsid/slot provided")
            sc = mica.starcheck.get_starcheck_catalog_at_date(date)
            star = sc['cat'][(sc['cat']['slot'] == slot)
                             & ((sc['cat']['type'] == 'GUI') | (sc['cat']['type'] == 'BOT'))]
            if not len(star):
                raise ValueError(
                    "No GUI or BOT in slot {} for obsid {} in dwell".format(slot, obsid))
            agasc_id = star['id'][0]
        # Could also add logic to infer star from loose position and magnitude
        star = agasc.get_star(agasc_id, date=date)
        self.agasc_id = agasc_id
        self.ra = star['RA_PMCORR']
        self.dec = star['DEC_PMCORR']

    @property
    def yags(self):
        return self._yags

    @yags.setter
    def yags(self, vals):
        if isinstance(vals, fetch.MSID):
            self._yags = np.array(vals.vals)
            self._yag_times = vals.times
        else:
            self._yags = np.array(vals)

    @property
    def yag_times(self):
        return self._yag_times

    @yag_times.setter
    def yag_times(self, vals):
        self._yag_times = np.array(vals)

    @property
    def zags(self):
        return self._zags

    @zags.setter
    def zags(self, vals):
        if isinstance(vals, fetch.MSID):
            self._zags = np.array(vals.vals)
            self._zag_times = vals.times
        else:
            self._zags = np.array(vals)

    @property
    def zag_times(self):
        return self._zag_times

    @zag_times.setter
    def zag_times(self, vals):
        self._zag_times = np.array(vals)

    def get_residuals(self):
        """
        Return dyags and dzags, corresponding to the centroid residuals
        for yags and zags respectively.  Note that the sampling and times
        of yags may be different from zags so these should be done
        independently.

        Probably the way to go is to generate predicted p_yags, p_zags
        for each available atts (using ra, dec) and then interpolate
        those to the respective yag/zag_times and compute delta.
        """
        eci = Ska.quatutil.radec2eci(self.ra, self.dec)
        # Transform the 3x3 to get the axes to align to have the dot product make sense
        d_aca = np.dot(quat_vtransform(self.atts).transpose(0, 2, 1), eci)
        p_yags = np.arctan2(d_aca[:, 1], d_aca[:, 0]) * R2A
        p_zags = np.arctan2(d_aca[:, 2], d_aca[:, 0]) * R2A
        dyags = self.yags - interpolate(p_yags, self.att_times, self.yag_times, sorted=True)
        dzags = self.zags - interpolate(p_zags, self.att_times, self.zag_times, sorted=True)
        return dyags, dzags

def get_obs_slot_residuals(obsid, slot, att_source='ground', centroid_source='ground'):
    ds = events.dwells.filter(obsid=obsid)
    start = ds[0].start
    stop = ds[len(ds) - 1].stop
    cr = CentroidResiduals()
    cr.set_atts(start, stop, att_source)
    cr.set_centroids(start, stop, centroid_source,  slot)
    cr.set_star(obsid=obsid, slot=slot, date=start)
    dyags, dzags = cr.get_residuals()
    return dyags, dzags
<commit_msg>Initialize CentroidResiduals object with start/stop<commit_after>import re
import numpy as np
from astropy.table import Table, vstack
from kadi import events
from Ska.Numpy import interpolate
from Quaternion import Quat
import Ska.quatutil
from mica.archive import asp_l1
import mica.starcheck
from Ska.engarchive import fetch
from Chandra.Time import DateTime
import agasc

R2A = 206264.81  # Convert from radians to arcsec

def quat_vtransform(qs):
    """
    Transform a Nx4 matrix of quaternions into a Nx3x3 transform matrix
    :returns: Nx3x3 transform matrix
    :rtype: numpy array
    """
    x, y, z, w = qs[:, 0], qs[:, 1], qs[:, 2], qs[:, 3]
    xx2 = x * x * 2.
    yy2 = y * y * 2.
    zz2 = z * z * 2.
    xy2 = x * y * 2.
    wz2 = w * z * 2.
    zx2 = z * x * 2.
    wy2 = w * y * 2.
    yz2 = y * z * 2.
    wx2 = w * x * 2.

    t = np.empty((len(qs), 3, 3), float)
    t[:, 0, 0] = 1. - yy2 - zz2
    t[:, 0, 1] = xy2 - wz2
    t[:, 0, 2] = zx2 + wy2
    t[:, 1, 0] = xy2 + wz2
    t[:, 1, 1] = 1. - xx2 - zz2
    t[:, 1, 2] = yz2 - wx2
    t[:, 2, 0] = zx2 - wy2
    t[:, 2, 1] = yz2 + wx2
    t[:, 2, 2] = 1. - xx2 - yy2

    return t

def quat_vmult(q1, q2):
    # just assume q1 and q2 have same length
    mult = np.zeros((len(q1), 4))
    mult[:,0] =  q1[:,3] * q2[:,0] - q1[:,2] * q2[:,1] + q1[:,1] * q2[:,2] + q1[:,0] * q2[:,3]
    mult[:,1] =  q1[:,2] * q2[:,0] + q1[:,3] * q2[:,1] - q1[:,0] * q2[:,2] + q1[:,1] * q2[:,3]
    mult[:,2] = -q1[:,1] * q2[:,0] + q1[:,0] * q2[:,1] + q1[:,3] * q2[:,2] + q1[:,2] * q2[:,3]
    mult[:,3] = -q1[:,0] * q2[:,0] - q1[:,1] * q2[:,1] - q1[:,2] * q2[:,2] + q1[:,3] * q2[:,3]
    return mult

class CentroidResiduals(object):
    centroid_source = None
    att_source = None
    ra = None
    dec = None

    def __init__(self, start, stop):
        self.start = start
        self.stop = stop

    def set_centroids(self, source, slot, alg=8):
        """
        Get centroids from ``source``.

        One could also just set yags, yag_times, zags, zag_times
        attributes directly.

        :param source: 'ground' | 'obc'
        :param slot: ACA slot
        """
        self.centroid_source = source
        start = self.start
        stop = self.stop
        # Get centroids from Ska eng archive or mica L1 archive
        # Might want to add filtering for track status here too
        # Also need to include something for time offsets
        # And warn to console if we hit multiple obsids
        if source == 'ground':
            acen_files = sorted(asp_l1.get_files(start=start, stop=stop, content=['ACACENT']))
            acen = vstack([Table.read(f) for f in sorted(acen_files)], metadata_conflicts='silent')
            ok = ((acen['slot'] == slot) & (acen['alg'] == alg) & (acen['status'] == 0)
                  & (acen['time'] >= DateTime(start).secs) & (acen['time'] <= DateTime(stop).secs))
            yags = np.array(acen[ok]['ang_y'] * 3600)
            zags = np.array(acen[ok]['ang_z'] * 3600)
            yag_times = np.array(acen[ok]['time'])
            zag_times = np.array(acen[ok]['time'])
        elif source == 'obc':
            telem = fetch.Msidset(['AOACYAN{}'.format(slot), 'AOACZAN{}'.format(slot)], start, stop)
            # Filter centroids for reasonble-ness
            yok = telem['AOACYAN{}'.format(slot)].vals != -3276.8
            zok = telem['AOACZAN{}'.format(slot)].vals != -3276.8
            yags = telem['AOACYAN{}'.format(slot)].vals[yok]
            yag_times = telem['AOACYAN{}'.format(slot)].times[yok]
            zags = telem['AOACZAN{}'.format(slot)].vals[zok]
            zag_times = telem['AOACZAN{}'.format(slot)].times[zok]
        self.yags = yags
        self.yag_times = yag_times
        self.zags = zags
        self.zag_times = zag_times

    def set_atts(self, source):
        """Get attitude solution quaternions from ``source``.

        One could also just set atts and att_times attributes directly.
        """
        self.att_source = source
        start = self.start
        stop = self.stop
        # Get attitudes and times
        if source == 'obc':
            # Need to figure out missing data
            telem = fetch.Msidset(['aoattqt*'], start, stop)
            atts = np.vstack([telem['aoattqt{}'.format(idx)].vals
                              for idx in [1, 2, 3, 4]]).transpose()
            att_times = telem['aoattqt1'].times
        if source == 'ground':
            asol_files = sorted(asp_l1.get_files(start=start, stop=stop, revision='last',
                                                 content=['ASPSOL']))
            acal_files = sorted(asp_l1.get_files(start=start, stop=stop, revision='last',
                                                 content=['ACACAL']))
            att_chunks = []
            time_chunks = []
            for f in asol_files:
                asol = Table.read(f)
                prefix = re.search("(.*)_asol1.*", f).group(1)
                # get the acal for each asol
                acal = None
                # There's probably a header to check for time ranges too, but use filenames to start
                for acf in acal_files:
                    if re.search("{}_acal1.*".format(prefix), acf):
                        acal = Table.read(acf)
                        break
                # Make a Nx4 list of the inv misalign quats
                q_mis_inv = np.repeat(Quat(acal['aca_misalign'][0]).inv().q,
                                      len(asol)).reshape((4, len(asol))).transpose()
                # Quaternion multiply the asol quats with that inv misalign and save
                # I could also do this with the transform matrix and then only need
                # one accessory quat function.
                att_chunks.append(quat_vmult(asol['q_att'], q_mis_inv))
                time_chunks.append(np.array(asol['time']))
            atts = np.vstack(att_chunks)
            att_times = np.hstack(time_chunks)
        self.atts = atts  # (N, 4) numpy array
        self.att_times = att_times

    def set_star(self, agasc_id=None, obsid=None, slot=None, date=None):
        """
        Set self.ra and dec from either agasc_id *or* obsid + slot.

        This assumes use of star in default agasc miniagasc (no 1.4 or 1.5 or very faint stars)

        Along the way set self.agasc_id.  One can also just set
        self.ra and dec directly.
        """
        if obsid is not None and slot is not None:
            if date is None:
                raise ValueError("Need date to look up star catalog if obsid/slot provided")
            sc = mica.starcheck.get_starcheck_catalog_at_date(date)
            star = sc['cat'][(sc['cat']['slot'] == slot)
                             & ((sc['cat']['type'] == 'GUI') | (sc['cat']['type'] == 'BOT'))]
            if not len(star):
                raise ValueError(
                    "No GUI or BOT in slot {} for obsid {} in dwell".format(slot, obsid))
            agasc_id = star['id'][0]
        # Could also add logic to infer star from loose position and magnitude
        star = agasc.get_star(agasc_id, date=date)
        self.agasc_id = agasc_id
        self.ra = star['RA_PMCORR']
        self.dec = star['DEC_PMCORR']

    @property
    def yags(self):
        return self._yags

    @yags.setter
    def yags(self, vals):
        if isinstance(vals, fetch.MSID):
            self._yags = np.array(vals.vals)
            self._yag_times = vals.times
        else:
            self._yags = np.array(vals)

    @property
    def yag_times(self):
        return self._yag_times

    @yag_times.setter
    def yag_times(self, vals):
        self._yag_times = np.array(vals)

    @property
    def zags(self):
        return self._zags

    @zags.setter
    def zags(self, vals):
        if isinstance(vals, fetch.MSID):
            self._zags = np.array(vals.vals)
            self._zag_times = vals.times
        else:
            self._zags = np.array(vals)

    @property
    def zag_times(self):
        return self._zag_times

    @zag_times.setter
    def zag_times(self, vals):
        self._zag_times = np.array(vals)

    def get_residuals(self):
        """
        Return dyags and dzags, corresponding to the centroid residuals
        for yags and zags respectively.  Note that the sampling and times
        of yags may be different from zags so these should be done
        independently.

        Probably the way to go is to generate predicted p_yags, p_zags
        for each available atts (using ra, dec) and then interpolate
        those to the respective yag/zag_times and compute delta.
        """
        eci = Ska.quatutil.radec2eci(self.ra, self.dec)
        # Transform the 3x3 to get the axes to align to have the dot product make sense
        d_aca = np.dot(quat_vtransform(self.atts).transpose(0, 2, 1), eci)
        p_yags = np.arctan2(d_aca[:, 1], d_aca[:, 0]) * R2A
        p_zags = np.arctan2(d_aca[:, 2], d_aca[:, 0]) * R2A
        self.dyags = self.yags - interpolate(p_yags, self.att_times, self.yag_times, sorted=True)
        self.dzags = self.zags - interpolate(p_zags, self.att_times, self.zag_times, sorted=True)
        return self.dyags, self.yag_times, self.dzags, self.zag_times

def get_obs_slot_residuals(obsid, slot, att_source='ground', centroid_source='ground'):
    ds = events.dwells.filter(obsid=obsid)
    start = ds[0].start
    stop = ds[len(ds) - 1].stop
    cr = CentroidResiduals(start, stop)
    cr.set_atts(att_source)
    cr.set_centroids(centroid_source,  slot)
    cr.set_star(obsid=obsid, slot=slot, date=start)
    dyags, yt, dzags, zt = cr.get_residuals()
    return dyags, yt, dzags, zt

<|endoftext|>


<commit_before>from __future__ import division

from math import sqrt, hypot
from bisect import bisect_left, bisect_right

class Spline(object):
    def __init__(self, p0, p1, p2, p3, N = 100):
        """ Create a spline given the start point,
        two control points and the end point"""
        assert not (p0 == p1 == p2 == p3)
        self.points = (p0, p1, p2, p3)
        self.N = N
        self.sampled = False
        self._functionals = None

    def calculate_functionals(self):
        # calculate functional parameters of the spline
        p = self.points
        A = p[3][0] - 3*p[2][0] + 3*p[1][0] - p[0][0]
        B = 3*p[2][0] - 6*p[1][0] + 3*p[0][0]
        C = 3*p[1][0] - 3*p[0][0]
        D = p[0][0]
        E = p[3][1] - 3*p[2][1] + 3*p[1][1] - p[0][1]
        F = 3*p[2][1] - 6*p[1][1] + 3*p[0][1]
        G = 3*p[1][1] - 3*p[0][1]
        H = p[0][1]
        self._functionals = A, B, C, D, E, F, G, H

    @property
    def functionals(self):
        if not self._functionals:
            self.calculate_functionals()
        return self._functionals

    def get_point(self, t):
        A, B, C, D, E, F, G, H = self.functionals
        x = (((A*t) + B)*t + C)*t + D
        y = (((E*t) + F)*t + G)*t + H
        return x, y

    def get_point_perp(self, t):
        A, B, C, D, E, F, G, H = self.functionals
        x = (((A*t) + B)*t + C)*t + D
        y = (((E*t) + F)*t + G)*t + H
        dx = 3*A*t**2 + 2*B*t + C
        dy = 3*E*t**2 + 2*F*t + G
        d = sqrt(dx**2 + dy**2)
        if d/self.length < 1e-8:
            if t < 0.5:
                #print "t = ", t, "increased"
                return self.get_point_perp(t + 0.001)
            else:
                #print "t = ", t, "decreased"
                return self.get_point_perp(t - 0.001)
        return x, y, -dy/d, dx/d

    def get_point_tan_perp(self, t):
        A, B, C, D, E, F, G, H = self.functionals
        x = (((A*t) + B)*t + C)*t + D
        y = (((E*t) + F)*t + G)*t + H
        dx = 3*A*t**2 + 2*B*t + C
        dy = 3*E*t**2 + 2*F*t + G
        d = sqrt(dx**2 + dy**2)
        if d/self.length < 1e-8:
            if t < 0.5:
                #print "t = ", t, "increased"
                return self.get_point_tan_perp(t + 0.001)
            else:
                #print "t = ", t, "decreased"
                return self.get_point_tan_perp(t - 0.001)
        return x, y, dx/d, dy/d, -dy/d, dx/d

    @property
    def length(self):
        if not self.sampled:
            self.sample_path()
        return self._length

    def sample_path(self, N = None):
        if not N:
            N = self.N
        else:
            self.N = N
        self.samples = [self.get_point(x / N) for x in range(N + 1)]
        self.distances = [hypot(p1[0] - p2[0], p1[1] - p2[1])
                          for p1, p2 in zip(self.samples[:-1], self.samples[1:])]
        self._length = sum(self.distances)
        cum = self.cumulative = [0]
        for dist in self.distances:
            cum.append(cum[-1] + dist)
        self.sampled = True

    def get_t(self, s):
        s = min(1, max(0, s))
        s *= self.length
        i = bisect_left(self.cumulative, s)
        if i == 0:
            i = 1
        part = (s - self.cumulative[i]) / self.distances[i-1]
        return (i*1.0 + part)/self.N

    def transform_point(self, ip):
        """transform a point according to the spline trafo
        x is length along the spline
        y is across and in the same units as x"""
        ix, iy = ip
        t = self.get_t(ix/self.length)
        x, y, px, py = self.get_point_perp(t)
        return x + px*iy, y + py*iy

    def transform_x_point(self, ix, pt):
        """transform a point pt according to the spline trafo at splinepoint x"""
        t = self.get_t(ix/self.length)
        x, y, dx, dy, px, py = self.get_point_tan_perp(t)
        return x + dx*(pt[0]-ix) + px*pt[1], y + dy*(pt[0]-ix) + py*pt[1]

    def transform_spline(self, spline):
        s0T = self.transform_point(spline.points[0])
        s3T = self.transform_point(spline.points[3])
        s1T = self.transform_x_point(spline.points[0][0],spline.points[1])
        s2T = self.transform_x_point(spline.points[3][0],spline.points[2])
        return Spline(s0T, s1T, s2T, s3T)

    @property
    def svg_path_data(self):
        return ("M %.5f %.5f C " + "%.f "*6) % reduce(tuple.__add__, self.points)

    def __str__(self):
        return "spline; start %s; control points %s; %s; end %s" % self.points

class SplineLine(object):
    def __init__(self, splines):
        self.splines = splines
        self.cumulative = None

    def cumulate(self):
        cum = self.cumulative = [0]
        for spline in self.splines:
            cum.append(cum[-1] + spline.length)

    def find_spline_at(self, s):
        if not self.cumulative:
            self.cumulate()
        i = bisect_left(self.cumulative, s) - 1
        i = max(0, min(len(self.splines) - 1, i))
        return self.splines[i], s - self.cumulative[i], self.cumulative[i]

    def transform_point(self, ip):
        spline, x, xleft = self.find_spline_at(ip[0])
        return spline.transform_point((x, ip[1]))

    def transform_x_point(self, ix, pt):
        spline, x, xleft = self.find_spline_at(ix)
        return spline.transform_x_point(x, (pt[0] - xleft, pt[1]))

    def transform_spline(self, spline):
        s0T = self.transform_point(spline.points[0])
        s3T = self.transform_point(spline.points[3])
        s1T = self.transform_x_point(spline.points[0][0],spline.points[1])
        s2T = self.transform_x_point(spline.points[3][0],spline.points[2])
        return Spline(s0T, s1T, s2T, s3T)

    @property
    def length(self):
        return sum(s.length for s in self.splines)

    @property
    def svg_path_data(self):
        return " ".join(s.svg_path_data for s in self.splines)

    def __str__(self):
        start, end = self.splines[0].points[0], self.splines[-1].points[-1]
        return "splineline; start %s; end %s" % (start, end)

def Line(Spline):
    def __init__(self, p0, p1):
        super(Line, self).__init__(p0, p0, p1, p1)

if __name__=="__main__":
    s = Spline(5.0, -10, 20.000, -10, 20.0, 10.000, 40.0, 10.000)
    lx, ly = 0, 0
    for i in range(500):
        t = s.get_t(i / 500)
        x, y, px, py = s.get_point_perp(t)
        if lx == 0 and ly == 0:
            lx, ly = x, y
        print t, x,y, px, py, hypot(x - lx, y - ly)
        lx, ly = x, y

<commit_msg>added 'clipping', you can now clip some length off the end of a spline<commit_after>from __future__ import division

from math import sqrt, hypot
from bisect import bisect_left, bisect_right

class Spline(object):
    def __init__(self, p0, p1, p2, p3, N = 100):
        """ Create a spline given the start point,
        two control points and the end point"""
        assert not (p0 == p1 == p2 == p3)
        self.points = (p0, p1, p2, p3)
        self.N = N
        self.sampled = False
        self._functionals = None

    def calculate_functionals(self):
        # calculate functional parameters of the spline
        p = self.points
        A = p[3][0] - 3*p[2][0] + 3*p[1][0] - p[0][0]
        B = 3*p[2][0] - 6*p[1][0] + 3*p[0][0]
        C = 3*p[1][0] - 3*p[0][0]
        D = p[0][0]
        E = p[3][1] - 3*p[2][1] + 3*p[1][1] - p[0][1]
        F = 3*p[2][1] - 6*p[1][1] + 3*p[0][1]
        G = 3*p[1][1] - 3*p[0][1]
        H = p[0][1]
        self._functionals = A, B, C, D, E, F, G, H

    @property
    def functionals(self):
        if not self._functionals:
            self.calculate_functionals()
        return self._functionals

    def get_point(self, t):
        A, B, C, D, E, F, G, H = self.functionals
        x = (((A*t) + B)*t + C)*t + D
        y = (((E*t) + F)*t + G)*t + H
        return x, y

    def get_point_perp(self, t):
        A, B, C, D, E, F, G, H = self.functionals
        x = (((A*t) + B)*t + C)*t + D
        y = (((E*t) + F)*t + G)*t + H
        dx = 3*A*t**2 + 2*B*t + C
        dy = 3*E*t**2 + 2*F*t + G
        d = sqrt(dx**2 + dy**2)
        if d/self.length < 1e-8:
            if t < 0.5:
                #print "t = ", t, "increased"
                return self.get_point_perp(t + 0.001)
            else:
                #print "t = ", t, "decreased"
                return self.get_point_perp(t - 0.001)
        return x, y, -dy/d, dx/d

    def get_point_tan_perp(self, t):
        A, B, C, D, E, F, G, H = self.functionals
        x = (((A*t) + B)*t + C)*t + D
        y = (((E*t) + F)*t + G)*t + H
        dx = 3*A*t**2 + 2*B*t + C
        dy = 3*E*t**2 + 2*F*t + G
        d = sqrt(dx**2 + dy**2)
        if d/self.length < 1e-8:
            if t < 0.5:
                #print "t = ", t, "increased"
                return self.get_point_tan_perp(t + 0.001)
            else:
                #print "t = ", t, "decreased"
                return self.get_point_tan_perp(t - 0.001)
        return x, y, dx/d, dy/d, -dy/d, dx/d

    @property
    def length(self):
        if not self.sampled:
            self.sample_path()
        return self._length

    def sample_path(self, N = None):
        if not N:
            N = self.N
        else:
            self.N = N
        self.samples = [self.get_point(x / N) for x in range(N + 1)]
        self.distances = [hypot(p1[0] - p2[0], p1[1] - p2[1])
                          for p1, p2 in zip(self.samples[:-1], self.samples[1:])]
        self._length = sum(self.distances)
        cum = self.cumulative = [0]
        for dist in self.distances:
            cum.append(cum[-1] + dist)
        self.sampled = True

    def get_t(self, s):
        s = min(1, max(0, s))
        s *= self.length
        i = bisect_left(self.cumulative, s)
        if i == 0:
            i = 1
        part = (s - self.cumulative[i]) / self.distances[i-1]
        return (i*1.0 + part)/self.N

    def transform_point(self, ip):
        """transform a point according to the spline trafo
        x is length along the spline
        y is across and in the same units as x"""
        ix, iy = ip
        t = self.get_t(ix/self.length)
        x, y, px, py = self.get_point_perp(t)
        return x + px*iy, y + py*iy

    def transform_x_point(self, ix, pt):
        """transform a point pt according to the spline trafo at splinepoint x"""
        t = self.get_t(ix/self.length)
        x, y, dx, dy, px, py = self.get_point_tan_perp(t)
        return x + dx*(pt[0]-ix) + px*pt[1], y + dy*(pt[0]-ix) + py*pt[1]

    def transform_spline(self, spline):
        s0T = self.transform_point(spline.points[0])
        s3T = self.transform_point(spline.points[3])
        s1T = self.transform_x_point(spline.points[0][0],spline.points[1])
        s2T = self.transform_x_point(spline.points[3][0],spline.points[2])
        return Spline(s0T, s1T, s2T, s3T)

    def get_clipped(self, clip_length):
        x = self.length - clip_length
        p0, p1 = self.points[0], self.points[1]
        p2 = list(self.points[2])
        p3 = self.transform_point((x, 0))
        for i in (0,1):
            p2[i] += p3[i] - self.points[3][i]
        return Spline(p0, p1, tuple(p2), p3, self.N)

    @property
    def svg_path_data(self):
        return ("M %.5f %.5f C " + "%.f "*6) % reduce(tuple.__add__, self.points)

    def __str__(self):
        return "spline; start %s; control points %s; %s; end %s" % self.points

class SplineLine(object):
    def __init__(self, splines):
        self.splines = splines
        self.cumulative = None

    def cumulate(self):
        cum = self.cumulative = [0]
        for spline in self.splines:
            cum.append(cum[-1] + spline.length)

    def find_spline_at(self, s):
        if not self.cumulative:
            self.cumulate()
        i = bisect_left(self.cumulative, s) - 1
        i = max(0, min(len(self.splines) - 1, i))
        return self.splines[i], s - self.cumulative[i], self.cumulative[i]

    def transform_point(self, ip):
        spline, x, xleft = self.find_spline_at(ip[0])
        return spline.transform_point((x, ip[1]))

    def transform_x_point(self, ix, pt):
        spline, x, xleft = self.find_spline_at(ix)
        return spline.transform_x_point(x, (pt[0] - xleft, pt[1]))

    def transform_spline(self, spline):
        s0T = self.transform_point(spline.points[0])
        s3T = self.transform_point(spline.points[3])
        s1T = self.transform_x_point(spline.points[0][0],spline.points[1])
        s2T = self.transform_x_point(spline.points[3][0],spline.points[2])
        return Spline(s0T, s1T, s2T, s3T)

    @property
    def length(self):
        return sum(s.length for s in self.splines)

    @property
    def svg_path_data(self):
        return " ".join(s.svg_path_data for s in self.splines)

    def __str__(self):
        start, end = self.splines[0].points[0], self.splines[-1].points[-1]
        return "splineline; start %s; end %s" % (start, end)

def Line(Spline):
    def __init__(self, p0, p1):
        super(Line, self).__init__(p0, p0, p1, p1)

if __name__=="__main__":
    s = Spline(5.0, -10, 20.000, -10, 20.0, 10.000, 40.0, 10.000)
    lx, ly = 0, 0
    for i in range(500):
        t = s.get_t(i / 500)
        x, y, px, py = s.get_point_perp(t)
        if lx == 0 and ly == 0:
            lx, ly = x, y
        print t, x,y, px, py, hypot(x - lx, y - ly)
        lx, ly = x, y

<|endoftext|>


<commit_before>from __future__ import print_function

import os
import sys
import time

class Logger(object):
    """ Abstract class for all logger implementations.

    Concrete classes will log messages using various methods,
    e.g. write to a file.
    """

    (ERROR, INFO, DEBUG) = (0, 1, 2)
    TYPES = ("ERROR", "Info", "Debug")
    debug_level = ERROR

    def __init__(self, debug_level):
        self.debug_level = int(debug_level)

    def log(self, string, level):
        """ Log a message """
        if level > self.debug_level:
            return
        self._actual_log(string, level)

    def _actual_log(self, string, level):
        """ Actually perform the logging (to be implemented in subclasses) """
        pass

    def shutdown(self):
        """ Action to perform when closing the logger """
        pass

    @staticmethod
    def time():
        """ Get a nicely formatted time string """
        return time.strftime("%a %d %Y %H:%M:%S", time.localtime())

    def format(self, string, level):
        """ Format the error message in a standard way """
        display_level = self.TYPES[level]
        return "- [%s] {%s} %s" % (display_level, self.time(), string)

class WindowLogger(Logger):

    """ Log messages to a window.

    The window object is passed in on construction, but
    only created if a message is written.
    """

    def __init__(self, debug_level, window):
        self.window = window
        super(WindowLogger, self).__init__(debug_level)

    def shutdown(self):
        if self.window is not None:
            self.window.is_open = False

    def _actual_log(self, string, level):
        if not self.window.is_open:
            self.window.create("rightbelow 6new")
        self.window.write(self.format(string, level)+"\n")

class FileLogger(Logger):

    """ Log messages to a window.

    The window object is passed in on construction, but
    only created if a message is written.
    """

    def __init__(self, debug_level, filename):
        self.filename = os.path.expanduser(filename)
        self.f = None
        super(FileLogger, self).__init__(debug_level)

    def __open(self):
        try:
            self.f = open(self.filename, 'w')
        except IOError as e:
            raise error.LogError("Invalid file name '%s' for log file: %s"
                                 % (self.filename, e))
        except:
            raise error.LogError("Error using file '%s' as a log file: %s"
                                 % (self.filename, sys.exc_info()[0]))

    def shutdown(self):
        if self.f is not None:
            self.f.close()

    def _actual_log(self, string, level):
        if self.f is None:
            self.__open()
        self.f.write(self.format(string, level)+"\n")
        self.f.flush()

class Log:

    loggers = {}

    def __init__(self, string, level=Logger.INFO):
        Log.log(string, level)

    @classmethod
    def log(cls, string, level=Logger.INFO):
        for logger in cls.loggers.values():
            logger.log(string, level)

    @classmethod
    def set_logger(cls, logger):
        k = logger.__class__.__name__
        if k in cls.loggers:
            cls.loggers[k].shutdown()
        cls.loggers[k] = logger

    @classmethod
    def remove_logger(cls, type):
        if type in cls.loggers:
            cls.loggers[type].shutdown()
            return True
        print("Failed to find logger %s in list of loggers" % type)
        return False

    @classmethod
    def shutdown(cls):
        for logger in cls.loggers.values():
            logger.shutdown()
        cls.loggers = {}
<commit_msg>Fix missing import<commit_after>from __future__ import print_function

import os
import sys
import time

from . import error

class Logger(object):
    """ Abstract class for all logger implementations.

    Concrete classes will log messages using various methods,
    e.g. write to a file.
    """

    (ERROR, INFO, DEBUG) = (0, 1, 2)
    TYPES = ("ERROR", "Info", "Debug")
    debug_level = ERROR

    def __init__(self, debug_level):
        self.debug_level = int(debug_level)

    def log(self, string, level):
        """ Log a message """
        if level > self.debug_level:
            return
        self._actual_log(string, level)

    def _actual_log(self, string, level):
        """ Actually perform the logging (to be implemented in subclasses) """
        pass

    def shutdown(self):
        """ Action to perform when closing the logger """
        pass

    @staticmethod
    def time():
        """ Get a nicely formatted time string """
        return time.strftime("%a %d %Y %H:%M:%S", time.localtime())

    def format(self, string, level):
        """ Format the error message in a standard way """
        display_level = self.TYPES[level]
        return "- [%s] {%s} %s" % (display_level, self.time(), string)

class WindowLogger(Logger):

    """ Log messages to a window.

    The window object is passed in on construction, but
    only created if a message is written.
    """

    def __init__(self, debug_level, window):
        self.window = window
        super(WindowLogger, self).__init__(debug_level)

    def shutdown(self):
        if self.window is not None:
            self.window.is_open = False

    def _actual_log(self, string, level):
        if not self.window.is_open:
            self.window.create("rightbelow 6new")
        self.window.write(self.format(string, level)+"\n")

class FileLogger(Logger):

    """ Log messages to a window.

    The window object is passed in on construction, but
    only created if a message is written.
    """

    def __init__(self, debug_level, filename):
        self.filename = os.path.expanduser(filename)
        self.f = None
        super(FileLogger, self).__init__(debug_level)

    def __open(self):
        try:
            self.f = open(self.filename, 'w')
        except IOError as e:
            raise error.LogError("Invalid file name '%s' for log file: %s"
                                 % (self.filename, e))
        except:
            raise error.LogError("Error using file '%s' as a log file: %s"
                                 % (self.filename, sys.exc_info()[0]))

    def shutdown(self):
        if self.f is not None:
            self.f.close()

    def _actual_log(self, string, level):
        if self.f is None:
            self.__open()
        self.f.write(self.format(string, level)+"\n")
        self.f.flush()

class Log:

    loggers = {}

    def __init__(self, string, level=Logger.INFO):
        Log.log(string, level)

    @classmethod
    def log(cls, string, level=Logger.INFO):
        for logger in cls.loggers.values():
            logger.log(string, level)

    @classmethod
    def set_logger(cls, logger):
        k = logger.__class__.__name__
        if k in cls.loggers:
            cls.loggers[k].shutdown()
        cls.loggers[k] = logger

    @classmethod
    def remove_logger(cls, type):
        if type in cls.loggers:
            cls.loggers[type].shutdown()
            return True
        print("Failed to find logger %s in list of loggers" % type)
        return False

    @classmethod
    def shutdown(cls):
        for logger in cls.loggers.values():
            logger.shutdown()
        cls.loggers = {}
<|endoftext|>


<commit_before>import numpy as np
import warnings

from astropy.table import Table, vstack
from kadi import events
from Ska.Numpy import interpolate
import Ska.quatutil
from mica.archive import asp_l1
import mica.starcheck
from Ska.engarchive import fetch
from Chandra.Time import DateTime
import agasc

R2A = 206264.81  # Convert from radians to arcsec

def quat_vtransform(qs):
    """
    Transform a Nx4 matrix of quaternions into a Nx3x3 transform matrix
    :returns: Nx3x3 transform matrix
    :rtype: numpy array
    """
    x, y, z, w = qs[:, 0], qs[:, 1], qs[:, 2], qs[:, 3]
    xx2 = x * x * 2.
    yy2 = y * y * 2.
    zz2 = z * z * 2.
    xy2 = x * y * 2.
    wz2 = w * z * 2.
    zx2 = z * x * 2.
    wy2 = w * y * 2.
    yz2 = y * z * 2.
    wx2 = w * x * 2.

    t = np.empty((len(qs), 3, 3), float)
    t[:, 0, 0] = 1. - yy2 - zz2
    t[:, 0, 1] = xy2 - wz2
    t[:, 0, 2] = zx2 + wy2
    t[:, 1, 0] = xy2 + wz2
    t[:, 1, 1] = 1. - xx2 - zz2
    t[:, 1, 2] = yz2 - wx2
    t[:, 2, 0] = zx2 - wy2
    t[:, 2, 1] = yz2 + wx2
    t[:, 2, 2] = 1. - xx2 - yy2

    return t

class CentroidResiduals(object):
    centroid_source = None
    att_source = None
    ra = None
    dec = None
    centroid_dt = None
    obsid = None

    def __init__(self, start, stop):
        self.start = start
        self.stop = stop

    def set_centroids(self, source, slot, alg=8, apply_dt=True):
        """
        Assign centroids from ``source`` and ``slot`` to the objects centroid attributes
        (yag, zag, yag_times, zag_times)

        For the supported sources (ground, obc) the centroids are fetched from the mica L1
        archive or telemetry.

        :param source: 'ground' | 'obc'
        :param slot: ACA slot
        :param alg: for ground processing, use centroids from this algorithm.
        :param apply_dt: apply centroid time offsets via 'set_offsets'
        """
        self.centroid_source = source
        self.centroid_dt = None
        start = self.start
        stop = self.stop
        # Get centroids from Ska eng archive or mica L1 archive
        # Might want to add filtering for track status here too
        # Also need to include something for time offsets
        # And warn to console if we hit multiple obsids
        if source == 'ground':
            acen_files = sorted(asp_l1.get_files(start=start, stop=stop, content=['ACACENT']))
            acen = vstack([Table.read(f) for f in sorted(acen_files)], metadata_conflicts='silent')
            ok = ((acen['slot'] == slot) & (acen['alg'] == alg) & (acen['status'] == 0)
                  & (acen['time'] >= DateTime(start).secs) & (acen['time'] <= DateTime(stop).secs))
            yags = np.array(acen[ok]['ang_y'] * 3600)
            zags = np.array(acen[ok]['ang_z'] * 3600)
            yag_times = np.array(acen[ok]['time'])
            zag_times = np.array(acen[ok]['time'])
        elif source == 'obc':
            telem = fetch.Msidset(['AOACYAN{}'.format(slot), 'AOACZAN{}'.format(slot)], start, stop)
            # Filter centroids for reasonble-ness
            yok = telem['AOACYAN{}'.format(slot)].vals > -3276
            zok = telem['AOACZAN{}'.format(slot)].vals > -3276
            yags = telem['AOACYAN{}'.format(slot)].vals[yok]
            yag_times = telem['AOACYAN{}'.format(slot)].times[yok]
            zags = telem['AOACZAN{}'.format(slot)].vals[zok]
            zag_times = telem['AOACZAN{}'.format(slot)].times[zok]
        else:
            raise ValueError("centroid_source must be 'obc' or 'ground'")
        self.yags = yags
        self.yag_times = yag_times
        self.zags = zags
        self.zag_times = zag_times
        if apply_dt is True:
            self.set_offsets()
        else:
            self.centroid_dt = 0

    def set_atts(self, source):
        """Get attitude solution quaternions from ``source``.

        One could also just set atts and att_times attributes directly.
        """
        self.att_source = source
        tstart = DateTime(self.start).secs
        tstop = DateTime(self.stop).secs
        # Get attitudes and times
        if source == 'obc':
            telem = fetch.Msidset(['aoattqt*'], tstart, tstop)
            atts = np.vstack([telem['aoattqt{}'.format(idx)].vals
                              for idx in [1, 2, 3, 4]]).transpose()
            att_times = telem['aoattqt1'].times
            # Fetch COBSQID at beginning and end of interval, check they match, and defined obsid
            if self.obsid is None:
                obsid_start = fetch.Msid('COBSRQID', tstart, tstart + 60)
                obsid_stop = fetch.Msid('COBSRQID', tstop - 60, tstop)
                if len(obsid_start.vals) == 0 or len(obsid_stop.vals) == 0:
                    raise ValueError(
                        "Error getting COBSRQID telem for tstart:{} tstop:{} from fetch_source:{}".format(
                            tstart, tstop, fetch.data_source.sources()[0]))
                if obsid_start.vals[-1] != obsid_stop.vals[0]:
                    raise ValueError("Time range covers more than one obsid; Not supported.")
                self.obsid = obsid_start.vals[-1]
        elif source == 'ground':
            atts, att_times, asol_recs = asp_l1.get_atts(start=tstart, stop=tstop)
            obsids = np.unique(np.array([int(rec['OBS_ID']) for rec in asol_recs]))
            if len(obsids) > 1:
                raise ValueError("Time range covers more than one obsid; Not supported.")
            self.obsid = obsids[0]
        else:
            raise ValueError("att_source must be 'obc' or 'ground'")
        ok = (att_times >= tstart) & (att_times < tstop)
        self.atts = atts[ok, :]  # (N, 4) numpy array
        self.att_times = att_times[ok]

    def set_atts_from_solfiles(self, asol_files, acal_files, aqual_files, filter=True):
        atts, att_times, asol_recs = asp_l1.get_atts_from_files(asol_files, acal_files, aqual_files, filter=filter)
        obsids = np.unique(np.array([int(rec['OBS_ID']) for rec in asol_recs]))
        if len(obsids) > 1:
            raise ValueError("Time range covers more than one obsid; Not supported.")
        self.atts = atts
        self.att_times = att_times

    def set_star(self, agasc_id=None, slot=None):
        """
        Set self.ra and dec from either agasc_id *or* slot.

        This assumes use of star in default agasc miniagasc (no 1.4 or 1.5 or very faint stars)
        Lookup by "slot" relies on database of starcheck catalogs.

        Along the way set self.agasc_id.  One can also just set
        self.ra and dec directly.
        """
        if agasc_id is not None:
            star = agasc.get_star(agasc_id, date=self.start)
        elif slot is not None:
            sc = mica.starcheck.get_starcheck_catalog_at_date(self.start)
            stars = sc['cat'][(sc['cat']['slot'] == slot)
                             & ((sc['cat']['type'] == 'GUI') | (sc['cat']['type'] == 'BOT'))]
            if not len(stars):
                raise ValueError(
                    "No GUI or BOT in slot {} at time {} in dwell".format(slot, DateTime(self.start).date))
            star = agasc.get_star(stars[0]['id'], date=self.start)
        else:
            raise ValueError("Need to supply agasc_id or slot to look up star")

        # Could also add logic to infer star from loose position and magnitude
        self.agasc_id = star['AGASC_ID']
        self.ra = star['RA_PMCORR']
        self.dec = star['DEC_PMCORR']

    @property
    def yags(self):
        return self._yags

    @yags.setter
    def yags(self, vals):
        if isinstance(vals, fetch.MSID):
            self._yags = np.array(vals.vals)
            self._yag_times = vals.times
        else:
            self._yags = np.array(vals)

    @property
    def yag_times(self):
        return self._yag_times

    @yag_times.setter
    def yag_times(self, vals):
        self._yag_times = np.array(vals)

    @property
    def zags(self):
        return self._zags

    @zags.setter
    def zags(self, vals):
        if isinstance(vals, fetch.MSID):
            self._zags = np.array(vals.vals)
            self._zag_times = vals.times
        else:
            self._zags = np.array(vals)

    @property
    def zag_times(self):
        return self._zag_times

    @zag_times.setter
    def zag_times(self, vals):
        self._zag_times = np.array(vals)

    def set_offsets(self):
        """
        Apply time offsets to centroids based on type and source of centroid, obsid
        (suggesting 8x8 or 6x6 data), telemetry source ('maude' or 'cxc') and aspect solution source.
        These time offsets were fit.  See fit notebooks at:

        http://nbviewer.jupyter.org/url/cxc.harvard.edu/mta/ASPECT/ipynb/centroid_time_offsets/OR.ipynb

        and

        http://nbviewer.jupyter.org/url/cxc.harvard.edu/mta/ASPECT/ipynb/centroid_time_offsets/ER.ipynb

        """
        # If already applied, do nothing
        if self.centroid_dt is not None:
            return
        if self.att_source is None or self.centroid_source is None:
            return
        # Get and check reasonable-ness of fetch data source
        if len(fetch.data_source.sources()) > 1:
            warnings.warn("Can't set offsets based on fetch data source if multiple data sources set")
            return
        fetch_source = fetch.data_source.sources()[0]
        if fetch_source != 'cxc' and fetch_source != 'maude':
            warnings.warn("Only maude and cxc fetch data sources are supported for offsets. Not applying offsets.")
            return
        obstype = 'or' if self.obsid < 38000 else 'er'
        if fetch_source == 'maude' and obstype == 'er':
            warnings.warn("Centroid time offsets not well fit for 'maude' telem source on ERs. Use caution.")

        # Offsets calculated using OR and ER notebooks in SKA/analysis/centroid_and_sol_time_offsets
        offsets = {
            # (centroid_source, att_source, fetch_source, obstype):  median offset in time
            ('obc', 'obc', 'cxc', 'or'): -2.45523126997,
            ('obc', 'ground', 'cxc', 'or'): -2.46900481785,
            ('ground', 'obc', 'cxc', 'or'): 0.0366092437236,
            ('ground', 'ground', 'cxc', 'or'): 0.0553306628318,
            ('obc', 'obc', 'maude', 'or'): -2.96746879688,
            ('obc', 'ground', 'maude', 'or'): -2.94076404877,
            ('ground', 'obc', 'maude', 'or'): 0.010515586472,
            ('obc', 'obc', 'cxc', 'er'): -2.53954270068,
            ('obc', 'ground', 'cxc', 'er'): -2.49080106675,
            ('ground', 'obc', 'cxc', 'er'): -0.0322463030744,
            ('ground', 'ground', 'cxc', 'er'): 0.0355677462107,
            ('obc', 'obc', 'maude', 'er'): -2.90454699136,
            ('obc', 'ground', 'maude', 'er'): -3.00151559564,
            ('ground', 'obc', 'maude', 'er'): 0.116096583881,
            }

        self.centroid_dt = offsets[(self.centroid_source,
                                    self.att_source,
                                    fetch_source,
                                    obstype)]

        self.yag_times = self.yag_times + self.centroid_dt
        self.zag_times = self.zag_times + self.centroid_dt

    def calc_residuals(self):
        """
        Calculate residuals based on attitude and ra/dec of star.  Note that the sampling and times
        of yags may be different from zags so these should be done independently.

        Residuals are available in self.dyags and self.dzags.
        Predicted values from attitude and star position in self.pred_yags and self.pred_zags

        """
        # If time offsets weren't applied because centroids were initialized before atts, try again
        if self.centroid_dt is None:
            self.set_offsets()
        # If still not set, warn
        if self.centroid_dt is None:
            warnings.warn("Residuals calculated on centroids without time offsets applied")
        if len(self.att_times) < 2:
            raise ValueError("Cannot attempt to calculate residuals with fewer than 2 attitude samples")
        eci = Ska.quatutil.radec2eci(self.ra, self.dec)
        # Transform the 3x3 to get the axes to align to have the dot product make sense
        d_aca = np.dot(quat_vtransform(self.atts).transpose(0, 2, 1), eci)
        p_yags = np.arctan2(d_aca[:, 1], d_aca[:, 0]) * R2A
        p_zags = np.arctan2(d_aca[:, 2], d_aca[:, 0]) * R2A
        self.pred_yags = interpolate(p_yags, self.att_times, self.yag_times, sorted=True)
        self.pred_zags = interpolate(p_zags, self.att_times, self.zag_times, sorted=True)
        self.dyags = self.yags - self.pred_yags
        self.dzags = self.zags - self.pred_zags

    @classmethod
    def for_slot(cls, obsid=None, start=None, stop=None,
                 slot=None, att_source='ground', centroid_source='ground'):
        if obsid is not None:
            if start is not None or stop is not None:
                raise ValueError('cannot specify both obsid and start / stop')
            ds = events.dwells.filter(obsid=obsid)
            start = ds[0].start
            stop = ds[len(ds) - 1].stop
        if start is None or stop is None:
            raise ValueError('must specify obsid or start / stop')
        cr = cls(start, stop)
        if obsid is not None:
            cr.obsid = obsid
        cr.set_atts(att_source)
        cr.set_centroids(centroid_source, slot)
        cr.set_star(slot=slot)
        cr.calc_residuals()  # instead of get_residuals
        return cr
<commit_msg>Remove some comments that don't seem to apply now<commit_after>import numpy as np
import warnings

from astropy.table import Table, vstack
from kadi import events
from Ska.Numpy import interpolate
import Ska.quatutil
from mica.archive import asp_l1
import mica.starcheck
from Ska.engarchive import fetch
from Chandra.Time import DateTime
import agasc

R2A = 206264.81  # Convert from radians to arcsec

def quat_vtransform(qs):
    """
    Transform a Nx4 matrix of quaternions into a Nx3x3 transform matrix
    :returns: Nx3x3 transform matrix
    :rtype: numpy array
    """
    x, y, z, w = qs[:, 0], qs[:, 1], qs[:, 2], qs[:, 3]
    xx2 = x * x * 2.
    yy2 = y * y * 2.
    zz2 = z * z * 2.
    xy2 = x * y * 2.
    wz2 = w * z * 2.
    zx2 = z * x * 2.
    wy2 = w * y * 2.
    yz2 = y * z * 2.
    wx2 = w * x * 2.

    t = np.empty((len(qs), 3, 3), float)
    t[:, 0, 0] = 1. - yy2 - zz2
    t[:, 0, 1] = xy2 - wz2
    t[:, 0, 2] = zx2 + wy2
    t[:, 1, 0] = xy2 + wz2
    t[:, 1, 1] = 1. - xx2 - zz2
    t[:, 1, 2] = yz2 - wx2
    t[:, 2, 0] = zx2 - wy2
    t[:, 2, 1] = yz2 + wx2
    t[:, 2, 2] = 1. - xx2 - yy2

    return t

class CentroidResiduals(object):
    centroid_source = None
    att_source = None
    ra = None
    dec = None
    centroid_dt = None
    obsid = None

    def __init__(self, start, stop):
        self.start = start
        self.stop = stop

    def set_centroids(self, source, slot, alg=8, apply_dt=True):
        """
        Assign centroids from ``source`` and ``slot`` to the objects centroid attributes
        (yag, zag, yag_times, zag_times)

        For the supported sources (ground, obc) the centroids are fetched from the mica L1
        archive or telemetry.

        :param source: 'ground' | 'obc'
        :param slot: ACA slot
        :param alg: for ground processing, use centroids from this algorithm.
        :param apply_dt: apply centroid time offsets via 'set_offsets'
        """
        self.centroid_source = source
        self.centroid_dt = None
        start = self.start
        stop = self.stop
        # Get centroids from Ska eng archive or mica L1 archive
        if source == 'ground':
            acen_files = sorted(asp_l1.get_files(start=start, stop=stop, content=['ACACENT']))
            acen = vstack([Table.read(f) for f in sorted(acen_files)], metadata_conflicts='silent')
            ok = ((acen['slot'] == slot) & (acen['alg'] == alg) & (acen['status'] == 0)
                  & (acen['time'] >= DateTime(start).secs) & (acen['time'] <= DateTime(stop).secs))
            yags = np.array(acen[ok]['ang_y'] * 3600)
            zags = np.array(acen[ok]['ang_z'] * 3600)
            yag_times = np.array(acen[ok]['time'])
            zag_times = np.array(acen[ok]['time'])
        elif source == 'obc':
            telem = fetch.Msidset(['AOACYAN{}'.format(slot), 'AOACZAN{}'.format(slot)], start, stop)
            # Filter centroids for reasonble-ness
            yok = telem['AOACYAN{}'.format(slot)].vals > -3276
            zok = telem['AOACZAN{}'.format(slot)].vals > -3276
            yags = telem['AOACYAN{}'.format(slot)].vals[yok]
            yag_times = telem['AOACYAN{}'.format(slot)].times[yok]
            zags = telem['AOACZAN{}'.format(slot)].vals[zok]
            zag_times = telem['AOACZAN{}'.format(slot)].times[zok]
        else:
            raise ValueError("centroid_source must be 'obc' or 'ground'")
        self.yags = yags
        self.yag_times = yag_times
        self.zags = zags
        self.zag_times = zag_times
        if apply_dt is True:
            self.set_offsets()
        else:
            self.centroid_dt = 0

    def set_atts(self, source):
        """Get attitude solution quaternions from ``source``.

        One could also just set atts and att_times attributes directly.
        """
        self.att_source = source
        tstart = DateTime(self.start).secs
        tstop = DateTime(self.stop).secs
        # Get attitudes and times
        if source == 'obc':
            telem = fetch.Msidset(['aoattqt*'], tstart, tstop)
            atts = np.vstack([telem['aoattqt{}'.format(idx)].vals
                              for idx in [1, 2, 3, 4]]).transpose()
            att_times = telem['aoattqt1'].times
            # Fetch COBSQID at beginning and end of interval, check they match, and defined obsid
            if self.obsid is None:
                obsid_start = fetch.Msid('COBSRQID', tstart, tstart + 60)
                obsid_stop = fetch.Msid('COBSRQID', tstop - 60, tstop)
                if len(obsid_start.vals) == 0 or len(obsid_stop.vals) == 0:
                    raise ValueError(
                        "Error getting COBSRQID telem for tstart:{} tstop:{} from fetch_source:{}".format(
                            tstart, tstop, fetch.data_source.sources()[0]))
                if obsid_start.vals[-1] != obsid_stop.vals[0]:
                    raise ValueError("Time range covers more than one obsid; Not supported.")
                self.obsid = obsid_start.vals[-1]
        elif source == 'ground':
            atts, att_times, asol_recs = asp_l1.get_atts(start=tstart, stop=tstop)
            obsids = np.unique(np.array([int(rec['OBS_ID']) for rec in asol_recs]))
            if len(obsids) > 1:
                raise ValueError("Time range covers more than one obsid; Not supported.")
            self.obsid = obsids[0]
        else:
            raise ValueError("att_source must be 'obc' or 'ground'")
        ok = (att_times >= tstart) & (att_times < tstop)
        self.atts = atts[ok, :]  # (N, 4) numpy array
        self.att_times = att_times[ok]

    def set_atts_from_solfiles(self, asol_files, acal_files, aqual_files, filter=True):
        atts, att_times, asol_recs = asp_l1.get_atts_from_files(asol_files, acal_files, aqual_files, filter=filter)
        obsids = np.unique(np.array([int(rec['OBS_ID']) for rec in asol_recs]))
        if len(obsids) > 1:
            raise ValueError("Time range covers more than one obsid; Not supported.")
        self.atts = atts
        self.att_times = att_times

    def set_star(self, agasc_id=None, slot=None):
        """
        Set self.ra and dec from either agasc_id *or* slot.

        This assumes use of star in default agasc miniagasc (no 1.4 or 1.5 or very faint stars)
        Lookup by "slot" relies on database of starcheck catalogs.

        Along the way set self.agasc_id.  One can also just set
        self.ra and dec directly.
        """
        if agasc_id is not None:
            star = agasc.get_star(agasc_id, date=self.start)
        elif slot is not None:
            sc = mica.starcheck.get_starcheck_catalog_at_date(self.start)
            stars = sc['cat'][(sc['cat']['slot'] == slot)
                             & ((sc['cat']['type'] == 'GUI') | (sc['cat']['type'] == 'BOT'))]
            if not len(stars):
                raise ValueError(
                    "No GUI or BOT in slot {} at time {} in dwell".format(slot, DateTime(self.start).date))
            star = agasc.get_star(stars[0]['id'], date=self.start)
        else:
            raise ValueError("Need to supply agasc_id or slot to look up star")

        # Could also add logic to infer star from loose position and magnitude
        self.agasc_id = star['AGASC_ID']
        self.ra = star['RA_PMCORR']
        self.dec = star['DEC_PMCORR']

    @property
    def yags(self):
        return self._yags

    @yags.setter
    def yags(self, vals):
        if isinstance(vals, fetch.MSID):
            self._yags = np.array(vals.vals)
            self._yag_times = vals.times
        else:
            self._yags = np.array(vals)

    @property
    def yag_times(self):
        return self._yag_times

    @yag_times.setter
    def yag_times(self, vals):
        self._yag_times = np.array(vals)

    @property
    def zags(self):
        return self._zags

    @zags.setter
    def zags(self, vals):
        if isinstance(vals, fetch.MSID):
            self._zags = np.array(vals.vals)
            self._zag_times = vals.times
        else:
            self._zags = np.array(vals)

    @property
    def zag_times(self):
        return self._zag_times

    @zag_times.setter
    def zag_times(self, vals):
        self._zag_times = np.array(vals)

    def set_offsets(self):
        """
        Apply time offsets to centroids based on type and source of centroid, obsid
        (suggesting 8x8 or 6x6 data), telemetry source ('maude' or 'cxc') and aspect solution source.
        These time offsets were fit.  See fit notebooks at:

        http://nbviewer.jupyter.org/url/cxc.harvard.edu/mta/ASPECT/ipynb/centroid_time_offsets/OR.ipynb

        and

        http://nbviewer.jupyter.org/url/cxc.harvard.edu/mta/ASPECT/ipynb/centroid_time_offsets/ER.ipynb

        """
        # If already applied, do nothing
        if self.centroid_dt is not None:
            return
        if self.att_source is None or self.centroid_source is None:
            return
        # Get and check reasonable-ness of fetch data source
        if len(fetch.data_source.sources()) > 1:
            warnings.warn("Can't set offsets based on fetch data source if multiple data sources set")
            return
        fetch_source = fetch.data_source.sources()[0]
        if fetch_source != 'cxc' and fetch_source != 'maude':
            warnings.warn("Only maude and cxc fetch data sources are supported for offsets. Not applying offsets.")
            return
        obstype = 'or' if self.obsid < 38000 else 'er'
        if fetch_source == 'maude' and obstype == 'er':
            warnings.warn("Centroid time offsets not well fit for 'maude' telem source on ERs. Use caution.")

        # Offsets calculated using OR and ER notebooks in SKA/analysis/centroid_and_sol_time_offsets
        offsets = {
            # (centroid_source, att_source, fetch_source, obstype):  median offset in time
            ('obc', 'obc', 'cxc', 'or'): -2.45523126997,
            ('obc', 'ground', 'cxc', 'or'): -2.46900481785,
            ('ground', 'obc', 'cxc', 'or'): 0.0366092437236,
            ('ground', 'ground', 'cxc', 'or'): 0.0553306628318,
            ('obc', 'obc', 'maude', 'or'): -2.96746879688,
            ('obc', 'ground', 'maude', 'or'): -2.94076404877,
            ('ground', 'obc', 'maude', 'or'): 0.010515586472,
            ('obc', 'obc', 'cxc', 'er'): -2.53954270068,
            ('obc', 'ground', 'cxc', 'er'): -2.49080106675,
            ('ground', 'obc', 'cxc', 'er'): -0.0322463030744,
            ('ground', 'ground', 'cxc', 'er'): 0.0355677462107,
            ('obc', 'obc', 'maude', 'er'): -2.90454699136,
            ('obc', 'ground', 'maude', 'er'): -3.00151559564,
            ('ground', 'obc', 'maude', 'er'): 0.116096583881,
            }

        self.centroid_dt = offsets[(self.centroid_source,
                                    self.att_source,
                                    fetch_source,
                                    obstype)]

        self.yag_times = self.yag_times + self.centroid_dt
        self.zag_times = self.zag_times + self.centroid_dt

    def calc_residuals(self):
        """
        Calculate residuals based on attitude and ra/dec of star.  Note that the sampling and times
        of yags may be different from zags so these should be done independently.

        Residuals are available in self.dyags and self.dzags.
        Predicted values from attitude and star position in self.pred_yags and self.pred_zags

        """
        # If time offsets weren't applied because centroids were initialized before atts, try again
        if self.centroid_dt is None:
            self.set_offsets()
        # If still not set, warn
        if self.centroid_dt is None:
            warnings.warn("Residuals calculated on centroids without time offsets applied")
        if len(self.att_times) < 2:
            raise ValueError("Cannot attempt to calculate residuals with fewer than 2 attitude samples")
        eci = Ska.quatutil.radec2eci(self.ra, self.dec)
        # Transform the 3x3 to get the axes to align to have the dot product make sense
        d_aca = np.dot(quat_vtransform(self.atts).transpose(0, 2, 1), eci)
        p_yags = np.arctan2(d_aca[:, 1], d_aca[:, 0]) * R2A
        p_zags = np.arctan2(d_aca[:, 2], d_aca[:, 0]) * R2A
        self.pred_yags = interpolate(p_yags, self.att_times, self.yag_times, sorted=True)
        self.pred_zags = interpolate(p_zags, self.att_times, self.zag_times, sorted=True)
        self.dyags = self.yags - self.pred_yags
        self.dzags = self.zags - self.pred_zags

    @classmethod
    def for_slot(cls, obsid=None, start=None, stop=None,
                 slot=None, att_source='ground', centroid_source='ground'):
        if obsid is not None:
            if start is not None or stop is not None:
                raise ValueError('cannot specify both obsid and start / stop')
            ds = events.dwells.filter(obsid=obsid)
            start = ds[0].start
            stop = ds[len(ds) - 1].stop
        if start is None or stop is None:
            raise ValueError('must specify obsid or start / stop')
        cr = cls(start, stop)
        if obsid is not None:
            cr.obsid = obsid
        cr.set_atts(att_source)
        cr.set_centroids(centroid_source, slot)
        cr.set_star(slot=slot)
        cr.calc_residuals()  # instead of get_residuals
        return cr
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
#
#   Copyright 2017-2021 Romain Boman
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

"""
Management of SVN and git commands
"""

import pytools.utils as pu
import os
import os.path
import subprocess
import re

class Repo:
    def __init__(self):
        pass

    def update(self):
        pass

class GITRepo(Repo):
    """ a git repository
    """
    def __init__(self, name, repo):
        self.name = name
        self.repo = repo

    def update(self):

        if not os.path.isdir(self.name):
            # print "skipping %s" % self.name  # should checkout instead
            # return
            # cmd = 'git clone --recursive %s' % self.repo
            # if not pu.isUnix():
            #     cmd = r'"C:\Program Files\Git\bin\sh.exe" --login -c "%s"' % cmd
            cmd = ['git','clone', '--recursive', self.repo ]
            status = subprocess.call(cmd)
            if status:
                raise Exception('"%s" FAILED with error %d' % (cmd, status))
            # print('status =', status)

        else:
            pu.chDir(self.name)
            # if pu.isUnix():
            #     cmd = 'git pull'
            # else:
            #     cmd = r'"C:\Program Files\Git\bin\sh.exe" --login -c "git pull"'
            # print(cmd)
            # os.system necessite des "" en plus autour de la cmd)
            #os.system('"%s"' % cmd)
            cmd = ['git', 'pull']
            status = subprocess.call(cmd)
            if status:
                raise Exception('"%s" FAILED with error %d' % (cmd, status))
            # print('status=', status)

            # update submodules
            if os.path.isfile('.gitmodules'):
                # '--init' for the case where modules were not present before
                cmd = ['git', 'submodule', 'update', '--init'] # '--recursive'] if sub-sub-modules
                status = subprocess.call(cmd)
                if status:
                    raise Exception('"%s" FAILED with error %d' % (cmd, status))

            pu.chDir('..')

        # set 'core.filemode=false' in '.git/config' on windows!
        # (otherwise executable files are considered as diffs)
        if not pu.isUnix():
            pu.chDir(self.name)
            # cmd = 'git config core.filemode false'
            # cmd = r'"C:\Program Files\Git\bin\sh.exe" --login -c "%s"' % cmd
            # print(cmd)
            cmd = ['git', 'config', 'core.filemode', 'false']
            status = subprocess.call(cmd)
            if status:
                raise Exception('"%s" FAILED with error %d' % (cmd, status))
            pu.chDir('..')

    def outdated(self):
        """checks whether the working copy is outdated or not
        """

        if not os.path.isdir(self.name):
            return True
        else:
            os.chdir(self.name)

        # fetch everything
        cmd = ['git', 'remote', '-v', 'update']
        # if not pu.isUnix():
        #     cmd = [r'C:\Program Files\Git\bin\sh.exe',
        #            '--login', '-c', ' '.join(cmd)]
        with open(os.devnull, 'w') as FNULL:
            status = subprocess.call(
                cmd, stdout=FNULL, stderr=subprocess.STDOUT)
        if status:
            raise Exception('"%s" FAILED with error %d in cwd=%s' % (cmd, status, os.getcwd()))

        # check "Your branch is up to date"
        cmd = ['git', 'status', '-uno']
        # if not pu.isUnix():
        #     cmd = [r'C:\Program Files\Git\bin\sh.exe',
        #            '--login', '-c', ' '.join(cmd)]
        out = subprocess.check_output(cmd)
        out = out.decode()  # python 3 returns bytes
        m = re.search(r'Your branch is up to date', out)
        os.chdir('..')

        return (m == None)

    def checkout(self, branch='master'):
        os.chdir(self.name)
        cmd = ['git', 'checkout', branch]
        # if not pu.isUnix():
        #     cmd = [r'C:\Program Files\Git\bin\sh.exe',
        #            '--login', '-c', ' '.join(cmd)]
        with open(os.devnull, 'w') as FNULL:
            status = subprocess.call(
                cmd, stdout=FNULL, stderr=subprocess.STDOUT)
        if status:
            raise Exception('"%s" FAILED with error %d' % (cmd, status))
        os.chdir('..')

class SVNRepo(Repo):
    """Old class used to manage SVN repositories
    """
    def __init__(self, name, repo):
        self.name = name
        self.repo = repo

        # set SVN_SSH, sinon: "can't create tunnel"
        if not pu.isUnix():
            os.environ[
                'SVN_SSH'] = r'C:\\Program Files\\TortoiseSVN\\bin\\TortoisePlink.exe'  # '\\\\' ou 'r et \\' !!

    def update(self):

        if not os.path.isdir(self.name):
            cmd = 'svn co %s %s' % (self.repo, self.name)
        else:
            cmd = 'svn update %s' % self.name

        print(cmd)
        status = subprocess.call(cmd, shell=True)
        if status:
            raise Exception('"%s" FAILED with error %d' % (cmd, status))

    def outdated(self):
        "checks whether the working copy is outdated"

        if not os.path.isdir(self.name):
            return True

        # svn info
        out = subprocess.check_output(['svn', 'info', self.name])
        out = out.decode(errors='ignore')  # python 3 returns bytes
        m = re.search(r'Last Changed Rev: (\d+)', out)
        if m and len(m.groups()) > 0:
            version = m.group(1)
        else:
            raise Exception('cannot read "svn info" output')

        # svn info -r HEAD
        out = subprocess.check_output(['svn', 'info', '-r', 'HEAD', self.name])
        out = out.decode(errors='ignore')  # python 3 returns bytes
        m = re.search(r'Last Changed Rev: (\d+)', out)
        if m and len(m.groups()) > 0:
            version_HEAD = m.group(1)
        else:
            raise Exception('cannot read "svn info -r HEAD" output')

        #print('version =', version)
        #print('version_HEAD =', version_HEAD)

        return version != version_HEAD
<commit_msg>cleaning<commit_after># -*- coding: utf-8 -*-
#
#   Copyright 2017-2021 Romain Boman
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

"""
Management of SVN and git commands
"""

import pytools.utils as pu
import os
import os.path
import subprocess
import re

class Repo:
    def __init__(self):
        pass

    def update(self):
        pass

class GITRepo(Repo):
    """ a git repository
    """
    def __init__(self, name, repo):
        self.name = name
        self.repo = repo

    def update(self):

        if not os.path.isdir(self.name):
            cmd = ['git', 'clone', '--recursive', self.repo ]
            status = subprocess.call(cmd)
            if status:
                raise Exception('"%s" FAILED with error %d' % (cmd, status))

        else:
            pu.chDir(self.name)
            cmd = ['git', 'pull']
            status = subprocess.call(cmd)
            if status:
                raise Exception('"%s" FAILED with error %d' % (cmd, status))

            # update submodules
            if os.path.isfile('.gitmodules'):
                # '--init' for the case where modules were not present before
                cmd = ['git', 'submodule', 'update', '--init'] # '--recursive'] if sub-sub-modules
                status = subprocess.call(cmd)
                if status:
                    raise Exception('"%s" FAILED with error %d' % (cmd, status))

            pu.chDir('..')

        # set 'core.filemode=false' in '.git/config' on windows!
        # (otherwise executable files are considered as diffs)
        if not pu.isUnix():
            pu.chDir(self.name)
            cmd = ['git', 'config', 'core.filemode', 'false']
            status = subprocess.call(cmd)
            if status:
                raise Exception('"%s" FAILED with error %d' % (cmd, status))
            pu.chDir('..')

    def outdated(self):
        """checks whether the working copy is outdated or not
        """

        if not os.path.isdir(self.name):
            return True
        else:
            os.chdir(self.name)

        # fetch everything
        cmd = ['git', 'remote', '-v', 'update']
        # this code can be used if git is not in the PATH
        # if not pu.isUnix():
        #     cmd = [r'C:\Program Files\Git\bin\sh.exe',
        #            '--login', '-c', ' '.join(cmd)]
        with open(os.devnull, 'w') as FNULL:
            status = subprocess.call(
                cmd, stdout=FNULL, stderr=subprocess.STDOUT)
        if status:
            raise Exception('"%s" FAILED with error %d in cwd=%s' % (cmd, status, os.getcwd()))

        # check "Your branch is up to date"
        cmd = ['git', 'status', '-uno']
        out = subprocess.check_output(cmd)
        out = out.decode()  # python 3 returns bytes
        m = re.search(r'Your branch is up to date', out)
        os.chdir('..')

        return (m == None)

    def checkout(self, branch='master'):
        os.chdir(self.name)
        cmd = ['git', 'checkout', branch]
        with open(os.devnull, 'w') as FNULL:
            status = subprocess.call(
                cmd, stdout=FNULL, stderr=subprocess.STDOUT)
        if status:
            raise Exception('"%s" FAILED with error %d' % (cmd, status))
        os.chdir('..')

class SVNRepo(Repo):
    """Old class used to manage SVN repositories
    """
    def __init__(self, name, repo):
        self.name = name
        self.repo = repo

        # set SVN_SSH, sinon: "can't create tunnel"
        if not pu.isUnix():
            os.environ[
                'SVN_SSH'] = r'C:\\Program Files\\TortoiseSVN\\bin\\TortoisePlink.exe'  # '\\\\' ou 'r et \\' !!

    def update(self):

        if not os.path.isdir(self.name):
            cmd = 'svn co %s %s' % (self.repo, self.name)
        else:
            cmd = 'svn update %s' % self.name

        print(cmd)
        status = subprocess.call(cmd, shell=True)
        if status:
            raise Exception('"%s" FAILED with error %d' % (cmd, status))

    def outdated(self):
        "checks whether the working copy is outdated"

        if not os.path.isdir(self.name):
            return True

        # svn info
        out = subprocess.check_output(['svn', 'info', self.name])
        out = out.decode(errors='ignore')  # python 3 returns bytes
        m = re.search(r'Last Changed Rev: (\d+)', out)
        if m and len(m.groups()) > 0:
            version = m.group(1)
        else:
            raise Exception('cannot read "svn info" output')

        # svn info -r HEAD
        out = subprocess.check_output(['svn', 'info', '-r', 'HEAD', self.name])
        out = out.decode(errors='ignore')  # python 3 returns bytes
        m = re.search(r'Last Changed Rev: (\d+)', out)
        if m and len(m.groups()) > 0:
            version_HEAD = m.group(1)
        else:
            raise Exception('cannot read "svn info -r HEAD" output')

        #print('version =', version)
        #print('version_HEAD =', version_HEAD)

        return version != version_HEAD
<|endoftext|>