ncarnate 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ncarnate/__init__.py +34 -0
- ncarnate/__main__.py +21 -0
- ncarnate/cli.py +288 -0
- ncarnate/constants.py +19 -0
- ncarnate/core.py +502 -0
- ncarnate/eos/__init__.py +12 -0
- ncarnate/eos/gctp.py +260 -0
- ncarnate/eos/grid.py +159 -0
- ncarnate/eos/structmeta.py +420 -0
- ncarnate/eos/swath.py +172 -0
- ncarnate/errors.py +85 -0
- ncarnate/formats.py +83 -0
- ncarnate/hdf4.py +1109 -0
- ncarnate/limits.py +64 -0
- ncarnate-2.0.0.dist-info/METADATA +199 -0
- ncarnate-2.0.0.dist-info/RECORD +19 -0
- ncarnate-2.0.0.dist-info/WHEEL +4 -0
- ncarnate-2.0.0.dist-info/entry_points.txt +2 -0
- ncarnate-2.0.0.dist-info/licenses/LICENSE +21 -0
ncarnate/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
'''
|
|
5
|
+
|
|
6
|
+
ncarnate: reincarnate legacy scientific data files as recompressed netCDF4.
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2020-2026 Erick Edward Shepherd. MIT License — see the
|
|
9
|
+
top-level LICENSE file.
|
|
10
|
+
|
|
11
|
+
'''
|
|
12
|
+
|
|
13
|
+
# Local application imports.
|
|
14
|
+
from ncarnate.constants import __author__
|
|
15
|
+
from ncarnate.constants import __version__
|
|
16
|
+
from ncarnate.core import recompress
|
|
17
|
+
from ncarnate.errors import NcarnateError
|
|
18
|
+
from ncarnate.errors import UnsupportedFormatError
|
|
19
|
+
from ncarnate.errors import UnsupportedTypeError
|
|
20
|
+
from ncarnate.errors import VerificationError
|
|
21
|
+
from ncarnate.formats import FileFormat
|
|
22
|
+
from ncarnate.formats import detect_format
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"recompress",
|
|
26
|
+
"detect_format",
|
|
27
|
+
"FileFormat",
|
|
28
|
+
"NcarnateError",
|
|
29
|
+
"UnsupportedFormatError",
|
|
30
|
+
"UnsupportedTypeError",
|
|
31
|
+
"VerificationError",
|
|
32
|
+
"__author__",
|
|
33
|
+
"__version__",
|
|
34
|
+
]
|
ncarnate/__main__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
'''
|
|
5
|
+
|
|
6
|
+
Allows the package to be run as a module: ``python -m ncarnate``.
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2020-2026 Erick Edward Shepherd. MIT License — see the
|
|
9
|
+
top-level LICENSE file.
|
|
10
|
+
|
|
11
|
+
'''
|
|
12
|
+
|
|
13
|
+
# Standard library imports.
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
# Local application imports.
|
|
17
|
+
from ncarnate.cli import main
|
|
18
|
+
|
|
19
|
+
if __name__ == "__main__":
|
|
20
|
+
|
|
21
|
+
sys.exit(main())
|
ncarnate/cli.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
'''
|
|
5
|
+
|
|
6
|
+
The command line interface for the ncarnate package, which allows users to
|
|
7
|
+
alter the compression of a supported netCDF or HDF file.
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2020-2026 Erick Edward Shepherd. MIT License — see the
|
|
10
|
+
top-level LICENSE file.
|
|
11
|
+
|
|
12
|
+
'''
|
|
13
|
+
|
|
14
|
+
# Standard library imports.
|
|
15
|
+
import argparse
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
|
|
19
|
+
# Third party imports.
|
|
20
|
+
from tqdm import tqdm
|
|
21
|
+
|
|
22
|
+
# Local application imports.
|
|
23
|
+
from ncarnate.constants import PACKAGE_NAME
|
|
24
|
+
from ncarnate.constants import SUPPORTED_EXTENSIONS
|
|
25
|
+
from ncarnate.constants import __version__
|
|
26
|
+
from ncarnate.core import recompress
|
|
27
|
+
from ncarnate.errors import NcarnateError
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _has_supported_extension(path : str) -> bool:
|
|
31
|
+
|
|
32
|
+
extension = os.path.splitext(path)[1].lower().lstrip(".")
|
|
33
|
+
|
|
34
|
+
return extension in SUPPORTED_EXTENSIONS
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _files_to_paths(root : str, files : list[str]) -> list[str]:
|
|
38
|
+
|
|
39
|
+
paths = [os.path.join(root, file) for file in files]
|
|
40
|
+
|
|
41
|
+
return paths
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _get_files(paths : list[str], recursive : bool) -> list[str]:
|
|
45
|
+
|
|
46
|
+
'''
|
|
47
|
+
|
|
48
|
+
Expands the given paths into the list of files to process. Directories
|
|
49
|
+
are scanned (recursively with ``recursive``) for supported extensions;
|
|
50
|
+
explicitly named files must exist and carry a supported extension.
|
|
51
|
+
|
|
52
|
+
'''
|
|
53
|
+
|
|
54
|
+
paths = [os.path.abspath(path) for path in paths]
|
|
55
|
+
files = []
|
|
56
|
+
|
|
57
|
+
for path in paths:
|
|
58
|
+
|
|
59
|
+
if os.path.isdir(path):
|
|
60
|
+
|
|
61
|
+
if recursive:
|
|
62
|
+
|
|
63
|
+
for root, subdirectories, subfiles in os.walk(path):
|
|
64
|
+
|
|
65
|
+
subfiles = _files_to_paths(root, subfiles)
|
|
66
|
+
valid_files = filter(_has_supported_extension, subfiles)
|
|
67
|
+
|
|
68
|
+
files += sorted(valid_files)
|
|
69
|
+
|
|
70
|
+
else:
|
|
71
|
+
|
|
72
|
+
subfiles = _files_to_paths(path, os.listdir(path))
|
|
73
|
+
subfiles = filter(os.path.isfile, subfiles)
|
|
74
|
+
valid_files = filter(_has_supported_extension, subfiles)
|
|
75
|
+
|
|
76
|
+
files += sorted(valid_files)
|
|
77
|
+
|
|
78
|
+
elif os.path.isfile(path):
|
|
79
|
+
|
|
80
|
+
if not _has_supported_extension(path):
|
|
81
|
+
|
|
82
|
+
raise NcarnateError(
|
|
83
|
+
f"Unsupported file extension: {path} (supported: "
|
|
84
|
+
f"{', '.join(sorted(SUPPORTED_EXTENSIONS))})"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
files += [path]
|
|
88
|
+
|
|
89
|
+
else:
|
|
90
|
+
|
|
91
|
+
raise NcarnateError(f"No such file or directory: {path}")
|
|
92
|
+
|
|
93
|
+
# De-duplicate while preserving order: overlapping arguments (the same
|
|
94
|
+
# file twice, a directory plus a file inside it, or overlapping trees
|
|
95
|
+
# under -r) would otherwise recompress the same path more than once.
|
|
96
|
+
# Paths are already absolute, so equal files compare equal.
|
|
97
|
+
return list(dict.fromkeys(files))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _build_argument_parser() -> argparse.ArgumentParser:
|
|
101
|
+
|
|
102
|
+
parser = argparse.ArgumentParser(
|
|
103
|
+
prog = PACKAGE_NAME,
|
|
104
|
+
description = "Losslessly rewrites netCDF4/HDF5 files with "
|
|
105
|
+
"different compression settings."
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
parser.add_argument(
|
|
109
|
+
"path",
|
|
110
|
+
type = str,
|
|
111
|
+
nargs = "+",
|
|
112
|
+
help = "The path(s) to the file(s) to alter."
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
parser.add_argument(
|
|
116
|
+
"--complevel",
|
|
117
|
+
type = int,
|
|
118
|
+
default = 7,
|
|
119
|
+
choices = list(range(10)),
|
|
120
|
+
help = "The desired gzip deflate compression level."
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
group = parser.add_mutually_exclusive_group(required = False)
|
|
124
|
+
|
|
125
|
+
group.add_argument(
|
|
126
|
+
"--zlib",
|
|
127
|
+
dest = "zlib",
|
|
128
|
+
action = "store_true",
|
|
129
|
+
help = "Enables zlib gzip compression."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
group.add_argument(
|
|
133
|
+
"--no-zlib",
|
|
134
|
+
dest = "zlib",
|
|
135
|
+
action = "store_false",
|
|
136
|
+
help = "Disables zlib gzip compression."
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
parser.set_defaults(zlib = True)
|
|
140
|
+
|
|
141
|
+
group = parser.add_mutually_exclusive_group(required = False)
|
|
142
|
+
|
|
143
|
+
group.add_argument(
|
|
144
|
+
"--shuffle",
|
|
145
|
+
dest = "shuffle",
|
|
146
|
+
action = "store_true",
|
|
147
|
+
help = "Enables the HDF5 shuffle filter."
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
group.add_argument(
|
|
151
|
+
"--no-shuffle",
|
|
152
|
+
dest = "shuffle",
|
|
153
|
+
action = "store_false",
|
|
154
|
+
help = "Disables the HDF5 shuffle filter."
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
parser.set_defaults(shuffle = True)
|
|
158
|
+
|
|
159
|
+
group = parser.add_mutually_exclusive_group(required = False)
|
|
160
|
+
|
|
161
|
+
group.add_argument(
|
|
162
|
+
"--overwrite",
|
|
163
|
+
dest = "overwrite",
|
|
164
|
+
action = "store_true",
|
|
165
|
+
help = "Replaces each source file with its recompressed copy "
|
|
166
|
+
"(only after the copy verifies lossless)."
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
group.add_argument(
|
|
170
|
+
"--no-overwrite",
|
|
171
|
+
dest = "overwrite",
|
|
172
|
+
action = "store_false",
|
|
173
|
+
help = "Keeps the source file; writes the recompressed copy "
|
|
174
|
+
"alongside it with a '_recompressed' suffix."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
parser.set_defaults(overwrite = True)
|
|
178
|
+
|
|
179
|
+
parser.add_argument(
|
|
180
|
+
"--no-geolocation",
|
|
181
|
+
dest = "geolocation",
|
|
182
|
+
action = "store_false",
|
|
183
|
+
default = True,
|
|
184
|
+
help = "Converts HDF-EOS2 files SDS-only, skipping CF "
|
|
185
|
+
"geolocation reconstruction (the escape hatch for "
|
|
186
|
+
"unsupported projections/layouts)."
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
parser.add_argument(
|
|
190
|
+
"-r",
|
|
191
|
+
"--recursive",
|
|
192
|
+
dest = "recursive",
|
|
193
|
+
action = "store_true",
|
|
194
|
+
default = False,
|
|
195
|
+
help = "Acts recursively on the given director(y/ies)."
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
parser.add_argument(
|
|
199
|
+
"-V",
|
|
200
|
+
"--version",
|
|
201
|
+
action = "version",
|
|
202
|
+
version = f"{PACKAGE_NAME} {__version__}",
|
|
203
|
+
help = "Prints the current package version."
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
return parser
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _configure_logging() -> logging.Logger:
|
|
210
|
+
|
|
211
|
+
logger = logging.getLogger(PACKAGE_NAME)
|
|
212
|
+
|
|
213
|
+
# Idempotent: repeated main() calls in one process (e.g. under tests)
|
|
214
|
+
# must not stack duplicate handlers.
|
|
215
|
+
if not logger.handlers:
|
|
216
|
+
|
|
217
|
+
handler = logging.StreamHandler()
|
|
218
|
+
formatter = logging.Formatter("%(levelname)s: %(message)s")
|
|
219
|
+
|
|
220
|
+
handler.setFormatter(formatter)
|
|
221
|
+
logger.addHandler(handler)
|
|
222
|
+
|
|
223
|
+
logger.setLevel(logging.WARNING)
|
|
224
|
+
|
|
225
|
+
return logger
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def main() -> int:
|
|
229
|
+
|
|
230
|
+
parser = _build_argument_parser()
|
|
231
|
+
args = parser.parse_args()
|
|
232
|
+
logger = _configure_logging()
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
|
|
236
|
+
files = _get_files(args.path, args.recursive)
|
|
237
|
+
|
|
238
|
+
except NcarnateError as error:
|
|
239
|
+
|
|
240
|
+
logger.error(str(error))
|
|
241
|
+
|
|
242
|
+
return 2
|
|
243
|
+
|
|
244
|
+
if not files:
|
|
245
|
+
|
|
246
|
+
logger.error("No supported input files found.")
|
|
247
|
+
|
|
248
|
+
return 2
|
|
249
|
+
|
|
250
|
+
failures = 0
|
|
251
|
+
|
|
252
|
+
for file in tqdm(files, desc = "Files recompressed"):
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
|
|
256
|
+
recompress(
|
|
257
|
+
file,
|
|
258
|
+
zlib = args.zlib,
|
|
259
|
+
shuffle = args.shuffle,
|
|
260
|
+
complevel = args.complevel,
|
|
261
|
+
overwrite = args.overwrite,
|
|
262
|
+
geolocation = args.geolocation
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
except (NcarnateError, OSError) as error:
|
|
266
|
+
|
|
267
|
+
logger.error("%s", error)
|
|
268
|
+
|
|
269
|
+
failures += 1
|
|
270
|
+
|
|
271
|
+
except Exception:
|
|
272
|
+
|
|
273
|
+
logger.exception(
|
|
274
|
+
"Unexpected error while recompressing the given file: %s",
|
|
275
|
+
file
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
failures += 1
|
|
279
|
+
|
|
280
|
+
if failures:
|
|
281
|
+
|
|
282
|
+
logger.error(
|
|
283
|
+
"%d of %d file(s) failed to recompress.", failures, len(files)
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
return 1
|
|
287
|
+
|
|
288
|
+
return 0
|
ncarnate/constants.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
'''
|
|
5
|
+
|
|
6
|
+
Defines constant values shared across the ncarnate package.
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2020-2026 Erick Edward Shepherd. MIT License — see the
|
|
9
|
+
top-level LICENSE file.
|
|
10
|
+
|
|
11
|
+
'''
|
|
12
|
+
|
|
13
|
+
# Constant definitions.
|
|
14
|
+
PACKAGE_NAME = "ncarnate"
|
|
15
|
+
SUPPORTED_EXTENSIONS = ["nc", "nc4", "hdf", "hdf5", "h5", "he5"]
|
|
16
|
+
|
|
17
|
+
# Module dunder definitions.
|
|
18
|
+
__author__ = "Erick Edward Shepherd"
|
|
19
|
+
__version__ = "2.0.0"
|