Writing PyCPL recipes with PyHDRL

This section shows how to turn PyHDRL high-level algorithms into a pipeline recipe based on cpl.ui.PyRecipe. The examples are drawn from the hdrldemo Python recipes (hdrldemo_pyfpn, hdrldemo_pymaglim, hdrldemo_pyresample). They are intentionally high level: the goal is the common skeleton and the PyHDRL-specific steps, not a line-by-line copy of the demo pipeline.

For the general PyCPL recipe model (PyRecipe, parameters, framesets, PyEsoRex), see the PyCPL User Guide. Algorithm details are in High-level algorithms and the API Reference.

Common recipe skeleton

A PyHDRL recipe is still a normal PyCPL recipe. The class inherits from cpl.ui.PyRecipe, declares metadata, builds a cpl.ui.ParameterList in __init__, and implements run:

import cpl.core
import cpl.ui
import cpl.dfs
import hdrl.func


class MyRecipe(cpl.ui.PyRecipe):
    _name = "my_recipe"
    _version = "1.0.0"
    _author = "..."
    _email = "..."
    _copyright = "GPL-2.0-or-later"
    _synopsis = "Short one-line description"
    _description = "Longer help text shown by PyEsoRex / EsoRex."

    def __init__(self) -> None:
        super().__init__()
        self.parameters = cpl.ui.ParameterList()
        # append cpl.ui.ParameterValue / ParameterRange / ...

    def run(self, frameset: cpl.ui.FrameSet, settings: dict) -> cpl.ui.FrameSet:
        # 1. apply settings, read parameters
        # 2. classify SOF frames, load data
        # 3. call hdrl.func / hdrl.core
        # 4. save products with cpl.dfs, return product FrameSet
        ...

run receives the input Set-Of-Frames (SOF) as a cpl.ui.FrameSet and a settings dictionary of parameter overrides. It must return a cpl.ui.FrameSet of product frames.

Reading recipe parameters

Build parameters in __init__ with a stable full name (recipe_name.param), a context (usually the recipe name), and a CLI alias for PyEsoRex:

par = cpl.ui.ParameterValue(
    name="my_recipe.ext-nb-raw",
    context="my_recipe",
    description="FITS extension of the RAW",
    default=0,
)
par.cli_alias = "ext-r"
self.parameters.append(par)

At the start of run, apply settings and then read values from self.parameters:

for key, value in settings.items():
    try:
        self.parameters[key].value = value
    except KeyError:
        cpl.core.Msg.warning(self.name, f"Unknown setting {key}")

ext_r = self.parameters["my_recipe.ext-nb-raw"].value

String parameters that map to PyHDRL enums (resampling method, Maglim border handling, collapse mode method, …) are usually converted explicitly before constructing the PyHDRL objects.

Reading SOF data

Typical pattern:

  1. Assign frame.group (RAW / CALIB / PRODUCT) from DO tags so DFS product headers are correct.

  2. Select frames by frame.tag (for example RAW, RAW_BPM, POWERSPEC_MASK).

  3. Load pixels with PyCPL: cpl.core.Image.load, cpl.core.ImageList.load, cpl.core.Mask.load, and headers with cpl.core.PropertyList.load.

  4. Propagate quality / BPM into the data image (for example image.reject_from_mask(...)) before calling PyHDRL.

Extension indices follow CPL conventions (primary HDU is 0). Demo recipes often expose ext-r / ext-b / ext-e parameters for this.

Calling PyHDRL

Keep the computational core small: build the PyHDRL parameter objects from recipe parameters, then call one high-level entry point. Examples:

Where an algorithm needs an error plane, wrap PyCPL images in hdrl.core.Image / hdrl.core.ImageList first (see Image and cube resampling).

Saving products

Use cpl.dfs so products get standard pipeline provenance (ESO PRO CATG, used frames, recipe name, pipe id). Common helpers:

  • cpl.dfs.save_image — image product (FPN power spectrum)

  • cpl.dfs.save_propertylist — header / QC-only product (Maglim QC)

  • cpl.dfs.save_table — table product (optional resample table)

Then append a cpl.ui.Frame with group=PRODUCT and the product tag to the returned frameset:

product_frames = cpl.ui.FrameSet()
cpl.dfs.save_image(
    frameset, self.parameters, usedframes,
    image, "my_recipe", applist, PIPE_ID, outfile,
)
product_frames.append(
    cpl.ui.Frame(
        file=outfile,
        tag="MY_PRODUCT",
        group=cpl.ui.Frame.FrameGroup.PRODUCT,
        level=cpl.ui.Frame.FrameLevel.FINAL,
        frameType=cpl.ui.Frame.FrameType.IMAGE,
    )
)
return product_frames

applist is a cpl.core.PropertyList that at least sets ESO PRO CATG and any QC keywords the recipe must publish.

What differs per demo recipe

Each of the following pages focuses on one hdrldemo recipe and the PyHDRL API it uses. Shared SOF / DFS boilerplate is only repeated where it matters for that algorithm.