|
| 1 | +"""Culling for ULTRA L1c.""" |
| 2 | + |
| 3 | +import astropy_healpix.healpy as hp |
| 4 | +import numpy as np |
| 5 | +from numpy.typing import NDArray |
| 6 | + |
| 7 | +from imap_processing.spice.geometry import ( |
| 8 | + SpiceBody, |
| 9 | + SpiceFrame, |
| 10 | + imap_state, |
| 11 | +) |
| 12 | + |
| 13 | + |
| 14 | +def compute_culling_mask( |
| 15 | + et: NDArray, |
| 16 | + keepout_radius_km: float, |
| 17 | + observer: SpiceBody = SpiceBody.EARTH, |
| 18 | + nside: int = 128, |
| 19 | + nested: bool = False, |
| 20 | +) -> tuple[NDArray, NDArray]: |
| 21 | + """ |
| 22 | + Compute a mask for HEALPix pixels within a keep-out radius of the target body. |
| 23 | +
|
| 24 | + Parameters |
| 25 | + ---------- |
| 26 | + et : NDArray |
| 27 | + Ephemeris times in TDB seconds past J2000. |
| 28 | + keepout_radius_km : float |
| 29 | + Radius (in km) within which HEALPix pixels will be excluded. |
| 30 | + observer : SpiceBody, optional |
| 31 | + Body from which IMAP is observed. |
| 32 | + nside : int, optional |
| 33 | + HEALPix NSIDE resolution. Default is 128. |
| 34 | + nested : bool, optional |
| 35 | + Whether to use NESTED indexing. |
| 36 | +
|
| 37 | + Returns |
| 38 | + ------- |
| 39 | + mask : tuple[NDArray, NDArray] |
| 40 | + Boolean array of shape (len(et), npix). |
| 41 | + unit_target_vecs : NDArray |
| 42 | + Unit vectors from IMAP to the target body |
| 43 | + (e.g., Earth), shape (len(et), 3). |
| 44 | + """ |
| 45 | + # Compute number of HEALPix pixels |
| 46 | + npix = hp.nside2npix(nside) |
| 47 | + |
| 48 | + # Compute IMAP to Earth position in the pointing frame. |
| 49 | + state = imap_state(et, ref_frame=SpiceFrame.IMAP_DPS, observer=observer) |
| 50 | + # Flip to get vector from IMAP to Earth |
| 51 | + # position.shape = (len(et), 3) |
| 52 | + position = -state[:, :3] |
| 53 | + |
| 54 | + # Distance from IMAP to target (e.g. Earth) (km): |
| 55 | + # distance.shape = (len(et),) |
| 56 | + distance = np.linalg.norm(position, axis=1) # shape (len(et),) |
| 57 | + |
| 58 | + # Calculate the keepout angle (radians). |
| 59 | + # keepout_angle.shape = (len(et),) |
| 60 | + keepout_angle = np.arcsin(keepout_radius_km / distance) # radians |
| 61 | + |
| 62 | + # Calculate the direction from IMAP to Earth. (shape: [N, 3]) |
| 63 | + # unit_target_vecs.shape = (len(et), 3) |
| 64 | + unit_target_vecs = position / distance[:, np.newaxis] |
| 65 | + |
| 66 | + # Get pixel unit vectors pointing from the center of the |
| 67 | + # HEALPix sphere to the center of each pixel on the sky. |
| 68 | + pixel_vecs = np.column_stack( |
| 69 | + hp.pix2vec(nside, np.arange(npix), nest=nested) |
| 70 | + ) # shape: (npix, 3) |
| 71 | + |
| 72 | + # Returns cos(theta) where theta is the separation angle between: |
| 73 | + # (1) vector from IMAP to Earth |
| 74 | + # (2) vector from IMAP to HEALPix pixel center |
| 75 | + # If theta is within the keepout angle, then the pixel is culled. |
| 76 | + cos_sep = np.dot(unit_target_vecs, pixel_vecs.T) # shape (N, npix) |
| 77 | + cos_sep = np.clip(cos_sep, -1.0, 1.0) |
| 78 | + # Get theta here. |
| 79 | + sep_angle = np.arccos(cos_sep) |
| 80 | + |
| 81 | + # Exclude pixels within the keepout angle. |
| 82 | + # mask.shape = (len(et), npix) |
| 83 | + mask = sep_angle > keepout_angle[:, np.newaxis] |
| 84 | + |
| 85 | + return mask, unit_target_vecs |
0 commit comments