Source code for astropy.units.format.fits

# Licensed under a 3-clause BSD style license - see LICENSE.rst

"""
Handles the "FITS" unit format.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

from astropy.units.errors import UnitScaleError
from astropy.utils import classproperty

from . import core, generic, utils

if TYPE_CHECKING:
    from typing import Literal

    from astropy.units import UnitBase


[docs] class FITS(generic.Generic): """ The FITS standard unit format. This supports the format defined in the Units section of the `FITS Standard <https://fits.gsfc.nasa.gov/fits_standard.html>`_. """ @classproperty(lazy=True) def _units(cls) -> dict[str, UnitBase]: from astropy import units as u # add some units up-front for which we don't want to use prefixes # and that have different names from the astropy default. names = {"Celsius": u.deg_C, "deg C": u.deg_C} bases = [ "m", "g", "s", "rad", "sr", "K", "A", "mol", "cd", "Hz", "J", "W", "V", "N", "Pa", "C", "Ohm", "S", "F", "Wb", "T", "H", "lm", "lx", "a", "yr", "eV", "pc", "Jy", "mag", "R", "bit", "byte", "G", "barn", ] # fmt: skip prefixes = [ "y", "z", "a", "f", "p", "n", "u", "m", "c", "d", "", "da", "h", "k", "M", "G", "T", "P", "E", "Z", "Y", ] # fmt: skip special_cases = {"dbyte": u.Unit("dbyte", 0.1 * u.byte)} for key, _ in utils.get_non_keyword_units(bases, prefixes): names[key] = special_cases[key] if key in special_cases else getattr(u, key) simple_units = [ "deg", "arcmin", "arcsec", "mas", "min", "h", "d", "Ry", "solMass", "u", "solLum", "solRad", "AU", "lyr", "count", "ct", "photon", "ph", "pixel", "pix", "D", "Sun", "chan", "bin", "voxel", "adu", "beam", "erg", "Angstrom", "angstrom", ] # fmt: skip names.update((unit, getattr(u, unit)) for unit in simple_units) return names @classmethod def _parse_unit(cls, unit: str, detailed_exception: bool = True) -> UnitBase: cls._validate_unit(unit, detailed_exception=detailed_exception) return cls._units[unit]
[docs] @classmethod def to_string( cls, unit: UnitBase, fraction: bool | Literal["inline"] = False ) -> str: # Remove units that aren't known to the format unit = cls._decompose_to_known_units(unit) parts = [] base = np.log10(unit.scale) if base % 1.0 != 0.0: raise UnitScaleError( "The FITS unit format is not able to represent scales " "that are not powers of 10. Multiply your data by " f"{unit.scale:e}." ) elif unit.scale != 1.0: # We could override format_exponential_notation to set the # scale factor but that would give the wrong impression that # all values in FITS are set that way. So, instead do it # here, and use a unity-scale unit for the rest. parts.append(f"10**{int(base)}") unit = core.CompositeUnit(1, unit.bases, unit.powers) if unit.bases: parts.append(super().to_string(unit, fraction=fraction)) return cls._scale_unit_separator.join(parts)
[docs] @classmethod def parse(cls, s: str, debug: bool = False) -> UnitBase: result = super().parse(s, debug) if hasattr(result, "function_unit"): raise ValueError("Function units are not yet supported for FITS units.") return result