Commit 32310f93 authored by vertighel's avatar vertighel
Browse files

Fase 2 templates: fillheader.py, chiavi FITS per i 3 canali + fix FILTER



Richiesto direttamente dall'utente (bug osservativo + necessita' di
nuove chiavi ora che esistono 3 canali, non solo l'imaging originale):

- FILTER: su cam1/STX il campo mostrava il codice breve del firmware
  ("u") invece del nome completo configurato ("SDSS u") -- stx.py
  scarica il FITS grezzo via HTTP cosi' com'e' dalla camera, nessuno a
  valle sovrascriveva FILTER. Ora fillheader.py lo sovrascrive con
  filter_name[cam.filter] solo per cam1; cam2/cam3 (nessuna filter
  wheel reale) restano vuoti, non "Undef.".
- HIERARCH STAGE NAMED / HIERARCH STAGE POS: letti da stage.named /
  stage.position (mercury.py) -- stato reale dello switchyard,
  indipendente da quale camera ha scattato. NAMED vuoto (non "None")
  se fuori tolleranza.
- DETECTOR eredita il nome camera gia' presente in INSTRUME (scritto
  dal firmware per STX, da Python in atik.py/stl.py) prima che
  INSTRUME diventi fisso "Cerbero". Nessuna modifica ai 3 device file
  necessaria per questa coppia.
- OBSTYPE (Imaging/Spectroscopy/Echelle) e OBSERVER (Cerbero {canale}
  operator) da una nuova tabella _CHANNEL_INFO per camera_name,
  centralizzata in fillheader.py invece che duplicata in stx.py/
  atik.py/stl.py (fillheader.py conosce gia' camera_name e gia' scrive
  chiavi condizionali per canale) -- deciso con l'utente, che
  inizialmente proponeva i device file.
- Fixato en passant lo stesso bug del default camera "cam" ->
  inesistente, gia' visto in observation.py -> "cam1".
- PLAN.md aggiornato.

Co-Authored-By: default avatarClaude Sonnet 5 <noreply@anthropic.com>
parent 4c1b9121
Loading
Loading
Loading
Loading
+41 −0
Original line number Diff line number Diff line
@@ -217,6 +217,47 @@ because a file is open for another reason; re-scope explicitly first.
- [ ] `fillheader.py:212-217` swallows FITS-writing errors silently
      (logs only, no `self.error`, no `return`, falls through to save
      anyway) — align with the standard error pattern. In scope.
