Commit 89242039 authored by vertighel's avatar vertighel
Browse files

Fase 2 templates: nome file FITS con canale, docstring, rimosso blocco WCS



- utils/structure.py: save_filename() accetta channel="", inserito tra
  FILE_PREFIX e il timestamp (OARPAF.spectro.2026-...fits invece di
  OARPAF.2026-...fits); ':' nel timestamp ISO sostituiti con '_'
  (poco amichevoli per filesystem/tool). FILE_PREFIX resta "OARPAF"
  (deciso con l'utente: nome del sito, non dello strumento, ed e'
  condiviso con current_log_path()/foc_path(), non solo l'output FITS).
- fillheader.py: _CHANNEL_INFO ha ora anche "channel"
  (imaging/spectro/echelle), passato a save_filename().
- Blocco WCS commentato in fillheader.py rimosso dall'utente
  direttamente.
- Docstring aggiunte a fillheader.py/focus.py/snapshot.py, stile
  leggero (Constructor/Template implementation) gia' usato in
  flat.py/skyflat.py.
- PLAN.md aggiornato: fase 2 (scope: snapshot/observation/fillheader/
  focus) chiusa.

Co-Authored-By: default avatarClaude Sonnet 5 <noreply@anthropic.com>
parent 32310f93
Loading
Loading
Loading
Loading
+26 −4
Original line number Diff line number Diff line
@@ -258,12 +258,34 @@ because a file is open for another reason; re-scope explicitly first.
        `"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`/
- [x] Add docstrings to `fillheader.py`, `focus.py`, `observation.py`/
      `snapshot.py` (the in-scope subset of the fuller list below). In
      scope.
      scope. Added the lightweight class + `content()` one-liners
      already used by `flat.py`/`skyflat.py` (`"""Constructor"""` on
      `__init__`, one-line class docstring, `"""Template
      implementation"""` on `content()`) — not full numpy
      Parameters/Returns blocks, to match the established per-template
      convention rather than the fuller one used for `devices/*.py`.
- [x] Real feature requested directly by the user while reviewing
      `fillheader.py` (not from the Phase-0 audit): output FITS
      filenames gain a channel tag and a filesystem-safer timestamp.
      `utils/structure.py:save_filename()` gained a `channel=""`
      parameter, inserted between `FILE_PREFIX` and the timestamp
      (`OARPAF.spectro.2026-...fits` instead of `OARPAF.2026-...fits`);
      the ISO timestamp's `:` are replaced with `_`
      (filesystem/tool-unfriendly otherwise). `fillheader.py`'s
      `_CHANNEL_INFO` dict gained a `"channel"` field
      (`"imaging"`/`"spectro"`/`"echelle"`) passed through at the
      `save_filename()` call site. `FILE_PREFIX` itself stays
      `"OARPAF"` (user's call — considered renaming it to `"CERBERO"`
      to match the new `INSTRUME` value, decided against: `OARPAF`
      names the observatory site, `Cerbero` the instrument, and
      `FILE_PREFIX` is shared with `current_log_path()`/`foc_path()`,
      not just FITS output).
- [ ] Remove dead/commented-out blocks: `fillheader.py` (large
      commented WCS block), `observation.py:28-33` (the in-scope
      subset of the fuller list below). In scope.
      commented WCS block) — user is removing this one directly, not
      Claude. `observation.py:28-33` is moot, that file is gone (see
      above).
- [ ] *(deferred)* Delete `testsonoff.py`.
- [ ] *(deferred)* Unify error handling: most templates use
      `except KeyError: log.error(...)` without `self.error.append()`
+12 −18
Original line number Diff line number Diff line
@@ -20,22 +20,27 @@ from .basetemplate import BaseTemplate
# 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).
# camera_name -> per-channel header values and output filename tag
# (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"},
    "cam1": {"channel": "imaging", "obstype": "Imaging", "observer": "Cerbero imaging operator"},
    "cam2": {"channel": "spectro", "obstype": "Spectroscopy", "observer": "Cerbero spectro operator"},
    "cam3": {"channel": "echelle", "obstype": "Echelle", "observer": "Cerbero echelle operator"},
}


