Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mb/docstrings #107

Merged
merged 11 commits into from
Feb 13, 2025
12 changes: 0 additions & 12 deletions Pipfile

This file was deleted.

5 changes: 5 additions & 0 deletions metisp/pymetis/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ version = "0.0.1"

dynamic = ["description","requires-python","license","authors","classifiers","urls","dependencies","optional-dependencies"]

[tool.pytest.ini_options]
addopts = "--strict-markers"
markers = [
"edps: marks EDPS tests that take a long time (deselect with '-m \"not edps\"')",
]
18 changes: 12 additions & 6 deletions metisp/pymetis/src/pymetis/base/impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"""
import os
from abc import ABC, abstractmethod
from typing import Dict, Any
from typing import Dict, Any, final

import cpl
from cpl.core import Msg
Expand Down Expand Up @@ -62,7 +62,7 @@ def run(self, frameset: cpl.ui.FrameSet, settings: Dict[str, Any]) -> cpl.ui.Fra
(and hence it does not have to be repeated or overridden anywhere).

ToDo: At least Martin thinks so now. It might change, but needs compelling arguments.
ToDo: If this structure does not cover the needs of your particular recipe, we should discuss and adapt.
FixMe: If this structure does not cover the needs of your particular recipe, we should discuss and adapt.
"""

try:
Expand All @@ -81,7 +81,10 @@ def run(self, frameset: cpl.ui.FrameSet, settings: Dict[str, Any]) -> cpl.ui.Fra


def import_settings(self, settings: Dict[str, Any]) -> None:
""" Update the recipe parameters with the values requested by the user """
"""
Update the recipe parameters with the values requested by the user.
Warn if any of the parameters is not recognized.
"""
for key, value in settings.items():
try:
self.parameters[key].value = value
Expand Down Expand Up @@ -121,6 +124,7 @@ def process_images(self) -> [PipelineProduct]:
"""
return {}

@final
def _save_products(self) -> None:
"""
Save and register the created products.
Expand All @@ -132,6 +136,7 @@ def _save_products(self) -> None:
for product in self.products:
product.save()

@final
def build_product_frameset(self) -> cpl.ui.FrameSet:
"""
Gather all the products and build a FrameSet from their frames so that it can be returned from `run`.
Expand All @@ -140,11 +145,12 @@ def build_product_frameset(self) -> cpl.ui.FrameSet:
f"Building the product frameset")
return cpl.ui.FrameSet([product.as_frame() for product in self.products])

@final
def as_dict(self) -> dict[str, Any]:
"""
Converts the object and its related data into a dictionary representation.

Return:
Returns:
dict[str, Any]: A dictionary that contains the serialized representation
of the object's data, including both input set data and product data.
"""
Expand All @@ -157,15 +163,15 @@ def as_dict(self) -> dict[str, Any]:
}

@staticmethod
def _create_dummy_header():
def _create_dummy_header() -> cpl.core.PropertyList:
"""
Create a dummy header (absolutely no assumptions, just to have something to work with).
This function should not survive in the future.
"""
return cpl.core.PropertyList()

@staticmethod
def _create_dummy_image():
def _create_dummy_image() -> cpl.core.Image:
"""
Create a dummy image (absolutely no assumptions, just to have something to work with).
This function should not survive in the future.
Expand Down
55 changes: 39 additions & 16 deletions metisp/pymetis/src/pymetis/base/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
"""

from abc import ABC
from typing import Any, final

import cpl
from cpl.core import Msg

PIPELINE = r"METIS"
PIPELINE = r'METIS'


class PipelineProduct(ABC):
Expand All @@ -35,6 +36,7 @@ class PipelineProduct(ABC):
_group: cpl.ui.Frame.FrameGroup = cpl.ui.Frame.FrameGroup.PRODUCT # ToDo: Is this a sensible default?
_level: cpl.ui.Frame.FrameLevel = None
_frame_type: cpl.ui.Frame.FrameType = None
_used_frames: cpl.ui.FrameSet = None

def __init__(self,
recipe_impl: 'MetisRecipeImpl',
Expand Down Expand Up @@ -64,9 +66,9 @@ def __init__(self,

self.add_properties()

def add_properties(self):
def add_properties(self) -> None:
"""
Hook for adding properties.
Hook for adding custom properties.
Currently only adds ESO PRO CATG to every product,
but derived classes are more than welcome to add their own stuff.
Do not forget to call super().add_properties() then.
Expand All @@ -79,7 +81,7 @@ def add_properties(self):
)
)