- [x] Real observatory fixes/features requested directly by the user
      (not from the Phase-0 audit), all in `fillheader.py`, all in
      scope:
      - **FILTER bug**: on the imaging channel (cam1/STX), `FILTER`
        showed the camera firmware's short code (`"u"`) instead of the
        configured full name (`"SDSS u"`, from `config/constants.py`'s
        `filter_name`) — `stx.py`'s `download()` copies the raw FITS
        straight from the camera's HTTP endpoint, firmware header and
        all, and nothing downstream ever overwrote `FILTER`. Now
        `fillheader.py` overwrites it with `filter_name[cam.filter]`
        for cam1 only; cam2/cam3 (no real filter wheel) get a blank
        value (`_FILTER_CAMERAS = {"cam1"}`), not a placeholder like
        `"Undef."`.
      - **New channel-generic keys** (fillheader.py predates the
        spectro/cam2/Atik and echelle/cam3/STL channels, only imaging
        existed when it was written): `INSTRUME` is now always
        `"Cerbero"`; `DETECTOR` takes over the camera model name that
        used to be in `INSTRUME` (already present in the file by the
        time `fillheader.py` runs, whether firmware-written for STX or
        Python-written in `atik.py`/`stl.py`'s own `download()`, so no
        device-file changes needed for this pair); `OBSTYPE`
        (`"Imaging"`/`"Spectroscopy"`/`"Echelle"`) and `OBSERVER`
        (`"Cerbero {imaging,spectro,echelle} operator"`) come from one
        new `_CHANNEL_INFO` dict keyed by `camera_name` — centralized
        in `fillheader.py` rather than duplicated into
        `stx.py`/`atik.py`/`stl.py`, since `fillheader.py` already
        knows `camera_name` and already branches per-key on it; three
        device files hardcoding their own channel identity would be
        the same information in four places to keep in sync (decided
        with the user, who originally suggested the device files).
      - **`HIERARCH STAGE NAMED`/`HIERARCH STAGE POS`**: read directly
        from the `stage` device (`mercury.py`'s `Stage.named`/
        `.position`) — the switchyard's own reported state, kept as a
        diagnostic independent of which camera actually took the
        frame. Blank (not `"None"`) if `.named` is out of tolerance.
      - Fixed in passing: `camera_name = params.get("camera") or
        "cam"` had the same broken-default bug as `observation.py`
        (`"cam"` doesn't exist, only `cam1`/`cam2`/`cam3`) — changed to
        `"cam1"`. Never hit in practice since the only caller
        (`snapshot.py`) always passes `"camera"` explicitly, but wrong
        regardless.
- [ ] Add docstrings to `fillheader.py`, `focus.py`, `observation.py`/
      `snapshot.py` (the in-scope subset of the fuller list below). In
      scope.
+46 −3
Original line number Diff line number Diff line
@@ -8,13 +8,26 @@ from astropy.io import fits
from astropy.time import Time

# Other templates
from ..config.constants import alt, lat, lon, pixscale, rotangle, temp_fits
from ..config.constants import (alt, filter_name, lat, lon, pixscale,
                                rotangle, temp_fits)
from .. import devices as _dev
from ..devices import dom, foc, lamp, light, rot, tel, tel_temp
from ..devices import dom, foc, lamp, light, rot, stage, tel, tel_temp
from ..utils.logger import log
from ..utils.structure import save_filename
from .basetemplate import BaseTemplate

# Camera whose FILTER is meaningful to record (imaging/STX has a real
# filter wheel; spectro/Atik and echelle/STL don't, FILTER stays blank).
_FILTER_CAMERAS = {"cam1"}

# camera_name -> per-channel header values (INSTRUME/DETECTOR use the
# camera's own model name already in the file, no lookup needed here).
_CHANNEL_INFO = {
    "cam1": {"obstype": "Imaging", "observer": "Cerbero imaging operator"},
    "cam2": {"obstype": "Spectroscopy", "observer": "Cerbero spectro operator"},
    "cam3": {"obstype": "Echelle", "observer": "Cerbero echelle operator"},
}


class Template(BaseTemplate):
    def __init__(self):
@@ -49,7 +62,7 @@ class Template(BaseTemplate):
        ##### Header update #####
        #########################

        camera_name = params.get("camera") or "cam"
        camera_name = params.get("camera") or "cam1"
        cam = getattr(_dev, camera_name)
        filename = params.get("fits_file") or temp_fits
        gps = Time(utc, format="unix")
@@ -75,6 +88,36 @@ class Template(BaseTemplate):
                    ).isot, "(UTC) Date the file was written"
                    # log.debug(f"CONST  keys end {(Time.now()-now).sec.item() :.2f}")

                    #################################
                    ##### Channel/detector info #####
                    #################################

                    # DETECTOR takes over the camera model name the device
                    # already wrote as INSTRUME (firmware-baked for STX,
                    # set in Python for atik.py/stl.py); INSTRUME becomes
                    # the fixed instrument name.
                    hdu.header['detector'] = hdu.header.get(
                        'INSTRUME', ''), "Detector/camera name"
                    hdu.header['instrume'] = "Cerbero", "Instrument name"

                    channel = _CHANNEL_INFO.get(camera_name, {})
                    hdu.header['obstype'] = channel.get(
                        'obstype', ''), "Observation type (channel)"
                    hdu.header['observer'] = channel.get(
                        'observer', ''), "Operator name"

                    # Overrides whatever the camera firmware/SDK wrote:
                    # STX bakes its own short filter code (e.g. "u") into
                    # the raw FITS, we want the config's full name (e.g.
                    # "SDSS u") instead. Blank on cameras with no real
                    # filter wheel rather than a placeholder like "Undef.".
                    hdu.header['filter'] = (
                        filter_name.get(cam.filter, '')
                        if camera_name in _FILTER_CAMERAS else ''), "Filter name"

                    hdu.header['hierarch STAGE NAMED'] = stage.named or '', "Stage named position at start"
                    hdu.header['hierarch STAGE POS'] = stage.position, "[mm] Stage position at start"

                    ########################
                    ##### Cabinet info #####
                    ########################