class Template(BaseTemplate):
    """Fills the header of an observed FITS file and archives a copy."""

    def __init__(self):
        """Constructor"""
        super().__init__()
        self.name = "fillheader"
        self.description = "Fills the header of observed FITS files"

    def content(self, params):
        """Template implementation"""

        ########################
        ##### Params check #####
@@ -66,6 +71,7 @@ class Template(BaseTemplate):
        cam = getattr(_dev, camera_name)
        filename = params.get("fits_file") or temp_fits
        gps = Time(utc, format="unix")
        channel = _CHANNEL_INFO.get(camera_name, {})

        try:
            with fits.open(filename, 'update') as h:
@@ -100,7 +106,6 @@ class Template(BaseTemplate):
                        '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(
@@ -238,17 +243,6 @@ class Template(BaseTemplate):
                    hdu.header["CRVAL2"] = radec[1]
                    hdu.header["RADESYS"] = "ICRS"

                    # wcss = WCS()
                    # wcss.wcs.ctype = ["RA---TAN", "DEC--TAN"]
                    # wcss.wcs.cd = scale*np.array([[np.cos(angle), np.sin(angle)],
                    #                         [np.sin(angle), flip*np.cos(angle)]])
                    # wcss.wcs.cunit = ['deg', 'deg']
                    # wcss.wcs.crval = [radec,
                    #                   radec*15]
                    # wcss.wcs.crpix = [ (camx[1]-camx[0])/2.,
                    #                    (camy[1]-camy[0])/2.]
                    # hdu.header.update(wcss.to_header())

                    log.debug(
                        f"Header updated in {(Time.now() - now1).sec.item():.2f}")

@@ -264,7 +258,7 @@ class Template(BaseTemplate):
        ###############################

        if save:
            outpath = save_filename(filename)
            outpath = save_filename(filename, channel=channel.get("channel", ""))
            log.info(f"Copying {filename} as {outpath}")
            self.filename = str(outpath)
            log.debug(f"fillheader {self.filename}")
+4 −0
Original line number Diff line number Diff line
@@ -125,12 +125,16 @@ def simple_ascii_plot(


class Template(BaseTemplate):
    """Focus template: scans M2 focus and fits FWHM vs position."""

    def __init__(self):
        """Constructor"""
        super().__init__()
        self.name = "focus"
        self.description = "Fit boxed images to get optimal focus"

    def content(self, params):
        """Template implementation"""

        ########################
        ##### Params check #####
+4 −0
Original line number Diff line number Diff line
@@ -25,12 +25,16 @@ _NO_FILTER_CAMERAS = {"cam3"}


class Template(BaseTemplate):
    """Snapshot template: single or repeated exposures on any of the 3 channels."""

    def __init__(self):
        """Constructor"""
        super().__init__()
        self.name = "snapshot"
        self.description = "Observes a field with the imaging, spectroscopic or échelle camera"

    def content(self, params):
        """Template implementation"""

        ########################
        ##### Params check #####
+18 −3
Original line number Diff line number Diff line
@@ -125,9 +125,22 @@ def foc_path(timestamp, dry=False):
    return outpath


def save_filename(infile_path_str):
def save_filename(infile_path_str, channel=""):
    """
    Save a fits file in its path with an ESO-style filename.

    Parameters
    ----------
    infile_path_str : str
        Path to the source FITS file.
    channel : str, optional
        Channel name (e.g. "imaging", "spectro", "echelle"), inserted
        into the output filename. Omitted if empty.

    Returns
    -------
    str
        Absolute path to the saved copy.
    """

    inpath = Path(infile_path_str)
@@ -135,8 +148,10 @@ def save_filename(infile_path_str):

    # '2021-12-28T20:09:56.163'
    date_obs_str = header[dateobs]  # DATE-OBS from FITS header
    name_for_file = Time(date_obs_str).isot
    outfile_name = f"{FILE_PREFIX}.{name_for_file}.{FITS_EXT}"
    # Colons aren't filename-friendly on every filesystem/tool.
    name_for_file = Time(date_obs_str).isot.replace(':', '_')
    parts = [FILE_PREFIX] + ([channel] if channel else []) + [name_for_file, FITS_EXT]
    outfile_name = ".".join(parts)
    outfile = Path(outfile_name)

    outdir = fits_path(header)  # This already creates the directory