def as_frame(self):
def as_frame(self) -> cpl.ui.Frame:
""" Create a CPL Frame from this Product """
return cpl.ui.Frame(
file=self.output_file_name,
Expand All @@ -89,15 +91,17 @@ def as_frame(self):
frameType=self.frame_type,
)

def as_dict(self):
def as_dict(self) -> dict[str, Any]:
""" Return a dictionary representation of this Product """
return {
'tag': self.tag,
}

@final
def __str__(self):
return f"{self.__class__.__qualname__} ({self.tag})"

@final
def save(self):
""" Save this Product to a file """
Msg.info(self.__class__.__qualname__,
Expand All @@ -121,38 +125,57 @@ def save(self):
)

@property
def tag(self):
@final
def tag(self) -> str:
return self._tag

@property
def group(self):
@final
def group(self) -> cpl.ui.Frame.FrameGroup:
return self._group

@property
def level(self):
@final
def level(self) -> cpl.ui.Frame.FrameLevel:
return self._level

@property
def frame_type(self):
@final
def frame_type(self) -> cpl.ui.Frame.FrameType:
return self._frame_type

@property
def category(self) -> str:
"""
Return the category of this product
Return the category of this product.

By default, the tag is the same as the category. Feel free to override if needed.
"""
return self.tag

@property
def output_file_name(self) -> str:
""" Form the output file name """
"""
Form the output file name.
By default, this should be just the category with ".fits" appended. Feel free to override if needed.
"""
return f"{self.category}.fits"

@property
def used_frames(self) -> cpl.ui.FrameSet:
"""
Returns
-------
cpl.ui.FrameSet: List of all frames actually used by the product.
"""
return self._used_frames


class DetectorSpecificProduct(PipelineProduct, ABC):
detector = None
"""
Products that are specific to a detector.
"""
detector: str = None

