Source code for pyhetdex.het.fplane

# Misc python library to support HETDEX software and data analysis
# Copyright (C) 2015, 2016, 2017  "The HETDEX collaboration"
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""Base class for fplane file parsing and IFU.

This module provides a basic parser for the focal plane file and an object
containing the informations about the IFU from the focal plane.

The focal plane is expected to be::

    ##IFUSLOT X_FP   Y_FP   SPECID SPECSLOT IFUID IFUROT PLATESC
      001     -450.0 150.0  37     42       024   0.0    1.00

Commented lines are ignored.
"""
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import six


class NoIFUError(KeyError):
    """Error raised when the required ifu does not exists"""
    pass


class UnknownIDTypeError(ValueError):
    """Unknown id type"""
    pass


[docs]class IFU(object): """Contain the information for the IFU from the focal plane file. The input type are cast to the corresponding types when initialising the object. Parameters ---------- ifuslot : string id of the ifu x, y : float x and y position of the ifu in the focal plane specid : int id of the spectrograph where the ifu is plugged into specslot : int id of the spectrograph slot where the spectrograph is plugged into ifuid : string id of the virus ifu bundle ifurot : float rotation of the IFU in its seat in the IHMP platescl : float focal plane plate scale at the position in the IHMP Attributes ---------- ifuid, x, y, specid, specslot, ifuid, ifurot, platescl : as before xid, yid : int x (column) and y (row) id of the ifu in the ifu head mounting plate (IHMP), generated from the ifuslot Raises ------ TypeError if the ``ifuslot`` is not a string """ def __init__(self, ifuslot, x, y, specid, specslot, ifuid, ifurot, platescl): if not isinstance(ifuslot, six.string_types): raise TypeError('ifuslot must be string, not', type(ifuslot)) self.ifuslot = ifuslot self.x = float(x) self.y = float(y) self.specid = int(specid) self.specslot = int(specslot) self.ifuid = str(ifuid) self.ifurot = float(ifurot) self.platescl = float(platescl) self.xid = int(self.ifuslot[0:2]) self.yid = int(self.ifuslot[2]) def __str__(self): msg = "ifuslot: '{0}'; ifuid: '{1}'; specid: '{2}'" return msg.format(self.ifuslot, self.ifuid, self.specid)
[docs]class FPlane(object): """Focal plane. Contains the dictionary of :class:`IFU` instance (or derived or others), with the ifu id as key. Parameters ---------- fplane_file : string name of the file containing the ids and position of the IFUs ifu_class : :class:`IFU` instance (or anything else), optional class definition containing the IFU information. empty_specid, empty_ifuid : string, optional if the entries for the SPECID (fourth column) or IFUID (sixt column) are as specified, they are replaced by a two digit negative number or a two digit number following a 'N'. The number is increased any time one of the two conditions is met. Use it with caution as the SPECID and IFUID are used as dictionary keywords and should not be duplicated to avoid losing IFUs exclude_ifuslot : list of string, optional list of ifu slot ids to exclude when loading the fplane file. The ids must much exactly the string in the first column of the file skip_empty : bool, optional if ``True`` skip one ifu if the specid/ifuid is marked as empty Attributes ---------- ifus ifuids ifuslots specids difus_ifuid difus_ifuslot difus_specid """ def __init__(self, fplane_file, ifu_class=IFU, empty_specid='00', empty_ifuid='000', exclude_ifuslot=[], skip_empty=False): self._fplane_file = fplane_file self._IFU = ifu_class self._ifus_by_id = {} self._ifus_by_slot = {} self._ifus_by_spec = {} self._load_fplane(fplane_file, empty_specid, empty_ifuid, exclude_ifuslot, skip_empty) @property def ifus(self): """list of :class:`IFU` instances""" return list(self._ifus_by_id.values()) @property def ifuids(self): """list of IFUIDs (strings)""" return list(self._ifus_by_id.keys()) @property def ifuslots(self): """list of IFUSLOTs (strings)""" return list(self._ifus_by_slot.keys()) @property def specids(self): """list of SPECIDs (integers)""" return list(self._ifus_by_spec.keys()) @property def difus_ifuid(self): """dictionary of ifus; key: IFUID (string); value: :class:`IFU` instance""" return self._ifus_by_id @property def difus_ifuslot(self): """dictionary of ifus; key: IFUSLOT (string); value: :class:`IFU` instance""" return self._ifus_by_slot @property def difus_specid(self): """dictionary of ifus; key: SPECID (int); value: :class:`IFU` instance""" return self._ifus_by_spec
[docs] def by_ifuid(self, ifuid): """Returns the ifu with ``ifuid`` Parameters ---------- ifuid : string id of the ifu Returns ------- :class:`IFU` instance Raises ------ NoIFUError if there is no IFU identified by the input ID """ try: return self._ifus_by_id[ifuid] except KeyError as e: six.raise_from(NoIFUError(e), e)
[docs] def by_ifuslot(self, ifuslot): """Returns the ifu with ``ifuslot`` Parameters ---------- ifuslot : string id of the ihmp slot Returns ------- :class:`IFU` instance Raises ------ NoIFUError if there is no IFU identified by the input ID """ try: return self._ifus_by_slot[ifuslot] except KeyError as e: six.raise_from(NoIFUError(e), e)
[docs] def by_slotpos(self, x, y): """Returns the ifu in ifu slot position x, y Parameters ---------- x : int x position in the IHMP (1 to 10) y : int y position in the IHMP (1 to 9) Returns ------- :class:`IFU` instance Raises ------ NoIFUError if there is no IFU for the input positions """ try: return self._ifus_by_slot['{:02d}{:d}'.format(x, y)] except KeyError as e: six.raise_from(NoIFUError(e), e)
[docs] def by_specid(self, specid): """Returns the ifu with ``specid`` Parameters ---------- specid : int or string id of the spectrograph; the value is cast to an integer Returns ------- :class:`IFU` instance Raises ------ NoIFUError if there is no IFU identified by the input ID TypeError if the input is not an int or a string that can be cast to an int """ if isinstance(specid, six.string_types): try: specid = int(specid) except ValueError as e: msg = ('If specid is a string it must be a valid literal for' ' int(), not ') six.raise_from(TypeError(msg, specid), e) elif isinstance(specid, (int, six.string_types)): pass else: raise TypeError('specid must be an integer or a string, not', type(specid)) try: return self._ifus_by_spec[specid] except KeyError as e: six.raise_from(NoIFUError(e), e)
[docs] def by_id(self, id_, idtype): """Returns the ifu with ``id_`` Parameters ---------- id_ : string id of the spectrograph idtype : str type of the id; must be one of 'ifuid', 'ifuslot', 'specid' Returns ------- :class:`IFU` instance Raises ------ NoIFUError if there is no IFU identified by the input ID UnknownIDTypeError if the ID type is not known """ if idtype == 'ifuid': ifu = self.by_ifuid elif idtype == 'ifuslot': ifu = self.by_ifuslot elif idtype == 'specid': ifu = self.by_specid else: raise UnknownIDTypeError("Id type {} is not known") return ifu(id_)
[docs] def _load_fplane(self, fname, empty_specid, empty_ifuid, exclude_ifuslot, skip_empty): """Load the focal plane file and creates the :class:`IFU` instances Parameters ---------- fname : string name of the focal plane file empty_specid, empty_ifuid, exclude_ifuslot, skip_empty : see :class:`FPlane` """ missing = 1 with open(fname, mode='r') as f: for l in f: if l.startswith("#"): continue line = l.strip("\n").strip() params = [i.strip() for i in line.split()] if params[0] in exclude_ifuslot: continue changed = False if params[3] == empty_specid: params[3] = '-%02d' % missing changed = True if skip_empty: continue if params[5] == empty_ifuid: params[5] = 'N%02d' % missing changed = True if skip_empty: continue if changed: missing += 1 self.add_ifu(params)
[docs] def add_ifu(self, fpars): """Parse a fplane ``line`` and add the IFU to the internal dictionary. Make sure that the ifuid, specid are a three digit string. Override this method if the ``ifu`` class constructor is not as the one of :class:`IFU`. Parameters ---------- line : string line of the fplane file """ ifuslot, x, y, specid, speclot, ifuid, ifurot, platescl = fpars _ifu = self._IFU(ifuslot, x, y, specid, speclot, ifuid, ifurot, platescl) self._ifus_by_id[_ifu.ifuid] = _ifu self._ifus_by_slot[_ifu.ifuslot] = _ifu self._ifus_by_spec[_ifu.specid] = _ifu