"""Predict / correct ionospheric RM in FITS images"""
# Follows report by Van Eck (2021) and
# Python implementation in FRion https://github.com/CIRADA-Tools/FRion/
# FRion License: MIT Copyright (c) 2021 Cameron Van Eck
from __future__ import annotations
from pathlib import Path
from typing import Any, NamedTuple
import astropy.units as u
import numpy as np
from astropy.coordinates import EarthLocation, SkyCoord
from astropy.io import fits
from astropy.time import Time, TimeDelta
from astropy.wcs import WCS
from spinifex.exceptions import FITSHeaderError
from spinifex.get_rm import RM, get_rm_from_skycoord
from spinifex.image_tools.image_tools import IntegratedRM, get_integrated_rm
from spinifex.logger import logger
[docs]
def get_rm_from_fits(
fits_path: Path,
timestep: u.Quantity = 15 * u.min,
iono_model_name: str = "ionex",
magnetic_model_name: str = "ppigrf",
**iono_kwargs: Any,
) -> RM:
"""Get the ionospheric RM from a FITS file
Parameters
----------
fits_path : Path
Path to FITS file
timestep : u.Quantity, optional
Time step to evaluate time-dependant RM, by default 15*u.min
iono_model_name : str, optional
ionospheric model name, by default "ionex". Must be a supported ionospheric model.
magnetic_model_name : str, optional
geomagnetic model name, by default "ppigrf". Must be a supported geomagnetic model.
iono_kwargs : dict
Keyword arguments for the ionospheric model
Returns
-------
RM
Rotation measure object
"""
fits_metadata = get_metadata_from_fits(fits_path=fits_path)
start_time = fits_metadata.start_time
duration = fits_metadata.duration
end_time = start_time + duration
times_mjd = np.arange(
start=start_time.mjd, stop=end_time.mjd, step=timestep.to(u.day).value
)
times = Time(times_mjd, format="mjd")
return get_rm_from_skycoord(
loc=fits_metadata.location,
times=times,
source=fits_metadata.source,
iono_model_name=iono_model_name,
magnetic_model_name=magnetic_model_name,
**iono_kwargs,
)
[docs]
def get_freq_from_fits(fits_path: Path) -> u.Quantity:
"""Get frequency array from FITS file
Parameters
----------
fits_path : Path
Path to FITS file
Returns
-------
u.Quantity
Frequency array
Raises
------
FITSHeaderError
If no spectral axis is found
"""
header = fits.getheader(fits_path)
if "SPECSYS" not in header:
msg = "Could not find `SPECSYS` in header, assuming `TOPOCENT`"
logger.warning(msg)
header["SPECSYS"] = "TOPOCENT"
wcs = WCS(header)
if not wcs.has_spectral:
msg = "FITS header has no spectral axis"
raise FITSHeaderError(msg)
specral_wcs = wcs.spectral
return specral_wcs.pixel_to_world(np.arange(*specral_wcs.array_shape))
[docs]
def get_integrated_rm_from_fits(
fits_path: Path,
timestep: u.Quantity = 15 * u.min,
iono_model_name: str = "ionex",
magnetic_model_name: str = "ppigrf",
**iono_kwargs: Any,
) -> IntegratedRM:
"""Computed the integrated RM effect following Van Eck (2021)
Parameters
----------
fits_path : Path
Path to FITS file
timestep : u.Quantity, optional
Timestep to use for computing time-dependent RM, by default 15*u.min
iono_model_name : str, optional
ionospheric model name, by default "ionex". Must be a supported ionospheric model.
magnetic_model_name : str, optional
geomagnetic model name, by default "ppigrf". Must be a supported geomagnetic model.
iono_kwargs : dict
keyword arguments for the ionospheric model
Returns
-------
IntegratedRM
Integrated rotation measure object
"""
# Get the time-dependent RM
time_dep_rm = get_rm_from_fits(
fits_path=fits_path,
timestep=timestep,
iono_model_name=iono_model_name,
magnetic_model_name=magnetic_model_name,
**iono_kwargs,
)
freq_arr = get_freq_from_fits(fits_path)
return get_integrated_rm(
time_dep_rm=time_dep_rm,
freq_arr=freq_arr,
)
[docs]
def correct_fits_images(
stokes_q_path: Path,
stokes_u_path: Path,
integrated_rm: IntegratedRM,
ext: int = 0,
suffix: str = "fr_corr",
) -> None:
msg = "TODO: Implement correct_fits_images"
raise NotImplementedError(msg)