Source code for pyobs.utils.fits
"""
TODO: write doc
"""
__title__ = "FITS utilities"
import logging
import re
from typing import Any, cast, Callable
from astropy.io import fits
from numpy.typing import NDArray
from pyobs.utils.time import Time
log = logging.getLogger(__name__)
[docs]
def fitssec(hdu: Any, keyword: str = "TRIMSEC") -> NDArray[Any]:
"""Trim an image to TRIMSEC or BIASSEC.
Args:
hdu: HDU to take data from.
keyword: Header keyword for section.
Returns:
Numpy array with image data.
"""
# keyword not given?
if keyword not in hdu.header:
# return whole data
return cast(NDArray[Any], hdu.data)
# get value of section
sec = hdu.header[keyword]
# split values
s = sec[1:-1].split(",")
x = s[0].split(":")
y = s[1].split(":")
x0 = int(x[0]) - 1
x1 = int(x[1])
y0 = int(y[0]) - 1
y1 = int(y[1])
# return data
return cast(NDArray[Any], hdu.data[y0:y1, x0:x1])
[docs]
class FilenameFormatter:
def __init__(self, fmt: str | list[str], keys: dict[str, int | float | str] | None = None):
"""Initializes a new filename formatter.
Args:
fmt: Filename format or list of formats. If list is given, first valid one is used.
keys: Additional keys to pass to the formatter.
"""
self.format = fmt
self.keys: dict[str, int | float | str] = {} if keys is None else keys
# define functions
self.funcs: dict[str, Callable[..., str]] = {
"lower": self._format_lower,
"time": self._format_time,
"date": self._format_date,
"filter": self._format_filter,
"string": self._format_string,
"type": self._format_type,
}
def _value(self, hdr: fits.Header, key: str) -> int | float | str:
"""Returns value for given key.
Args:
hdr: fits.Header to take value from.
key: Key to return value for.
Returns:
Value for given key.
"""
# first check keys
if key in self.keys:
return self.keys[key]
# then check header
return cast(int | float | str, hdr[key])
def __call__(self, hdr: fits.Header) -> str:
"""Formats a filename given a format template and a FITS header.
Args:
hdr: FITS header to take values from.
Returns:
Formatted filename.
Raises:
KeyError: If either keyword could not be found in header or method could not be found.
"""
# make fmt a list
if self.format is None:
return ""
if isinstance(self.format, str):
self.format = [self.format]
# loop formats
for f in self.format:
try:
# find all placeholders in format
placeholders = re.findall(r"\{[\w\d_-]+(?:\|[\w\d_-]+\:?(?:[\w\d_-]+)*)?\}", f)
if len(placeholders) == 0:
return f
# create output
output = f
# loop all placeholders
for ph in placeholders:
# call method and replace
output = output.replace(ph, str(self._format_placeholder(ph, hdr)))
# finished
return output
except KeyError:
# this format didn't work
pass
# still here?
raise KeyError("No valid format found.")
def _format_placeholder(self, placeholder: str, hdr: fits.Header) -> str:
"""Format a given placeholder.
Args:
placeholder: Placeholder to format.
hdr: FITS header to take values from.
Returns:
Formatted placeholder.
Raises:
KeyError: If method or FITS header keyword don't exist.
"""
# remove curly brackets
key = placeholder[1:-1]
method = None
params = []
# do we have a pipe in here?
if "|" in key:
key, method = key.split("|")
# parameters for method?
if method is not None and ":" in method:
method, *params = method.split(":")
# if no method is given, just replace
if method is None:
return str(self._value(hdr, key))
else:
# get function (may raise KeyError)
func = self.funcs[method]
# call method and replace
return func(hdr, key, *params)
def _format_lower(self, hdr: fits.Header, key: str) -> str:
"""Sets a given string to lowercase.
Args:
hdr: FITS header to take values from.
key: The name of the FITS header key to use.
Returns:
Formatted string.
"""
return str(self._value(hdr, key)).lower()
def _format_time(self, hdr: fits.Header, key: str, delimiter: str = "-") -> str:
"""Formats time using the given delimiter.
Args:
hdr: FITS header to take values from.
key: The name of the FITS header key to use.
delimiter: Delimiter for time formatting.
Returns:
Formatted string.
"""
fmt = "%H" + delimiter + "%M" + delimiter + "%S"
date_obs = Time(self._value(hdr, key))
return str(date_obs.datetime.strftime(fmt))
def _format_date(self, hdr: fits.Header, key: str, delimiter: str = "-") -> str:
"""Formats date using the given delimiter.
Args:
hdr: FITS header to take values from.
key: The name of the FITS header key to use.
delimiter: Delimiter for date formatting.
Returns:
Formatted string.
"""
fmt = "%Y" + delimiter + "%m" + delimiter + "%d"
date_obs = Time(self._value(hdr, key))
return str(date_obs.datetime.strftime(fmt))
def _format_filter(self, hdr: fits.Header, key: str, image_type: str = "IMAGETYP", prefix: str = "-") -> str:
"""Formats a filter, prefixed by a given separator, only if the image type requires it.
Args:
hdr: FITS header to take values from.
key: The name of the FITS header key to use.
image_type: FITS header key for IMAGETYP.
prefix: Prefix to add to filter.
Returns:
Formatted string.
"""
it = hdr[image_type].lower()
if it in ["light", "object", "flat", "skyflat"]:
return prefix + str(self._value(hdr, key))
else:
return ""
def _format_string(self, hdr: fits.Header, key: str, fmt: str) -> str:
"""Formats a string using Python string substitution.
Args:
hdr: FITS header to take values from.
key: The name of the FITS header key to use.
fmt: A Python string format like %d, %05d, or %4.1f.
Returns:
Formatted string.
"""
fmt = "%" + fmt
return str(fmt % self._value(hdr, key))
def _format_type(self, hdr: fits.Header, key: str) -> str:
"""Formats an image type to a one-letter code.
Args:
hdr: FITS header to take values from.
key: The name of the FITS header key to use.
Returns:
Formatted string.
"""
if self._value(hdr, key) == "bias":
return "b"
elif self._value(hdr, key) == "skyflat":
return "f"
elif self._value(hdr, key) == "dark":
return "d"
else:
return "e"
[docs]
def format_filename(hdr: fits.Header, fmt: str | list[str], keys: dict[str, Any] | None = None) -> str:
"""Formats a filename given a format template and a FITS header.
Args:
hdr: FITS header to take values from.
fmt: Filename format or list of formats. If multiple formats are given, the first valid one is used.
keys: Additional keys to pass to the format string.
Returns:
Filename
Raises:
KeyError: If either keyword could not be found in header or method could not be found.
"""
ff = FilenameFormatter(fmt, keys)
return ff(hdr)
__all__ = ["format_filename", "FilenameFormatter", "fitssec"]