def __init__(self,
recipe: 'MetisRecipe',
Expand All @@ -173,7 +196,7 @@ def __init__(self,


class TargetSpecificProduct(PipelineProduct, ABC):
target = None
target: str = None

def __init__(self,
recipe: 'MetisRecipe',
Expand All @@ -193,8 +216,8 @@ def __init__(self,
- or as a provided property (if it has to be computed dynamically)
"""
if self.target is None:
raise NotImplementedError(f"Products specific to a target must define 'target', but "
f"{self.__class__.__qualname__} does not")
raise NotImplementedError(f"Products specific to a target must define 'target', "
f"but {self.__class__.__qualname__} does not")

super().__init__(recipe, header, image, **kwargs)

Expand All @@ -203,7 +226,7 @@ class BandSpecificProduct(PipelineProduct, ABC):
"""
Product specific to one band. Probably should be merged with all other similar classes.
"""
band = None
band: str = None

def __init__(self,
recipe: 'MetisRecipe',
Expand Down
22 changes: 16 additions & 6 deletions metisp/pymetis/src/pymetis/base/recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@ class MetisRecipe(cpl.ui.PyRecipe):
"""
The abstract base class for all METIS recipes.
In an ideal world it would also be abstract (derived from ABC, or metaclass=abc.ABCMeta),
but `pyesorex` wants to instantiate all recipes it finds
and would crash if it were an abstract class.
but `pyesorex` tries to instantiate all recipes it finds and would crash if it were an abstract class.

The underscored _fields must be present but should be overwritten
by every child class (and `pyesorex` actually checks for their presence).
by every child class (`pyesorex` actually checks for their presence).
"""
_name = "metis_abstract_base"
_version = "0.0.1"
Expand All @@ -41,15 +40,26 @@ class MetisRecipe(cpl.ui.PyRecipe):
_description = ("This class serves as the base class for all METIS recipes."
"Bonus points if it is not visible from pyesorex.")

parameters = cpl.ui.ParameterList([]) # By default, a recipe does not have any parameters.
# ToDo: There is a pyesorex bug that prevents
# this from being actually used.
parameters: cpl.ui.ParameterList = cpl.ui.ParameterList([]) # By default, a recipe does not have any parameters.
implementation_class: type["MetisRecipeImpl"] # Dummy class, this must be overridden in the derived classes anyway

def __init__(self):
super().__init__()
self.implementation: "MetisRecipeImpl" = None

# ToDo: This handles the bug in `pyesorex` where zero-length parameter list crashes the whole thing
# A fix is already provided (just test with `if parameters is None` and not `if not parameters`)
# but not yet merged upstream.
if len(self.parameters) == 0:
self.parameters = cpl.ui.ParameterList([
cpl.ui.ParameterValue(
name=f"{self._name}.dummy",
context=self._name,
description="Dummy parameter to avoid problems with `pyesorex` bug",
default="dummy",
)
])

def dispatch_implementation_class(self, frameset) -> type["MetisRecipeImpl"]:
"""
Return the actual implementation class. By default, just returns `implementation_class`,
Expand Down
2 changes: 1 addition & 1 deletion metisp/pymetis/src/pymetis/inputs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class PinholeTableInput(SinglePipelineInput):


class FluxTableInput(SinglePipelineInput):
_title = "flux standard star catalogue table"
_title: str = "flux standard star catalogue table"
_tags: Pattern = re.compile(r"FLUXSTD_CATALOG")
_group: cpl.ui.Frame.FrameGroup = cpl.ui.Frame.FrameGroup.CALIB

Expand Down
9 changes: 9 additions & 0 deletions metisp/pymetis/src/pymetis/inputs/inputset.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ def as_dict(self) -> dict[str, Any]:

@property
def used_frames(self) -> cpl.ui.FrameSet:
"""
Return the frames that actually affect the output anyhow (if a frame is not listed here, the output without
that frame should be identical

- [HB]: also includes frames that do not contribute any pixel data,
for instance discarded outliers (without them a different frame might be an outlier)

FixMe: Currently this only ensures that frames are loaded, not actually used!
"""
frameset = cpl.ui.FrameSet()

for inp in self.inputs:
Expand Down
2 changes: 1 addition & 1 deletion metisp/pymetis/src/pymetis/prefab/flat.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class RawInput(RawInput):
"""
A subclass of RawInput that is handling the flat image raws.
"""
_tags = re.compile(r"(?P<band>(LM|N))_FLAT_(?P<target>LAMP|TWILIGHT)_RAW")
_tags: re.Pattern = re.compile(r"(?P<band>(LM|N))_FLAT_(?P<target>LAMP|TWILIGHT)_RAW")

def __init__(self, frameset: cpl.ui.FrameSet):
super().__init__(frameset)
Expand Down
4 changes: 2 additions & 2 deletions metisp/pymetis/src/pymetis/prefab/rawimage.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from cpl.core import Msg

from pymetis.base import MetisRecipeImpl
from pymetis.inputs import PipelineInputSet
from pymetis.inputs import PipelineInputSet, RawInput


class RawImageProcessor(MetisRecipeImpl, ABC):
Expand All @@ -34,7 +34,7 @@ class RawImageProcessor(MetisRecipeImpl, ABC):
"""

class InputSet(PipelineInputSet):
RawInput: type = None
RawInput: type[RawInput] = None
detector: str = None

def __init__(self, frameset: cpl.ui.FrameSet):
Expand Down
5 changes: 2 additions & 3 deletions metisp/pymetis/src/pymetis/recipes/cal/metis_cal_chophome.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,13 @@ class MetisCalChophomeImpl(RawImageProcessor): # TODO replace parent class?
class InputSet(RawImageProcessor.InputSet):
"""Inputs for metis_cal_chophome"""
class RawInput(RawInput):
_tags = re.compile(r"LM_CHOPHOME_RAW")
_tags: re.Pattern = re.compile(r"LM_CHOPHOME_RAW")

class BackgroundInput(RawInput):
_tags = re.compile(r"LM_WCU_OFF_RAW")
_tags: re.Pattern = re.compile(r"LM_WCU_OFF_RAW")

def __init__(self, frameset: cpl.ui.FrameSet):
super().__init__(frameset)
self.raw = self.RawInput(frameset)
self.background = self.BackgroundInput(frameset)
self.linearity = LinearityInput(frameset)
self.gain_map = GainMapInput(frameset)
Expand Down
Loading