"""HST detection and wavelength-based tint."""
import logging
from typing import Any
from picmaker._rgb import BFUNC, GFUNC, RFUNC, RGB_BY_NM
logger = logging.getLogger(__name__)
[docs]
def detect_vicar(vic: Any) -> tuple[str, str, str] | None:
"""HST is not delivered as VICAR — always returns ``None``.
Parameters:
vic: A :class:`vicar.VicarImage` instance (unused).
Returns:
Always ``None``.
"""
return None
[docs]
def detect_fits(hdulist: Any) -> tuple[str, str, Any] | None:
"""Detect an HST FITS image.
The ``TELESCOP`` keyword identifies the host, ``INSTRUME`` (with an
optional ``DETECTOR`` suffix) identifies the instrument, and the
filter name comes from one of ``FILTER``, ``FILTER1``/``FILTER2``
(HST/ACS), or ``FILTNAM1``/``FILTNAM2`` (HST/WFPC2).
Parameters:
hdulist: An ``astropy.io.fits`` HDU list.
Returns:
``(inst_host, inst_id, filter_name)`` where ``filter_name`` may
be a string or a 2-tuple of strings, or ``None`` if the file is
not an HST FITS image.
"""
try:
inst_host = hdulist[0].header['TELESCOP']
except KeyError:
return None
try:
inst_id = hdulist[0].header['INSTRUME']
if 'DETECTOR' in hdulist[0].header:
inst_id += '/' + hdulist[0].header['DETECTOR']
except KeyError:
return None
filter_name: Any = None
try:
filter_name = hdulist[0].header['FILTER'].upper().strip()
except KeyError:
pass
try:
filter_name = (hdulist[0].header['FILTER1'], hdulist[0].header['FILTER2'])
except KeyError:
pass
try:
filter_name = (hdulist[0].header['FILTNAM1'], hdulist[0].header['FILTNAM2'])
except KeyError:
pass
return (inst_host, inst_id, filter_name)
[docs]
def matches(inst_host: str, inst_id: str) -> bool:
"""Host-level predicate: accept any host that mentions HUBBLE or HST.
Parameters:
inst_host: Instrument host string (e.g. ``'HST'`` or ``'HUBBLE
SPACE TELESCOPE'``).
inst_id: Instrument id.
Returns:
``True`` if either substring appears in ``inst_host``.
"""
return 'HUBBLE' in inst_host or 'HST' in inst_host
[docs]
def tint_for(inst_id: str, filter_name: Any) -> list[tuple[int, int, int]] | None:
"""Return the full ``[black, tint, white]`` colormap for an HST filter.
The tint color comes from inferring a wavelength out of the numeric
characters in ``filter_name`` (e.g. ``'F606W'`` → 606 nm) and looking
that wavelength up in :data:`picmaker._rgb.RGB_BY_NM` via the
:data:`picmaker._rgb.RFUNC` / :data:`picmaker._rgb.GFUNC` /
:data:`picmaker._rgb.BFUNC` splines. Each detector family has its
own correction:
* NICMOS scales the inferred number by 3.5 (its filter names encode
tens of nm rather than nm).
* WFC3/IR and ACS/SBC also scale by 3.5 when the inferred number is
below 200.
* WFPC2 quad-filters ``FQUV*`` and ``FQCH4*`` are pinned to 300 nm
and 900 nm respectively.
* NICMOS polarisers ``POL0S`` / ``POL0L`` are pinned to 110 nm and
220 nm before the NICMOS x3.5 scaling.
Broadband filters ``F350LP``, ``F606W``, and ``LONG_PASS`` short-
circuit to a plain ``[black, white]`` colormap.
Parameters:
inst_id: Instrument id, possibly with detector suffix (e.g.
``'WFC3/IR'``).
filter_name: HST filter name; passed through as-is from
:func:`detect_fits`.
Returns:
``[(0, 0, 0), (r, g, b), (255, 255, 255)]`` for a successfully
inferred wavelength, ``[(0, 0, 0), (255, 255, 255)]`` for the
broadband short-circuits, or ``None`` when no wavelength can be
inferred (a WARNING is logged).
"""
if filter_name in ('F350LP', 'F606W', 'LONG_PASS'):
return [(0, 0, 0), (255, 255, 255)]
wavelength: float = 0
for c in filter_name:
if '0' <= c <= '9' and wavelength < 1600:
wavelength = 10 * wavelength + int(c)
# Detector-specific wavelength scaling. The elif chain is kept as
# three separate branches (rather than `or`-combined) so future
# per-detector adjustments don't change the others' precedence.
if 'NIC' in inst_id: # noqa: SIM114
wavelength *= 3.5
elif ('WFC3' in inst_id or 'IR' in inst_id) and wavelength < 200: # noqa: SIM114
wavelength *= 3.5
elif ('ACS' in inst_id or 'SBC' in inst_id) and wavelength < 200:
wavelength *= 3.5
elif filter_name.startswith('FQUV'):
wavelength = 300
elif filter_name.startswith('FQCH4'):
wavelength = 900
elif filter_name == 'POL0S':
wavelength = 110 * 3.5
elif filter_name == 'POL0L':
wavelength = 220 * 3.5
if wavelength == 0:
logger.warning('Unknown HST filter: %s %s', inst_id, filter_name)
return None
wavelength = max(wavelength, RGB_BY_NM[0, 0])
wavelength = min(wavelength, RGB_BY_NM[-1, 0])
r = int(RFUNC(wavelength))
g = int(GFUNC(wavelength))
b = int(BFUNC(wavelength))
return [(0, 0, 0), (r, g, b), (255, 255, 255)]
__all__ = ['detect_fits', 'detect_vicar', 'matches', 'tint_for']