# 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/>.
""" Telescope module
Stores information related to the guide, i.e. guide probe and tracker
information. Also deals with illumination and image quality servers.
"""
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import abc
from distutils.spawn import find_executable
import os
import subprocess as sp
import numpy as np
from pyhetdex.tools.six_ext import SubprocessExeError
import six
[docs]class Shot(object):
"""Class to store information about the whole shot and retrieve fwhm,
illumination, transparency and normalization.
Each of them rely on some underlying model, with the interface defined by
:class:`ModelInterface`
Parameters
----------
fwhm_fallback : float, optionally
number to use when instantiating a :class:`ConstantModel` if a
:attr:`fwhm_model` is not provided
illumination_fallback : float, optionally
number to use when instantiating a :class:`ConstantModel` if a
:attr:`illumination_model` is not provided
transparency_fallback : float, optionally
number to use when instantiating a :class:`ConstantModel` if a
:attr:`transparency_model` is not provided
Attributes
----------
fwhm_model
illumination_model
transparency_model
"""
def __init__(self, fwhm_fallback=1.6, illumination_fallback=1.,
transparency_fallback=1.):
self._fwhm = None
self._illumination = None
self._transparency = None
self._fwhm_fallback = fwhm_fallback
self._illumination_fallback = illumination_fallback
self._transparency_fallback = transparency_fallback
@property
def fwhm_model(self):
'''Retrieve, set or remove the fwhm model. If not set or removed, fall
back to one that returns a constant value'''
if self._fwhm is None:
self._fwhm = ConstantModel(self._fwhm_fallback)
return self._fwhm
@fwhm_model.setter
def fwhm_model(self, model):
self._fwhm = model
@fwhm_model.deleter
def fwhm_model(self):
self._fwhm = None
[docs] def fwhm(self, x, y, dither):
""" Return the FWHM
Parameters
----------
x, y : float
position in the focal plane in arcseconds
dither : int
the dither number (starting from 1)
"""
return self.fwhm_model(x, y, dither)
@property
def illumination_model(self):
'''Retrieve, set or remove the illumination model. If not set or removed, fall
back to one that returns a constant value'''
if self._illumination is None:
self._illumination = ConstantModel(self._illumination_fallback)
return self._illumination
@illumination_model.setter
def illumination_model(self, model):
self._illumination = model
@illumination_model.deleter
def illumination_model(self):
self._illumination = None
[docs] def illumination(self, x, y, dither):
""" Return the illumination
Parameters
----------
x,y : float
position in the focal plane in arcseconds
dither : int
the dither number (starting from 1)
"""
return self.illumination_model(x, y, dither)
@property
def transparency_model(self):
'''Retrieve, set or remove the transparency model. If not set or removed, fall
back to one that returns a constant value'''
if self._transparency is None:
self._transparency = ConstantModel(self._transparency_fallback)
return self._transparency
@transparency_model.setter
def transparency_model(self, model):
self._transparency = model
@transparency_model.deleter
def transparency_model(self):
self._transparency = None
[docs] def transparency(self, dither):
""" Return the sky transparency
Parameters
----------
dither : int
the dither number (starting from 1)
"""
return self.transparency_model(0, 0, dither)
[docs] def normalisation(self, x, y, dither):
""" Return the normalisation (transparency * illumination)
Parameters
----------
x, y : float
position in the focal plane in arcseconds
dither : int
the dither number (starting from 1)
"""
return (self.illumination(x, y, dither) *
self.transparency(dither))
# ==== Models ====
[docs]@six.add_metaclass(abc.ABCMeta)
class ModelInterface(object):
'''Abstract Base Class for the models. :class:`Shot` expects that all the
models are derived from this one and/or that implement the
:meth:`__call__`'''
[docs] @abc.abstractmethod
def __call__(self, x, y, dither): # pragma: no cover
'''Returns the value of the model in position x, y for ``dither``
Parameters
----------
x, y : float
position in the focal plane in arcseconds
dither : int
the dither number, it should start from 1
Returns
-------
number
value of the model
'''
pass
[docs]class ConstantModel(ModelInterface):
'''Dummy model that always return the value passed to the constructor
Parameters
----------
constant : number
number that :meth:`__call__` should return
'''
def __init__(self, constant):
self._constant = constant
[docs] def __call__(self, x, y, dither):
'''Returns the constant passed to the constructor. The arguments are
ignored'''
return self._constant
[docs]class PerDitherValueModel(ModelInterface):
''' A model that returns a single value per
dither, that doesn't depend on x, y
Parameters
----------
vals : array
an array of values, one for each dither
'''
def __init__(self, vals):
self._vals = vals
[docs] def __call__(self, x, y, dither):
'''Returns the val for this dither. The x,y arguments are
ignored'''
return self._vals[dither - 1]
[docs]class HetpupilModel(ModelInterface):
'''Run the ``$CUREBIN/hetpupil`` code on the input files and save the
relative illumination into a list. If required normalize the whole list to
the first element. :func:`subprocess.check_output` is used.
``hetpupil`` is searched in CUREBIN and then in the path
Parameters
----------
files : list of strings
name of the files on which to run the command
normalize : bool, optional
normalize the output of hetpupil to the first value found
Attributes
----------
fill_factor : list
list of filling factors for each input file
Raises
------
``pyhetdex.tools.six_ext.SubprocessExeError``
if he hetpupil executable cannot be found
'''
def __init__(self, files, normalize=True):
# try to get distutils from the path
hetpupil = find_executable('hetpupil',
path=os.environ.get('CUREBIN', None))
if not hetpupil: # otherwise in CUREBIN
hetpupil = find_executable('hetpupil')
if not hetpupil:
raise SubprocessExeError('The executable ``hetpupil`` cannot be'
' found in $CUREBIN nor in the $PATH')
cmd_list = [hetpupil, '-q'] + files
stdout = sp.check_output(cmd_list, universal_newlines=True)
self.fill_factor = [float(i.split()[-1].strip())
for i in stdout.splitlines()]
self.fill_factor = np.array(self.fill_factor)
if normalize:
self.fill_factor /= self.fill_factor[0]
[docs] def __call__(self, x, y, dither):
'''Returns the value of the model for ``dither``. ``x/y`` are ignored
Parameters
----------
x, y : float
position in the focal plane in arcseconds
dither : int
the dither number, starting from 1
Returns
-------
float
relative/absolute pupil fill factor
'''
return self.fill_factor[dither - 1]