Source code for bioniumx.spectra.reflectance

"""bioniumx.spectra.reflectance

Lightweight data structures for directly imaged planet *surface/reflectance*
spectra.

The core library currently supports transmission and emission spectra.
This module introduces a minimal ReflectanceSpectrum container so that we can
attach surface/biosignature spectral models to directly-imaged data.
"""

from __future__ import annotations

import numpy as np

from bioniumx.core import BioniumXObject


[docs] class ReflectanceSpectrum(BioniumXObject): """Directly imaged planet surface reflectance (albedo) spectrum. Parameters ---------- wavelength : array-like Wavelength in microns. reflectance : array-like Surface reflectance / albedo (dimensionless). err : array-like, optional 1-sigma uncertainty on reflectance. If None, set to zeros. target_name : str, optional Planet identifier. instrument : str, optional Instrument name. resolution : float, optional Resolving power R = λ/Δλ. meta : dict, optional Additional metadata. """ _required_attrs = ["wavelength", "reflectance"] _metadata_attrs = ["target_name", "instrument", "resolution"]
[docs] def __init__( self, wavelength, reflectance, err=None, target_name: str = "Unknown", instrument: str = "Unknown", resolution: float | None = None, meta: dict | None = None, gti: list | None = None, ): self.wavelength = np.asarray(wavelength, dtype=float) self.reflectance = np.asarray(reflectance, dtype=float) if err is not None: self.err = np.asarray(err, dtype=float) else: self.err = np.zeros_like(self.reflectance) self.meta = { "target_name": target_name, "instrument": instrument, "resolution": resolution, } if meta: self.meta.update(meta) self.gti = gti self._validate()
def apply_wavelength_mask(self, wl_min: float, wl_max: float): mask = (self.wavelength >= wl_min) & (self.wavelength <= wl_max) return ReflectanceSpectrum( wavelength=self.wavelength[mask], reflectance=self.reflectance[mask], err=self.err[mask], target_name=self.meta.get("target_name", "Unknown"), instrument=self.meta.get("instrument", "Unknown"), resolution=self.meta.get("resolution", None), meta={k: v for k, v in self.meta.items() if k not in {"target_name", "instrument", "resolution"}}, ) def rebin(self, factor: int): n = len(self.wavelength) // factor * factor wl = self.wavelength[:n].reshape(-1, factor).mean(axis=1) r = self.reflectance[:n].reshape(-1, factor).mean(axis=1) err = np.sqrt((self.err[:n].reshape(-1, factor) ** 2).sum(axis=1)) / factor return ReflectanceSpectrum( wl, r, err=err, target_name=self.meta.get("target_name", "Unknown"), instrument=self.meta.get("instrument", "Unknown"), resolution=self.meta.get("resolution", None), meta={k: v for k, v in self.meta.items() if k not in {"target_name", "instrument", "resolution"}}, ) def snr(self) -> np.ndarray: with np.errstate(divide="ignore", invalid="ignore"): return np.where(self.err > 0, self.reflectance / self.err, np.inf) def plot(self, ax=None, **kwargs): import matplotlib.pyplot as plt if ax is None: _, ax = plt.subplots(figsize=(10, 4)) ax.errorbar(self.wavelength, self.reflectance, yerr=self.err, fmt="o", ms=3, **kwargs) ax.set_xlabel("Wavelength (μm)") ax.set_ylabel("Reflectance / Albedo") ax.set_title(self.meta.get("target_name", "")) return ax