Skip to content

Conversation

@albertbrotzer
Copy link

@albertbrotzer albertbrotzer commented Nov 10, 2025

The H SAF product owners recently changed the output format of these products, and EUMETSAT requested support for the new format in EUMETView.

Copy link
Member

@ameraner ameraner left a comment

Choose a reason for hiding this comment

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

Thanks for this! I would propose to actually have only one yaml file for this reader, and you can have different file types inside the same yaml (see e.g. the other PR here https://github.com/pytroll/satpy/pull/2793/files#diff-4ce2f1c002c4dae06506b9ad57f5a0504f278028002abc6263d2cd90ae50c8c6 ). This way it's more user friendly as there is only one reader for all HSAF products, and it's easier to maintain.

Also, there are a few things to fix from the pre-commit checks.

@ameraner ameraner added enhancement code enhancements, features, improvements component:readers labels Nov 10, 2025
@ameraner ameraner moved this to In Progress in Remote PCW Autumn 2025 Nov 10, 2025
@ameraner
Copy link
Member

Also, would it be possible to add support for the H90 product as well, if it's not a lot of work? That way we could close the other PR #2793 for good

@albertbrotzer
Copy link
Author

albertbrotzer commented Nov 12, 2025

Hi @ameraner, there’s now support for H90 products. It uses the same reader and enhancements as H60 and H63. Please let us know if there’s anything else missing. Thanks for the support!

@ameraner ameraner marked this pull request as ready for review November 12, 2025 09:23
@codecov
Copy link

codecov bot commented Nov 12, 2025

Codecov Report

❌ Patch coverage is 98.08917% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.33%. Comparing base (ee989f6) to head (cba3797).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
satpy/readers/hsaf_nc.py 95.16% 3 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main    #3287    +/-   ##
========================================
  Coverage   96.33%   96.33%            
========================================
  Files         463      466     +3     
  Lines       58870    59036   +166     
========================================
+ Hits        56711    56874   +163     
- Misses       2159     2162     +3     
Flag Coverage Δ
behaviourtests 3.59% <0.00%> (-0.02%) ⬇️
unittests 96.42% <98.08%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Member

@ameraner ameraner left a comment

Choose a reason for hiding this comment

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

Thank you for the updates! I took a closer looks and gave some more comments. The area definition assignments needs to be revised, otherwise only minor things.

@ameraner
Copy link
Member

Also, seems that the gzip decompression things are not well coverd by tests, according to codecov...
Note that for some readers we delegate the decompression to the user using FSFile, e.g. see

Compression
-----------
Gzip-compressed MTSAT files can be decompressed on the fly using
:class:`~satpy.readers.core.remote.FSFile`:
.. code-block:: python
import fsspec
from satpy import Scene
from satpy.readers.core.remote import FSFile
filename = "/data/HRIT_MTSAT1_20090101_0630_DK01IR1.gz"
open_file = fsspec.open(filename, compression="gzip")
fs_file = FSFile(open_file)
scn = Scene([fs_file], reader="jami_hrit")
scn.load(["IR1"])

maybe the same could be done here for simplicity?

@ameraner
Copy link
Member

Actually, @mraspaud pointed me to the generic_open context manager, that by using Upath it could actually handle the gz files automagically without extra code, see below (it's already used in a few readers):

def generic_open(filename, *args, **kwargs):
"""Context manager for opening either a regular file or a compressed file.
Yields an open file-like object.
"""

albertbrotzer and others added 2 commits November 17, 2025 09:48
This commit introduces a reader enhancement for HSAF precipitation
products h60b, h63 and h90. The reader includes handling for externally
compressed (.gz) files by automatically unzipping them to a local
temporary location.
This commit restructures and improves the reader following initial review
feedback.

Key changes:
- Unified all file_type definitions into a single YAML configuration file
- Simplified and cleaned up reader logic, removing redundant code
- Expanded and clarified docstrings across the reader implementation
- Improved handling of area definitions in the HSAF NetCDF reader
- Generalized dataset definitions so they can be reused by multiple file types
Replaced the custom logic for handling externally zipped NetCDF files
with Satpy's generic_open helper, which transparently supports both
compressed and uncompressed inputs. This simplifies the reader
implementation and removes compression-detection code.

Because generic_open uses a context manager and closes the file as soon
as the reader exits the `with` block, the dataset must now be fully
loaded immediately. The reader therefore calls `.compute()` on the
xarray dataset to ensure all data is available after the context closes.
@albertbrotzer
Copy link
Author

@ameraner @mraspaud the PR should be ready for review again. I think we addressed all comments and checks. Thanks again for your exceptional support and feedback!

Copy link
Member

@ameraner ameraner left a comment

Choose a reason for hiding this comment

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

Thanks for the updates! Looks good - only one point on the use of compute, and a minor point on the attributes

…mains accessible

- Replace the short-lived with generic_open(...) usage with manual
__enter__() / __exit__() handling

- Avoid loading the entire dataset via .compute(), preserving lazy
operation while preventing “seek of closed file” errors.

- Ensure the NetCDF file remains open for Dask-backed arrays until the
reader is destroyed.

- Add safe cleanup in __del__() using explicit context-manager exit and
catching expected cleanup-related exceptions.
...as they're added automatically through the property methods
Copy link
Member

@ameraner ameraner left a comment

Choose a reason for hiding this comment

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

LGTM, thanks for taking the initiative to add this reader and all the updates!

ReadTheDocs is currently failing, but only due to a warning regarding not being able to fetch a numpy inventory due to HTTP error, so probably just a glitch and nothing related to the PR.

@pnuu
Copy link
Member

pnuu commented Nov 19, 2025

There are some failing tests. I think the others are glitches, but I think this I fixed in an earlier PR that was merged:

FAILED satpy/tests/test_utils.py::TestGetSatPos::test_get_satpos_from_satname - ImportError: cannot import name 'tlefile' from 'pyorbital.orbital' (/home/runner/miniconda3/envs/test-environment/lib/python3.11/site-packages/pyorbital/orbital.py)

The same error is in few other tests, but all of them hopefully go away if you merge the current main branch to your PR.

@pnuu
Copy link
Member

pnuu commented Nov 19, 2025

Ah, the fix is still unmerged in #3294

@albertbrotzer
Copy link
Author

LGTM, thanks for taking the initiative to add this reader and all the updates!

ReadTheDocs is currently failing, but only due to a warning regarding not being able to fetch a numpy inventory due to HTTP error, so probably just a glitch and nothing related to the PR.

Thanks to you all for supporting us in the process. I think we can learn a lot from you guys and we will for sure add more as soon as time allows.

@pnuu
Copy link
Member

pnuu commented Nov 19, 2025

Ok, we merged couple of PRs to main and main to your PR branch. Hopefully now all the tests will pass.

@coveralls
Copy link

coveralls commented Nov 19, 2025

Pull Request Test Coverage Report for Build 19674468060

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • 2 unchanged lines in 1 file lost coverage.
  • Overall coverage increased (+0.005%) to 96.414%

Files with Coverage Reduction New Missed Lines %
tests/test_resample.py 2 99.53%
Totals Coverage Status
Change from base Build 19515384246: 0.005%
Covered Lines: 56752
Relevant Lines: 58863

💛 - Coveralls

Copy link
Member

@mraspaud mraspaud left a comment

Choose a reason for hiding this comment

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

Thanks a lot for the work on supporting these HSAF products!
I especially appreciate the clarity of the code.

I have some comments, questions and suggestions inline, but I think this is almost ready :)

"""

def __init__(self, filename, filename_info, filetype_info):
"""Opens the nc file and store the nc data."""
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"""Opens the nc file and store the nc data."""
"""Open the nc file and store the nc data."""

super().__init__(filename, filename_info, filetype_info)
self._area_name = None
self._lon_0 = None
# use generic_open contextmanager to handle compression transparently
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# use generic_open contextmanager to handle compression transparently

self._lon_0 = None
# use generic_open contextmanager to handle compression transparently
self._cm = generic_open(filename, mode="rb", compression="infer")
# manually enter the context manager
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# manually enter the context manager

self._cm = generic_open(filename, mode="rb", compression="infer")
# manually enter the context manager
fp = self._cm.__enter__()
# lazily load nc file through h5netcdf without forcing read
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# lazily load nc file through h5netcdf without forcing read

def __del__(self):
"""Instruct the context manager to clean up and close the file."""
try:
if hasattr(self, "_cm") and self._cm:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if hasattr(self, "_cm") and self._cm:

I don't think this is needed: self._cm will we be present and have a value as long as the class has been instantiated. If not, this __del__ function will never be called anyway.

self._area_name = self.filetype_info["area"]
self._lon_0 = float(self._nc_data.attrs["sub_satellite_longitude"].rstrip("f"))

# Add global attributes which are shared across datasets
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# Add global attributes which are shared across datasets

@property
def end_time(self):
"""Get end time."""
return self.start_time + dt.timedelta(minutes=15)
Copy link
Member

Choose a reason for hiding this comment

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

15: So these products will never contain rss or fci data?

Copy link
Member

Choose a reason for hiding this comment

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

the products supported so far are all SEVIRI full-discs - when someone will add support for the FCI counterparts, this will need some refactoring

Copy link
Member

Choose a reason for hiding this comment

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

ok, how do we keep track of this?

Copy link
Member

Choose a reason for hiding this comment

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

looks like Alexandra made it configurable now, so it's solved I'd say

Comment on lines 89 to 90
with mock.patch("satpy.readers.hsaf_nc.generic_open") as mock_generic_open, \
mock.patch("satpy.readers.hsaf_nc.xr.open_dataset") as mock_open_dataset:
Copy link
Member

Choose a reason for hiding this comment

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

We have now as policy to try to create a stub file in tmp_path instead of mocking. I think here, it's a simple matter of writing the xarray dataset to disk and use that as input file. With the added benefit that you can (g/b)zip it to test the decompression...

Comment on lines 141 to 156
@pytest.mark.parametrize(
("file_type", "loadable_ids"),
[
(FILE_PARAMS[FILE_TYPE_H60], ["rr", "qind"]),
(FILE_PARAMS[FILE_TYPE_H63], ["rr", "qind"]),
(FILE_PARAMS[FILE_TYPE_H90], ["acc_rr", "qind"]),
],
)
@pytest.mark.skipif(not os.path.exists(FILE_PARAMS[FILE_TYPE_H60]["real_file"]) or
not os.path.exists(FILE_PARAMS[FILE_TYPE_H63]["real_file"]),
reason="Real HSAF file not present")
def test_real_hsaf_file(self, file_type, loadable_ids):
"""Test the reader with a real HSAF NetCDF file."""
# Select files
loadables = file_type["reader"].select_files_from_pathnames([file_type["real_file"]])
assert loadables, "No loadables found for the real file"
Copy link
Member

Choose a reason for hiding this comment

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

when are these real files available? are they easily downloadable from somewhere so that the user can test this?
Otherwise, I think it's as good to create stub files that mimic these files closely.

This will make it possible to use this reader for other H-SAF products (e.g. H40/H40B) in the future,
provided that the corresponding file type is configured.
...so that it complies with the guidelines for the use of stub files.
...so the defined enhancement will only be applied to `rr`.
@armelzer
Copy link

armelzer commented Nov 25, 2025

@ameraner @mraspaud The standard_name has been changed as suggested for rr and acc_rr, and I think we should also take into account all other comments/suggestions made so far. We have not made any specific improvements to acc_rr, as we are not experts in this field and in another PR .In addition, the delta value (in minutes) for the ‘end_time’ is now configurable across file types.

So thanks again for your exceptional support and feedback (also to @pnuu)!

With this commit the tear down method of the `TestHSAFNCReader` will
be correctly called and the test for the enhancement will no longer fail.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component:readers enhancement code enhancements, features, improvements

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

Add a reader for EUMETSAT HSAF SEVIRI H60 and H63

6 participants