Repository navigation
Conversation
Add an extra_mode_flag kwarg to Dataset that is ORed into the mode passed to nc_open, so files without a magic number can be opened with a user-defined format (UDF) dispatch layer, such as those provided by the NetCDF Expansion Pack (NEP). Add NEP_UDF_* module constants (names match nep.h). Only allowed for plain read-only opens (mode='r', not diskless, memory or parallel), requires netcdf-c >= 4.10.0. Add tests for the kwarg (opening an mmCIF file with no magic number) and for reading GRIB2 with NEP (test/gfs_sample.grb2, the first 3 messages of pygrib's sampledata/gfs.grb). The NEP read tests are skipped unless the NEP readers are registered with netcdf-c. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
This PR currently has no test coverage under Github Actions - the tests are skipped because NEP is not installed in any of the environments the tests run in. This can be remedied once the conda-forge package exists (conda-forge/staged-recipes#34309). EDIT: Actually, that conda-forge recipe only builds compression plugins, not the extra file formats. @captainkirk99 are you planning to create a conda-package for the file formats capabilities in NEP? |
|
@jswhit thanks for this effort. Unfortunately, as my AI informs me, it will not work because I am also changing how UDF mode flags are assigned and that changes the values of existing UDF flags. See Unidata/netcdf-c#3442. Here's what my AI has to say: PR #1518: UDF mode flags are hard-coded and break with the new netcdf-c encodingPR: #1518 ( 1. What is wrong1.1 The flag values are hard-codedThe PR defines the NEP mode flags as numbers in NEP_UDF_GEOTIFF_BIGTIFF = 0x0040 # NC_UDF0
NEP_UDF_GEOTIFF_STANDARD = 0x0080 # NC_UDF1
NEP_UDF_GRIB2 = 0x10000 # NC_UDF2
NEP_UDF_FITS = 0x80000 # NC_UDF3
NEP_UDF_CDF = 0x100000 # NC_UDF4
NEP_UDF_PDS4 = 0x200000 # NC_UDF5
NEP_UDF_DICOM = 0x400000 # NC_UDF6
NEP_UDF_PDB = 0x800000 # NC_UDF7
NEP_UDF_MMCIF = 0x1000000 # NC_UDF8
NEP_UDF_NEXTCDF4 = 0x2000000 # NC_UDF9These are the values of 1.2 netcdf-c is changing the encodingnetcdf-c commit #define NC_UDF_FLAG 0x0040 /* Mode selects a user-defined format (bit 6). */
#define NC_UDF_NUM_SHIFT 19 /* Bit position of the UDF slot number field. */
#define NC_UDF_NUM_MASK 0x3F /* 6-bit UDF slot number field: slots 0-63. */
#define NC_UDF(n) (NC_UDF_FLAG | ((n) << NC_UDF_NUM_SHIFT))
#define NC_UDF0 NC_UDF(0)
...
#define NC_UDF9 NC_UDF(9)
#define NC_MAX_UDF_FORMATS 64From its RELEASE_NOTES entry:
NEP is fine with this change:
1.3 The PR's tests don't catch this
1.4 Smaller issues
2. How to fix itWork out the mode flag for a slot at runtime, from the libnetcdf that is 2.1 Detecting the encoding
So 2.2 Checking whether a slot is registeredUDF plugins listed in 3. Replacement codeThe code below replaces the corresponding parts of PR #1518. Everything not 3.1
|
| netcdf-c | NEP | expected |
|---|---|---|
| < 4.10.0 | - | NoUDFSupportTestCase passes, rest skipped |
4.10.x (main) |
none | all non-NEP tests pass, NEP tests skipped |
ejh_udf_082126 |
none | all non-NEP tests pass, NEP tests skipped |
4.10.x (main) |
registered via NCRCENV_RC |
all pass |
ejh_udf_082126 |
rebuilt against it, registered | all pass |
PR #1518 unchanged + ejh_udf_082126 |
none | an equivalent of UDFModeFlagReachesNcOpenTestCase using the PR constants fails (the file opens as netCDF) |
Also run stubtest netCDF4 and mypy test, as the PR does.
5. Notes
- The probe depends on current netcdf-c behaviour. It assumes the new
encoding keeps rejecting0x0080innc_inq_user_format(). A netcdf-c test
asserting that would lock it in. - A cleaner long-term option is for netcdf-c to report the encoding
directly, for example anNC_HAS_UDF_SLOTFIELDentry innetcdf_meta.h
plus a runtime query. The probe is still needed for 4.10.x libraries already
in the field. - Possible API change: a
udf_slot=kwarg taking a slot number would hide
the encoding from users entirely.extra_mode_flagplusudf_mode()keeps
the PR's API, and the two would differ only in which value the user passes.
|
In terms of testing, this can be tested if you also expose the nc_def_user_format() function. Then a test can construct it's own user format, and ensure that a file is opened with the correct user format. WRT compression and the NEP: I have just separated out the compression filters into separate package nep-compress. nep-compress currently has pending PRs for both spack and conda. The next release of NEP will have no compression, and the conda package will build formats. I hope that will happen soon. |
Add
extra_mode_flagkwarg toDatasetfor user-defined formats (closes #1490)netcdf-c (>= 4.10.0) supports user-defined formats (UDFs), which let
external dispatch layers read non-netCDF formats through the netCDF API.
The NetCDF Expansion Pack (NEP)
uses this to add readers for GRIB2, FITS, NASA CDF, PDS4, DICOM, PDB,
mmCIF and GeoTIFF.
netcdf-c normally picks the dispatch layer from the file's magic number,
so
Dataset()already works for these formats with no changes. Some fileshave no magic number, though (mmCIF, for example). For those, netcdf-c
needs a UDF mode flag passed to
nc_open(), such asnc_open(path, NC_NOWRITE|NC_UDF8, &ncid). Until now there was no way topass one from Python.
Almost all NEP supported formats contain the "magic number" and can be read by netcdf-c 4.10.0 without the extra flag. AFAICT, only DICOM files require it.
Changes
Datasetkwargextra_mode_flag(defaultNone). An integerthat is ORed into the mode passed to
nc_open():NEP_UDF_*: the names match the macros in NEP'snep.h, and each maps a format to itsNC_UDFnslot:NEP_UDF_GEOTIFF_BIGTIFF(UDF0),NEP_UDF_GEOTIFF_STANDARD(UDF1),NEP_UDF_GRIB2(UDF2),NEP_UDF_FITS(UDF3),NEP_UDF_CDF(UDF4),NEP_UDF_PDS4(UDF5),NEP_UDF_DICOM(UDF6),NEP_UDF_PDB(UDF7),NEP_UDF_MMCIF(UDF8),NEP_UDF_NEXTCDF4(UDF9). They are hard-coded,like
NC_DISKLESS/NC_PERSIST, so the module still compiles againstolder netcdf-c.
nc_open()(
cmode == NC_NOWRITE). These cases raise an error:modeother than'r'(including'rs'), ordiskless,memoryorparallel‚ÜíValueError. NEP readers are read-only, and parallel orin-memory opens aren't expected to work with UDFs.
NEP_UDF_*constants ‚ÜíValueError.This stops arbitrary mode bits (such as
NC_WRITE) from being passedthrough.
NetCDF4MissingFeatureException.__init__.pyi,_netCDF4.pyi) updated.The UDF itself still has to be registered with netcdf-c. For NEP that's
done through its
.ncrcfile, for example withNCRCENV_RCpointing at it.Tests
The NEP-dependent tests are skipped unless the NEP readers are registered
with netcdf-c, so CI without NEP is unaffected.
test/test_extra_mode_flag.pydata_magic number removed, checksthat it fails to open by default, and checks that it opens and reads
correctly with
extra_mode_flag=NEP_UDF_MMCIF.test/test_nep_grib2.pywithtest/gfs_sample.grb2(27 KB, the first 3messages of pygrib's
sampledata/gfs.grb): reads HGT, TMP and RH andchecks them against reference values from pygrib. It runs twice, once with
the format detected from the magic number and once with
extra_mode_flag=NEP_UDF_GRIB2.MANIFEST.innow includestest/*grb2.Tested on macOS arm64 with netcdf-c 4.10.1, HDF5 2.2.0 and NEP v4.1.0 built
from source. The full test suite passes with and without NEP, and so do
stubtestandmypy test.Notes
2.5° grid it gives
(y=144, x=73), but the data are stored as(lat=73, lon=144). The GRIB2 test reshapes before comparing, so it will
keep passing once that's fixed in NEP.
nepisn't on conda-forge yet(Add the NetCDF Expansion Pack (NEP) - allowing netCDF to read additional data formats as if they were netCDF conda-forge/staged-recipes#34309 is still open), and that package
(v3.0.0) doesn't include the format readers, so testing currently needs
a source build of NEP.
ü§ñ Generated with Claude Code