Source code for coscon.fits_helper

from __future__ import annotations

from dataclasses import dataclass
from functools import cached_property
from logging import getLogger
from pathlib import Path
from typing import List

import defopt
import healpy as hp
import numpy as np
import pandas as pd
from astropy.io import fits
from astropy.io.fits.hdu.table import BinTableHDU
from tabulate import tabulate

import coscon.cmb

logger = getLogger('coscon')


[docs]@dataclass class BaseFitsHelper: """A class for general fits files""" path: Path memmap: bool = True name: str = '' def __post_init__(self): self.path = Path(self.path) if not self.name: self.name = self.path.stem def __str__(self) -> str: return '\n\n'.join((self.__info_str__, self.__infos_str__)) def _repr_html_(self) -> str: return ''.join((self.__info_html__, self.__infos_html__)) @cached_property def info(self) -> pd.DataFrame: with fits.open(self.path, memmap=self.memmap) as file: return pd.DataFrame( (tup[:7] for tup in file.info(output=False)), columns=['No.', 'Name', 'Ver', 'Type', 'Cards', 'Dimensions', 'Format'], ) @cached_property def __info_str__(self) -> str: return tabulate(self.info, tablefmt='psql', headers="keys", showindex=False) @cached_property def __info_html__(self) -> str: return self.info.to_html(index=False) @cached_property def infos(self) -> List[pd.DataFrame]: with fits.open(self.path, memmap=self.memmap) as file: return [pd.Series(datum.header).to_frame(datum.name) for datum in file] @cached_property def __infos_str__(self) -> str: return '\n\n'.join(tabulate(df, tablefmt='psql', headers="keys") for df in self.infos) @cached_property def __infos_html__(self) -> str: return ''.join(df.to_html() for df in self.infos)
[docs]@dataclass class CMBFitsHelper(BaseFitsHelper): """Specialized in fits container typically used in CMB analysis""" @cached_property def n_maps(self) -> int: """Return total number of maps assuming it is a CMB fits """ with fits.open(self.path, memmap=self.memmap) as file: for f in file: if type(f) is BinTableHDU: return f.header['TFIELDS'] raise TypeError(f"Is {self.path} really contains CMB maps?") @cached_property def names(self) -> List[str]: """Get names of maps """ with fits.open(self.path, memmap=self.memmap) as file: for f in file: if type(f) is BinTableHDU: # return [key for key in f.header if key.startswith('TTYPE')] return [f.header[f'TTYPE{i}'] for i in range(1, self.n_maps + 1)] raise TypeError(f"Is {self.path} really contains CMB maps?") @cached_property def maps(self) -> np.ndarray: """Return a 2D healpix map array. where the first dimension is the i-th map """ n_maps = self.n_maps # force 2D array # healpy tried to be smart and return 1D array only if there's only 1 map return ( hp.ma(hp.read_map(self.path)).reshape(1, -1) ) if n_maps == 1 else ( hp.ma(hp.read_map(self.path, field=range(n_maps))) ) @property def to_maps(self) -> coscon.cmb.Maps: """package data in a Maps object""" return coscon.cmb.Maps(self.names, self.maps, name=self.name)
def _fits_info(*filenames: Path): """Print a summary of the HDUs in a FITS file(s). :param Path filenames: Path to one or more FITS files. Wildcards are supported. """ for filename in filenames: print(BaseFitsHelper(filename))
[docs]def fits_info_cli(): defopt.run(_fits_info)