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

Massive overhaul of core functionality #113

Merged
merged 48 commits into from
Mar 10, 2025
Merged

Massive overhaul of core functionality #113

merged 48 commits into from
Mar 10, 2025

Conversation

sesquideus
Copy link
Contributor

Sorry for the branch name, it got out of hand pretty quickly.

I tried to reimplement the loading part of the class hierarchy, so that it does not have to instantiate the RecipeImpl twice. As usual, the rabbithole went on and on... and we can now:

  • inspect and build the class before it is instantiated, even without a valid frameset;
  • split everything into working child classes (by detector, target, band...) much more easily
  • also automatically promote the class to a child based on the data
  • produce a working pyesorex --man-page <recipe> from the attributes of the Recipe, and without a valid frameset. No more writing and formatting the description yourself!
  • have a much more verbose as_dict to compare with the DRLD, including OCA keywords, algorithm (my long-term goal is to to recreate all DRLD boxes from the code, even if we don't actually do it, it's good to be able to)
  • get rid of the whole repetitive inputs |= {...}, as this is now done fully automatically with inspect (every PipelineInput in the InputSet gets instantiated and filled with data)
  • have much simpler and declarative class definitions, and also mixins make a bit more sense

Some downsides:

  • certain pyesorex errors are more cryptic / nonexistent, because it catches many exceptions, so if you screw up, suddenly your recipe does not exist anymore (requires some minor tweaks to your working pyesorex, no problem if you ˙pip install -e` it)
  • some things are defined implicitly and no longer collocated in the code, also IDE inspection has some problems
  • I noticed that many Inputs / Products are in fact the same (no shit Sherlock) and also keywords could probably be objects and not just strings, so I have to fix this too

Requires fixes to pyesorex (terminal size thing and better comparing for None) so it will not pass the tests right now.

@hugobuddel
Copy link
Contributor

I keep smiling at the title of this PR in the email notifications. Maybe we should generate an image for each PR based on the title 🤓

A detailed, high-resolution digital rendering of a futuristic sci-fi scene featuring a massive overhaul of ore functionality  The image is rich with glowing bioluminescent colors, natural light, vibrant glows, and advanced te

@sesquideus sesquideus changed the title Massive overhaul of ore functionality Massive overhaul of core functionality Feb 25, 2025
@sesquideus
Copy link
Contributor Author

sesquideus commented Feb 25, 2025

Sorry to rain on your asteroid mining parade 😄

@hugobuddel
Copy link
Contributor

The error is raised by from pymetis.tests.classes import BaseRecipeTest, BaseInputSetTest, BaseProductTest. The error is E ImportError: cannot import name 'BaseRecipeTest' from 'pymetis.tests.classes' (unknown location), and it seems to me that BaseRecipeTest is indeed not defined in pymetis.tests.classes .Maybe there should be a __init__.py in pymetis/tests/classes that imports BaseRecipeTest?

@@ -1,6 +1,6 @@
[metadata]
name = pymetis
version = @PACKAGE_VERSION@
version = 0.0.1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ESO build system might expect @PACKAGE_VERSION@ here. I suppose everything would still work with a specific version though.

I think the idea is to have only one place where the version number is defined, which canonically is

AC_INIT([METIS Instrument Pipeline], [0.0.1], [https://support.eso.org],
. That version is then propagated to here by autotools or cmake.

Maybe it is easier for our development if we (also) define the version explicitly here, but it should be a conscious decision that means that we then have an extra location to update the version number.

I'm impartial in what we decide, so I'm not for or against this change; I'm just noting we should be aware of what it means.

(I personally think that the 'current version' should be derived from the git tag/branch/hash, like astropy, but I don't actually suggest doing that now.)

Comment on lines 161 to 162
return re.sub(r"\?P<\w+>", "", cls.tags().pattern)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two return statements in a row seems like a mistake.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot about this from some late night debugging session. Right now I'd prefer the unmodified version, since it explicitly shows what the name of the parameter is -- and how it is propagated to outputs.

Comment on lines 126 to 127
class FluxstdCatalogInput(SinglePipelineInput):
_title: str = "flux standard star catalogue table"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note that we made a distinction between "tables" and "catalogs". A 'catalog' is a list of sources (e.g. stars) with their attributes, it is encoded as a FITS table extension. A 'table' contains something else, e.g. a list of offsets for each detector, and we have not decided how it should be encoded. For example, a table with just four numbers can perhaps be better encoded as four FITS headers, because that makes it queryable through the archive. The term "catalog[ue] table" is never used in the DRLD in order to prevent confusion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad. This was probably autogenerated by some PyCharm AI voodoo and I failed to notice.

cut_input = re.compile(r'Input$')

# Now iterate over all refined Inputs, instantiate them and feed them the frameset to filter.
for (name, input_type) in inspect.getmembers(self.__class__,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually, when I want to use inspect, I'm doing something wrong. So this line triggers me, and there is not enough information here to take away my fear that we are doing something wrong here too. Maybe it is fine though.

Perhaps you can iterate over self.__class__.__dict__.items() instead? But maybe that won't work for some reason? It would still be a bit hacky, but somewhat less, IMHO.

I think inspect is also pretty heavy, and maybe implementation dependent (not sure)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure. There are many possible approaches, this felt cleanest to me. Also we can go fully explicit.

What I both like and dislike the most is that it happens fully automatically. It can lead to disgusting bugs (like failing to match the name of the overridden class, which results in extra inputs (but that can be caught by looking at the man page)). Also code collocation is horrendous -- all the magic happens way too high in the hierarchy.

for (name, input_type) in inspect.getmembers(self.__class__,
lambda x: inspect.isclass(x) and issubclass(x, PipelineInput)):
inp = input_type(frameset)
# FixMe: very hacky for now: determine the name of the instance from the name of the class
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just run with it! I can understand it, I think:

(?<!^)(?=[A-Z]) means: look-back to determine we are not at the start, and then look-here/ahead but don't consume (?) and this is a capital. If so, insert an underscore. Before all that, remove "Input", and afterwards make lowercase. So this is debugable.

@hugobuddel hugobuddel mentioned this pull request Mar 2, 2025

# Use this regex to verify that the product tag is correct.
# This base version only verifies it is ALL_CAPS_WITH_UNDERSCORES
_regex_tag: re.Pattern = re.compile(r"^[A-Z0-9]+[A-Z0-9_]+[A-Z0-9]+$")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a tag starting with a number going to work? I can imagine it leading to problems, not sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it is (at least I don't see a reason why it should not work). But it might be better to just forbid that.

@@ -181,3 +183,21 @@ def _create_dummy_image() -> cpl.core.Image:
@property
def valid_frames(self) -> cpl.ui.FrameSet:
return self.inputset.valid_frames

def _dispatch_child_class(self) -> type["MetisRecipeImpl"]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Supersweet!

Copy link
Contributor

@hugobuddel hugobuddel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good!

@hugobuddel
Copy link
Contributor

Perhaps we should update the cached files? Or maybe wait till pyesorex 1.0.2 is released (which should be next week)

@sesquideus
Copy link
Contributor Author

Perhaps we should update the cached files? Or maybe wait till pyesorex 1.0.2 is released (which should be next week)

No need to hurry now. I would like to fix the tests first anyway.

@sesquideus
Copy link
Contributor Author

OK, I can reproduce the same test failures with my local machine on pyesorex main. And with the latest fixes it might now pass.

@sesquideus sesquideus merged commit 53e6a91 into main Mar 10, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants