Source code for irispy.meta
import textwrap
import numpy as np
import astropy.units as u
from astropy.coordinates import SkyCoord
from astropy.time import Time
from ndcube.meta import NDMeta
from sunpy.coordinates import Helioprojective
from sunraster.meta import RemoteSensorMetaABC, SlitSpectrographMetaABC
from irispy.utils.constants import SPECTRAL_BAND
__all__ = ["BaseMeta", "SGMeta", "SJIMeta"]
[docs]
class BaseMeta(NDMeta):
def __init__(self, header, **kwargs) -> None:
super().__init__(header, **kwargs)
def __repr__(self) -> str:
return f"{object.__repr__(self)}\n{self!s}"
def _construct_time(self, key):
val = self.get(key)
if val is not None:
val = Time(val, format="fits", scale="utc")
return val
@property
def fits_header(self):
return self._fits_header
@property
def spectral_window(self):
return self.get(f"TDESC{self._iwin}")
@property
def detector(self):
return self.get(f"TDET{self._iwin}")
@property
def instrument(self):
return self.get("INSTRUME")
@property
def observatory(self):
return self.get("TELESCOP")
@property
def processing_level(self):
return int(self.get("DATA_LEV"))
@property
def distance_to_sun(self):
return (self.get("DSUN_OBS") * u.m).to(u.AU)
@property
def date_reference(self):
return self._construct_time("DATE_OBS")
@property
def date_start(self):
return self.date_reference
@property
def date_end(self):
return self._construct_time("DATE_END")
@property
def observing_mode_id(self):
return int(self.get("OBSID"))
# ---------- IRIS-specific metadata properties ----------
@property
def observing_mode_description(self):
return self.get("OBS_DESC")
@property
def observing_campaign_start(self):
"""
Start time of observing campaign.
"""
return self._construct_time("STARTOBS")
@property
def observing_campaign_end(self):
"""
End time of observing mode.
"""
return self._construct_time("ENDOBS")
@property
def observation_includes_saa(self):
"""
Whether IRIS passed through SAA during observations.
"""
return bool(self.get("SAA"))
@property
def satellite_rotation(self):
"""
Satellite roll from solar north.
"""
return self.get("SAT_ROT") * u.deg
@property
def exposure_control_triggers_in_observation(self):
"""
Number of times automatic exposure control triggered during observing campaign.
"""
return self.get("AECNOBS")
@property
def exposure_control_triggers_in_raster(self):
"""
Number of times automatic exposure control was triggered during this raster.
"""
return self.get("AECNRAS")
@property
def number_of_unique_raster_positions(self):
"""
Number of unique positions in raster.
"""
return self.get("NRASTERP")
@property
def number_of_raster_positions(self):
"""
Number of positions in raster.
"""
return self.get("RASNRPT")
@property
def spectral_range(self):
"""
The spectral range of the spectral window.
"""
return [self.get(f"TWMIN{self._iwin}"), self.get(f"TWMAX{self._iwin}")] * u.AA
@property
def spectral_band(self):
"""
The spectral band of the spectral window.
"""
return SPECTRAL_BAND.get(self.spectral_window, self.spectral_window)
@property
def raster_fov_width_y(self):
"""
Width of the field of view of the raster in the Y (slit) direction.
"""
return self.get("FOVY") * u.arcsec
@property
def raster_fov_width_x(self):
"""
Width of the field of view of the raster in the X (rastering) direction.
"""
return self.get("FOVX") * u.arcsec
@property
def fov_center(self):
"""
Location of the center of the field of view.
"""
return SkyCoord(
Tx=self.get("XCEN"),
Ty=self.get("YCEN"),
unit=u.arcsec,
frame=Helioprojective,
)
@property
def automatic_exposure_control_enabled(self):
return bool(self.get("IAECFLAG"))
@property
def tracking_mode_enabled(self):
return bool(self.get("TR_MODE"))
@property
def observatory_at_high_latitude(self):
"""
Whether IRIS passed through high Earth latitude during observations.
"""
return bool(self.get("HLZ"))
@property
def spatial_summing_factor(self):
"""
Number of pixels summed together in the spatial (Y/slit) direction.
"""
return self.get("SUMSPAT")
@property
def spectral_summing_factor(self):
"""
Number of pixels summed together in the spectral direction.
"""
if "fuv" in self.detector.lower():
return self.get("SUMSPTRF")
return self.get("SUMSPTRN")
[docs]
class SJIMeta(BaseMeta, RemoteSensorMetaABC):
"""
Metadata class for IRIS slit-jaw images.
"""
def __init__(self, header, **kwargs) -> None:
super().__init__(header, **kwargs)
self._iwin = 1
self._fits_header = header
def __str__(self) -> str:
return textwrap.dedent(
f"""
SJIMeta
-------
Observatory: {self.observatory}
Instrument: {self.instrument}
Detector: {self.detector}
Spectral Window: {self.spectral_window}
Spectral Range: {self.spectral_range}
Spectral Band: {self.spectral_band}
Dimensions: {self.data_shape}
Date: {self.date_reference}
OBS ID: {self.observing_mode_id}
OBS Description: {self.observing_mode_description}
""",
)
@property
def spectral_window(self):
return super().spectral_window.replace("SJI_", "")
[docs]
class SGMeta(BaseMeta, SlitSpectrographMetaABC):
"""
Metadata class for IRIS slit spectrograph data.
"""
def __init__(self, header, spectral_window, **kwargs) -> None:
super().__init__(header, **kwargs)
spectral_windows = np.array([self[f"TDESC{i}"] for i in range(1, self["NWIN"] + 1)])
window_mask = np.array([spectral_window in window for window in spectral_windows])
if window_mask.sum() < 1:
msg = (
"Spectral window not found. "
f"Input spectral window: {spectral_window}; "
f"Spectral windows in header: {spectral_windows}"
)
raise ValueError(
msg,
)
if window_mask.sum() > 1:
msg = (
"Spectral window must be unique. "
f"Input spectral window: {spectral_window}; "
f"Ambiguous spectral windows in header: {spectral_windows[window_mask]}"
)
raise ValueError(
msg,
)
self._iwin = np.arange(len(spectral_windows))[window_mask][0] + 1
self._fits_header = header
def __str__(self) -> str:
return textwrap.dedent(
f"""
SGMeta
------
Observatory: {self.observatory}
Instrument: {self.instrument}
Detector: {self.detector}
Spectral Window: {self.spectral_window}
Spectral Range: {self.spectral_range}
Spectral Band: {self.spectral_band}
Dimensions: {self.data_shape}
Date: {self.date_reference}
OBS ID: {self.observing_mode_id}
OBS Description: {self.observing_mode_description}
""",
)