Source code for ixpeobssim.instrument.mma

#!/urs/bin/env python
#
# Copyright (C) 2018--2022, the ixpeobssim team.
#
# 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

from __future__ import print_function, division


import numpy

from ixpeobssim.instrument.sc import dithering_pattern
from ixpeobssim.utils.units_ import arcmin_to_degrees, degrees_to_arcsec
from ixpeobssim.instrument.gpd import rotate_detxy
from ixpeobssim.utils.logging_ import logger

# The focal length has been updated form the nominal 4-m value to the measured
# value of 3997 mm based on issue #336.
FOCAL_LENGTH = 3997.


[docs] def fiducial_backscal(half_side_x, half_side_y): """Calculate the BACKSCALE value for a give fiducial rectangle in detector coordinates. This function was introduced in response to the issue https://github.com/lucabaldini/ixpeobssim/issues/668 in place of the old FIDUCIAL_BACKSCAL constant. Arguments --------- half_side_x : float The half side of the fiducial rectangle along the x coordinate in mm. half_side_y : float The half side of the fiducial rectangle along the y coordinate in mm. """ dx = numpy.degrees(2. * half_side_x / FOCAL_LENGTH) dy = numpy.degrees(2. * half_side_y / FOCAL_LENGTH) return degrees_to_arcsec(dx) * degrees_to_arcsec(dy)
def _sky_to_gpd_naive(ra, dec, ra_pnt, dec_pnt): """Convert an array of (ra, dec) positions in the sky to an array of (detx, dety) positions onto the GPD reference frame. As the name (and the prepending _) suggest, this is a simplified version of the transformation, not taking into account a number of effects, including the relative rotation of the DUs, the roll angle and the dithering of the observatory. It is still useful to provide an implementation that is agnostic about the specific identifier of the DU, the roll angle and the event time---which is necessary for calculting the dithering information. Note that this function is called twice in the actual sky_to_gpd(). Arguments --------- ra : float or array The ra position in the sky in decimal degrees dec : float or array The dec position in the sky in decimal degrees ra_pnt : float or array The pointing ra in decimal degrees dec_pnt : float or array The pointing dec in decimal degrees """ cos_dec = numpy.cos(numpy.radians(dec_pnt)) detx = - FOCAL_LENGTH * numpy.radians(ra - ra_pnt) * cos_dec dety = FOCAL_LENGTH * numpy.radians(dec - dec_pnt) return detx, dety
[docs] def parse_dithering_kwargs(**kwargs): """Parse the keyword arguments related to the dithering. """ if not kwargs.get('dithering'): return None return (kwargs.get('ditherampl'), kwargs.get('ditherpa'), kwargs.get('ditherpx'), kwargs.get('ditherpy'))
def _dithering_delta(time_, dither_params, degrees=True): """Return the angular offset of the observatory boresight wrt to the nominal pointing position at a given array of event times and for a given set of dithering parameters. Note that the ditheing pattern expresses the orientation of the observatory with respect to the nominal pointing direction, so that if you want to turn this into the actual displacement in the sky of a given photon in the event list due to the dithering you should take the opposite and take cosine of the declination into account downstream. By default the return value is expressed in decimal degrees. Arguments --------- time_ : float or array The time at which the angular offset should be calculated dither_params : tuple or list The complete set of dithering parameters, in the following order (ditherampl, ditherpa, ditherpx, ditherpy), in the same units they would be passed to xpobssim via command-line. degrees : bool If true (default) the agular offset in ra and dec is converted in decimal degrees. """ logger.info('Calculating the dithering angular offset...') logger.info('A = %.3f arcmin, pa = %.3f s, px = %.3f s, py = %.3f s', *dither_params) dithering = dithering_pattern(*dither_params) # Compute the dithering offset based on time---note this is in arcmin! delta_ra, delta_dec = dithering(time_) if degrees: # Convert from arcmin to degrees. delta_ra = arcmin_to_degrees(delta_ra) delta_dec = arcmin_to_degrees(delta_dec) return delta_ra, delta_dec
[docs] def apply_dithering(time_, ra_pnt, dec_pnt, dither_params=None): """Apply dithering correction directly to pointing direction in celestial coordinates. In this way the correction gets propagated down the chain and the appropriate IRFs are used. See convolve_event_list in mma.py Arguments --------- time : float or array The event time ra_pnt : float or array The pointing ra in decimal degrees dec_pnt : float or array The pointing dec in decimal degrees dither_params : (amplitude, pa, px, py) tuple, optional The parameters for the dithering of the observatory. The dithering is not applied if this is set to None. """ # Apply the dithering. if dither_params is None: return numpy.full(time_.shape, ra_pnt), numpy.full(time_.shape, dec_pnt) delta_ra, delta_dec = _dithering_delta(time_, dither_params) ra_pnt += delta_ra / numpy.cos(numpy.radians(dec_pnt)) dec_pnt += delta_dec return ra_pnt, dec_pnt
[docs] def sky_to_gpd(ra, dec, time_, ra_pnt, dec_pnt, du_id, roll_angle, dither_params=None): """Convert an array of (ra, dec) positions in the sky to an array of (x, y) positions onto the gpd reference frame. This involves essentially three different steps: first we project the sky coordinates onto the focal plane reference frame, then we apply the dithering pattern (if enabled) based on the event times and finally we rotate the coordinates according to the DU id and the telescope roll angle. Warning ------- Change x and y to detx and dety! Arguments --------- ra : float or array The ra position in the sky in decimal degrees dec : float or array The dec position in the sky in decimal degrees time : float or array The event time ra_pnt : float or array The pointing ra in decimal degrees dec_pnt : float or array The pointing dec in decimal degrees du_id : int The detector unit id roll_angle : float The telescope roll angle in decimal degrees dither_params : (amplitude, pa, px, py) tuple, optional The parameters for the dithering of the observatory. The dithering is not applied if this is set to None. """ # Project sky position to the "naive" reference frame detx, dety = _sky_to_gpd_naive(ra, dec, ra_pnt, dec_pnt) if dither_params is not None: # Apply the dithering. delta_ra, delta_dec = _dithering_delta(time_, dither_params) # Project the dithering onto the focal plane by calling the naive # transformation centered on the pointing of the observatory. dx, dy = _sky_to_gpd_naive(delta_ra, delta_dec, 0., 0.) detx -= dx dety -= dy # Rotate the xy coordinates according to du id and roll angle return rotate_detxy(detx, dety, du_id, roll_angle)
def _gpd_to_sky_naive(detx, dety, ra_pnt, dec_pnt): """Convert an array of (detx, dety) positions in detector coordinates to an array of (ra, dec) positions in the sky. This is supposed to be the inverse of _sky_to_gpd_naive(), so that the two operations, applied in series, should preserve the original data. Arguments --------- detx : float or array The detx position in detector coordinates in mm dety : float or array The dety position in detector coordinates in mm ra_pnt : float or array The pointing ra in decimal degrees dec_pnt : float or array The pointing dec in decimal degrees """ cos_dec = numpy.cos(numpy.radians(dec_pnt)) ra = ra_pnt - numpy.degrees(detx / FOCAL_LENGTH / cos_dec) dec = dec_pnt + numpy.degrees(dety / FOCAL_LENGTH) return ra, dec
[docs] def gpd_to_sky(detx, dety, time_, ra_pnt, dec_pnt, du_id, roll_angle, dither_params=None): """Convert an array of (detx, dety) positions in detector coordinates to an array of (ra, dec) positions in the sky. This is supposed to be the inverse of sky_to_gpd(), so that the two operations, applied in series, should preserve the original data. Arguments --------- detx : float or array The detx position in detector coordinates in mm dety : float or array The dety position in detector coordinates in mm time : float or array The event time ra_pnt : float or array The pointing ra in decimal degrees dec_pnt : float or array The pointing dec in decimal degrees du_id : int The detector unit id roll_angle : float The telescope roll angle in decimal degrees dither_params : (amplitude, pa, px, py) tuple, optional The parameters for the dithering of the observatory. The dithering is not applied if this is set to None. """ detx, dety = rotate_detxy(detx, dety, du_id, roll_angle, inverse=True) ra, dec = _gpd_to_sky_naive(detx, dety, ra_pnt, dec_pnt) if dither_params is not None: # Apply the dithering. delta_ra, delta_dec = _dithering_delta(time_, dither_params) ra += delta_ra / numpy.cos(numpy.radians(dec_pnt)) dec += delta_dec return ra, dec