Source code for ecpi.common.sky.extented_body

"""To manage extended body in field of view, like:  Earth, moon, sun
"""

import logging
import numpy as np
from astropy.constants import R_earth
from astropy.constants import R_sun
import transforms3d.quaternions as tq
from scipy.linalg import norm
from ecpi.common.mission.attitude import xyz_to_direlev, direlev_to_xyz
from ecpi.common.instru.model_geom import InstruECLAIRs
from ecpi.common.mission.attitude import AttitudeECLAIRs
import ecpi.common.num.array_square_cell as asc

#pylint:disable=C0326

s_logger = logging.getLogger(__name__)


[docs]class ExtentedBodyInFov(object): """Informs if an arbitrary astronomical body is in the filed of view of the ECLAIRs instrument. """ def __init__(self): """**Constructor** """ self._name = "TBD" self._body_radius_km = None # km self._pos_body = None self._instru_ecl = InstruECLAIRs() self._ecl_att = AttitudeECLAIRs() self._ecl_att.set_attitude_svom_quater([1.0, 0, 0, 0]) self._pos_sat = np.array([1.0, 0.0, 0.0]) self.inst_half_fov = self._instru_ecl.get_half_fov() self.fov_ptg = None self.fov_ptg_side = 0
[docs] def set_quater_svom(self, quater): """Set SVOM quaternion ..warning: set position satellite before :param quater: unit quaternion :type quater: array(float) of length 4 """ self._ecl_att = AttitudeECLAIRs() self._ecl_att.set_attitude_svom_quater(quater) self._update_geom()
# TODO: COVERAGE
[docs] def set_eclairs_attitude(self, ecl_att): """Set ECLAIRs attitude object :param ecl_att: ECLAIRs instrument attitude object :type ecl_att: array(float) of length 3 """ assert isinstance(ecl_att, AttitudeECLAIRs) self._ecl_att = ecl_att self._update_geom()
[docs] def set_pos_sat_time(self, pos_sat, cur_time=None): """Set satellite position at considered time. :param pos_sat: satellite position [X, Y, Z] in km :type pos_sat: array(float) of length 3 :param cur_time: considered time in s. Default is None. :type cur_time: float """ self._time = cur_time self._pos_sat = pos_sat self._update_geom()
def _update_geom(self): """update body sat dist, limb angle, body pos in fov """ self._compute_dist_body_sat() # don't change order, limb need dist body sat self._compute_limb_body() self._compute_pos_fov() def _compute_dist_body_sat(self): """Compute satellite altitude in km. .. note:: must be redefined in herit classself.flag_update_openfov_map = True with def of self._pos_body """ self._dist_sat_body = norm(self._pos_body) def _compute_limb_body(self): """Compute limb angle in deg. """ self._limb = np.arcsin(self._body_radius_km / self._dist_sat_body) self._cos_limb = np.cos(self._limb) self._limb = np.rad2deg(self._limb) # TODO: COVERAGE
[docs] def body_direlev(self): """Spherical position in FOV of body in direction and elevation convention """ return self._body_direlev
[docs] def body_xyz(self): """Cartesian position in FOV of body """ return self._body_fov_xyz
def _compute_pos_fov(self): """Compute Earth position in term of direction and elevation (both in deg) in the field of view. """ quater = self._ecl_att._q_j2000_instf sat_body_unit = self._pos_body / self._dist_sat_body # sat_body is in ref J2000~GCRF cf CNES doc see attitude.py self._body_fov_xyz = tq.rotate_vector(sat_body_unit, tq.qconjugate(quater)) self._body_direlev = xyz_to_direlev(self._body_fov_xyz[np.newaxis, :], deg=True)[0] direlev = self._body_direlev s_logger.debug(f'dir Earth={direlev[0]} deg | elev Earth={direlev[1]} deg')
[docs] def limb_angle(self): """ :return: limb angle in degrees :rtype: float """ return self._limb
[docs] def is_in_fov(self): """Return whether Earth appears in ECALIRs field of view :return: if Earth is in FOV :rtype: bool """ limb_angle = self._limb colat = 90 - self._body_direlev[1] cond = not (colat > limb_angle + self.inst_half_fov) s_logger.debug(f'{self._name} in FOV: {cond} \tInstru half fov:'\ ' {self.inst_half_fov:.1f} deg') return cond
def _create_fov_ptg(self, side_fov): """Pointing of fov array .. note:: by Colley Jean-Marc Pointing of fov array is a constant of instrument for a given size_fov side array. So I can pre-compute it and store it in fov_ptg attribut: - float array with unit pointing vector of each position in fov - type : numpy array (side_fov, side_fov, 3) :param side_fov: number cell to represent fov (always square array) """ self.fov_ptg_side = side_fov # by defintion size_sky_pix = self._instru_ecl.pitch_pix_size # TODO: adapt 199 for another geometry ? size_fov_cm = 199 * size_sky_pix size_cell = size_fov_cm / side_fov array_fov = asc.ArraySquareCell(side_fov, side_fov, size_cell) array_fov.set_origin_center() a_fov_pos = array_fov.array_pos_center_cells() # convert in sky pixel unit a_fov_pos /= size_sky_pix # elev in deg elev, dir_src = self._instru_ecl.skypix_to_elevdir(a_fov_pos[:,:,0].ravel(), a_fov_pos[:,:,1].ravel()) a_dir_elev = np.array([dir_src, elev]).T ptg = direlev_to_xyz(a_dir_elev, deg=True) # unit vector pointing in FOV self.fov_ptg = ptg.reshape((side_fov, side_fov, 3))
[docs] def compute_openfov_map(self, side_fov=199): """Create the open field of view map .. note:: Definition OpenFOV map is square boolean array (side_fovxside_fov), True is open (no extended body) :param side_fov: integer, number of side pixels :return: True if extended body is NOT present :rtype: numpy array (side_fov, side_fov) of bool """ if self.fov_ptg_side != side_fov: self._create_fov_ptg(side_fov) # Optimize note by JM Colley : # Instead of directly comparing the angle, use the comparison # of their cosine # scalar product between unit vector ptg fov and center earth position cos_ptg_earth = np.dot(self.fov_ptg, self._body_fov_xyz) # fov map is array test between cos(earth, ptg fov) and cos(limb) self.open_fov = cos_ptg_earth < self._cos_limb return self.open_fov
[docs] def compute_occultation_frac(self): """Compute occultation fraction in FOV, call open_fov_map() method before :param side_fov: integer, number of side pixels :return: earth fraction [0..1] :rtype: float """ earth_frac = 1.0 - np.sum(self.open_fov) / self.open_fov.size s_logger.debug(f'fraction of fov covered by Earth: {(100*earth_frac):.2f} %') return earth_frac
[docs]class EarthInFov(ExtentedBodyInFov): """Informs if Earth is in the filed of view of the ECLAIRs instrument. Inherits from ExtendedBodyinFov class. """ def __init__(self): """**Constructor** """ super().__init__() self._name = "earth" self._body_radius_km = 1e-3 * R_earth.value # km self._pos_sat = np.array([self._body_radius_km, 0, 0]) self._limb = None self._body_fov_xyz = None def _compute_dist_body_sat(self): """ Must be redefined in herit class with def of self._pos_body """ self._pos_body = -self._pos_sat super()._compute_dist_body_sat()
# Remove pragma: no cover when class is used.
[docs]class MoonInFov(ExtentedBodyInFov): # pragma: no cover """Informs if Moon is in the filed of view of the ECLAIRs instrument. Inherits from ExtendedBodyinFov class. """ def __init__(self): """ **Constructor** .. note :: Moon radius note implemented in astropy.constants """ super().__init__() self._name = "moon" self._body_radius_km = 1737.0 # km self._pos_sat = np.array([self._body_radius_km, 0, 0]) def _compute_dist_body_sat(self): """ Must be redefined in herit class with def of self._pos_body """ # TODO: position de la lune a t dans GCRS self._pos_body = "TBD" super()._compute_dist_body_sat()
# Remove pragma: no cover when class is used.
[docs]class SunInFov(ExtentedBodyInFov): # pragma: no cover """Informs if Sun is in the filed of view of the ECLAIRs instrument. Inherits from ExtendedBodyinFov class. """ def __init__(self): """ **Constructor** """ super().__init__() self._name = "sun" self._body_radius_km = 1e-3 * R_sun.value # km self._pos_sat = np.array([self._body_radius_km, 0, 0]) def _compute_dist_body_sat(self): """ Must be redefined in herit class with def of self._pos_body """ # TODO: position du soleil a t dans GCRS self._pos_body = "TBD" super()._compute_dist_body_sat()