Skip to content

Add DL3 writer#2979

Open
mdebony wants to merge 5 commits into
cta-observatory:mainfrom
mdebony:dl3_writer
Open

Add DL3 writer#2979
mdebony wants to merge 5 commits into
cta-observatory:mainfrom
mdebony:dl3_writer

Conversation

@mdebony
Copy link
Copy Markdown
Contributor

@mdebony mdebony commented Apr 2, 2026

Add a DL3 writer for the GADF format to catapipe.

This PR is sub element of #2727 to simplify the review.

@ctao-sonarqube
Copy link
Copy Markdown

ctao-sonarqube Bot commented Apr 9, 2026

Quality Gate failed Quality Gate failed

Failed conditions
1 New issue

See analysis details on SonarQube

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE SonarQube for IDE

@mdebony mdebony requested review from kosack and maxnoe April 9, 2026 17:29
Copy link
Copy Markdown
Member

@kosack kosack left a comment

Choose a reason for hiding this comment

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

I'm still working through this, so this is not yet a complete review, but here are a few initial comments

Comment thread src/ctapipe/io/dl3.py
return np.rad2deg(result_rad) % 360

@staticmethod
def _circular_mean(angles_deg):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd suggest just using quantities here:

Suggested change
def _circular_mean(angles_deg):
def _circular_mean(angles: u.Quantity[deg]) → u.Quantity[deg]:

And that simplifies the code, since numpy will use the correct angle unit automatically:

In [1]: a = [0,45,90] *u.deg

In [2]: np.sin(a)
Out[2]: <Quantity [0.        , 0.70710678, 1.        ]>

In [3]: np.sin(a.to_value("rad"))
Out[3]: array([0.        , 0.70710678, 1.        ])

Then below, you can drop using np.rad2deg, and it should all work.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Or better yet, why not just use astropy's implementation


@pytest.fixture(scope="session")
def dl2_events_for_dl3(single_obs_gamma_diffuse_full_reco_file, dl2_meta_for_dl3):
preprocessor = DL2EventPreprocessor(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you like, I can open a PR to this branch to convert this to use the new EventPreprocessor, which could simplify a lot of the code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

See #3004 where I started to see what changes I need to EventPreprocessor to support the DL2 to DL3 transition

Comment thread src/ctapipe/io/dl3.py
__all__ = ["DL3EventsWriter", "DL3GADFEventsWriter"]


class DL3EventsWriter(Component):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

My main comment here is that I think that the DL3 writer should not store state of the observation.

The inputs to the actual writing function should be the event list, the irfs and additionally needed data, but it should not keep that as state on the writer instance.

It should be possible to write multiple observation blocks using the same instance of the writer.

Comment thread src/ctapipe/io/dl3.py

def __init__(self, **kwargs):
super().__init__(**kwargs)
self._obs_id = None
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It looks more like this is a data class storing all information that should go into a DL3 file, not a writer.

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